< Summary

Information
Class: System.Net.Http.VariableLengthIntegerHelper
Assembly: System.Net.Http
File(s): D:\runner\runtime\src\libraries\Common\src\System\Net\Http\aspnetcore\Http3\Helpers\VariableLengthIntegerHelper.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 81
Coverable lines: 81
Total lines: 209
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 36
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
TryRead(...)0%16160%
TryWrite(...)0%14140%
WriteInteger(...)100%110%
GetByteCount(...)0%660%

File(s)

D:\runner\runtime\src\libraries\Common\src\System\Net\Http\aspnetcore\Http3\Helpers\VariableLengthIntegerHelper.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Buffers.Binary;
 6using System.Diagnostics;
 7
 8namespace System.Net.Http
 9{
 10    /// <summary>
 11    /// Variable length integer encoding and decoding methods. Based on https://tools.ietf.org/html/draft-ietf-quic-tran
 12    /// A variable-length integer can use 1, 2, 4, or 8 bytes.
 13    /// </summary>
 14    internal static class VariableLengthIntegerHelper
 15    {
 16        public const int MaximumEncodedLength = 8;
 17
 18        // The high 4 bits indicate the length of the integer.
 19        // 00 = length 1
 20        // 01 = length 2
 21        // 10 = length 4
 22        // 11 = length 8
 23        private const byte LengthMask = 0xC0;
 24        private const byte InitialOneByteLengthMask = 0x00;
 25        private const byte InitialTwoByteLengthMask = 0x40;
 26        private const byte InitialFourByteLengthMask = 0x80;
 27        private const byte InitialEightByteLengthMask = 0xC0;
 28
 29        // Bits to subtract to remove the length.
 30        private const uint TwoByteLengthMask = 0x4000;
 31        private const uint FourByteLengthMask = 0x80000000;
 32        private const ulong EightByteLengthMask = 0xC000000000000000;
 33
 34        // public for internal use in aspnetcore
 35        public const uint OneByteLimit = (1U << 6) - 1;
 36        public const uint TwoByteLimit = (1U << 14) - 1;
 37        public const uint FourByteLimit = (1U << 30) - 1;
 38        public const long EightByteLimit = (1L << 62) - 1;
 39
 40        public static bool TryRead(ReadOnlySpan<byte> buffer, out long value, out int bytesRead)
 041        {
 042            if (buffer.Length != 0)
 043            {
 044                byte firstByte = buffer[0];
 45
 046                switch (firstByte & LengthMask)
 47                {
 48                    case InitialOneByteLengthMask:
 049                        value = firstByte;
 050                        bytesRead = 1;
 051                        return true;
 52                    case InitialTwoByteLengthMask:
 053                        if (BinaryPrimitives.TryReadUInt16BigEndian(buffer, out ushort serializedShort))
 054                        {
 055                            value = serializedShort - TwoByteLengthMask;
 056                            bytesRead = 2;
 057                            return true;
 58                        }
 059                        break;
 60                    case InitialFourByteLengthMask:
 061                        if (BinaryPrimitives.TryReadUInt32BigEndian(buffer, out uint serializedInt))
 062                        {
 063                            value = serializedInt - FourByteLengthMask;
 064                            bytesRead = 4;
 065                            return true;
 66                        }
 067                        break;
 68                    default: // InitialEightByteLengthMask
 069                        Debug.Assert((firstByte & LengthMask) == InitialEightByteLengthMask);
 070                        if (BinaryPrimitives.TryReadUInt64BigEndian(buffer, out ulong serializedLong))
 071                        {
 072                            value = (long)(serializedLong - EightByteLengthMask);
 073                            Debug.Assert(value >= 0 && value <= EightByteLimit, "Serialized values are within [0, 2^62).
 74
 075                            bytesRead = 8;
 076                            return true;
 77                        }
 078                        break;
 79                }
 080            }
 81
 082            value = 0;
 083            bytesRead = 0;
 084            return false;
 085        }
 86
 87        public static bool TryRead(ref SequenceReader<byte> reader, out long value)
 88        {
 89            // Hot path: we probably have the entire integer in one unbroken span.
 90            if (TryRead(reader.UnreadSpan, out value, out int bytesRead))
 91            {
 92                reader.Advance(bytesRead);
 93                return true;
 94            }
 95
 96            // Cold path: copy to a temporary buffer before calling span-based read.
 97            return TryReadSlow(ref reader, out value);
 98
 99            static bool TryReadSlow(ref SequenceReader<byte> reader, out long value)
 100            {
 101                ReadOnlySpan<byte> span = reader.CurrentSpan;
 102
 103                if (reader.TryPeek(out byte firstByte))
 104                {
 105                    int length =
 106                        (firstByte & LengthMask) switch
 107                        {
 108                            InitialOneByteLengthMask => 1,
 109                            InitialTwoByteLengthMask => 2,
 110                            InitialFourByteLengthMask => 4,
 111                            _ => 8 // LengthEightByte
 112                        };
 113
 114                    Span<byte> temp = (stackalloc byte[8])[..length];
 115                    if (reader.TryCopyTo(temp))
 116                    {
 117                        bool result = TryRead(temp, out value, out int bytesRead);
 118                        Debug.Assert(result);
 119                        Debug.Assert(bytesRead == length);
 120
 121                        reader.Advance(bytesRead);
 122                        return true;
 123                    }
 124                }
 125
 126                value = 0;
 127                return false;
 128            }
 129        }
 130
 131        // If callsite has 'examined', set it to buffer.End if the integer wasn't successfully read, otherwise set exami
 132        public static bool TryGetInteger(in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out long integ
 133        {
 134            var reader = new SequenceReader<byte>(buffer);
 135            if (TryRead(ref reader, out integer))
 136            {
 137                consumed = buffer.GetPosition(reader.Consumed);
 138                return true;
 139            }
 140            else
 141            {
 142                consumed = buffer.Start;
 143                return false;
 144            }
 145        }
 146
 147        public static bool TryWrite(Span<byte> buffer, long longToEncode, out int bytesWritten)
 0148        {
 0149            Debug.Assert(longToEncode >= 0);
 0150            Debug.Assert(longToEncode <= EightByteLimit);
 151
 0152            if (longToEncode <= OneByteLimit)
 0153            {
 0154                if (buffer.Length != 0)
 0155                {
 0156                    buffer[0] = (byte)longToEncode;
 0157                    bytesWritten = 1;
 0158                    return true;
 159                }
 0160            }
 0161            else if (longToEncode <= TwoByteLimit)
 0162            {
 0163                if (BinaryPrimitives.TryWriteUInt16BigEndian(buffer, (ushort)((uint)longToEncode | TwoByteLengthMask)))
 0164                {
 0165                    bytesWritten = 2;
 0166                    return true;
 167                }
 0168            }
 0169            else if (longToEncode <= FourByteLimit)
 0170            {
 0171                if (BinaryPrimitives.TryWriteUInt32BigEndian(buffer, (uint)longToEncode | FourByteLengthMask))
 0172                {
 0173                    bytesWritten = 4;
 0174                    return true;
 175                }
 0176            }
 177            else // EightByteLimit
 0178            {
 0179                if (BinaryPrimitives.TryWriteUInt64BigEndian(buffer, (ulong)longToEncode | EightByteLengthMask))
 0180                {
 0181                    bytesWritten = 8;
 0182                    return true;
 183                }
 0184            }
 185
 0186            bytesWritten = 0;
 0187            return false;
 0188        }
 189
 190        public static int WriteInteger(Span<byte> buffer, long longToEncode)
 0191        {
 0192            bool res = TryWrite(buffer, longToEncode, out int bytesWritten);
 0193            Debug.Assert(res);
 0194            return bytesWritten;
 0195        }
 196
 197        public static int GetByteCount(long value)
 0198        {
 0199            Debug.Assert(value >= 0);
 0200            Debug.Assert(value <= EightByteLimit);
 201
 0202            return
 0203                value <= OneByteLimit ? 1 :
 0204                value <= TwoByteLimit ? 2 :
 0205                value <= FourByteLimit ? 4 :
 0206                8; // EightByteLimit
 0207        }
 208    }
 209}