diff --git a/src/libraries/Common/src/System/Net/Http/aspnetcore/Http3/Helpers/VariableLengthIntegerHelper.cs b/src/libraries/Common/src/System/Net/Http/aspnetcore/Http3/Helpers/VariableLengthIntegerHelper.cs index 33869c9254aeed..fb08f762705c04 100644 --- a/src/libraries/Common/src/System/Net/Http/aspnetcore/Http3/Helpers/VariableLengthIntegerHelper.cs +++ b/src/libraries/Common/src/System/Net/Http/aspnetcore/Http3/Helpers/VariableLengthIntegerHelper.cs @@ -149,7 +149,7 @@ public static bool TryWrite(Span buffer, long longToEncode, out int bytesW Debug.Assert(longToEncode >= 0); Debug.Assert(longToEncode <= EightByteLimit); - if (longToEncode < OneByteLimit) + if (longToEncode <= OneByteLimit) { if (buffer.Length != 0) { @@ -158,7 +158,7 @@ public static bool TryWrite(Span buffer, long longToEncode, out int bytesW return true; } } - else if (longToEncode < TwoByteLimit) + else if (longToEncode <= TwoByteLimit) { if (BinaryPrimitives.TryWriteUInt16BigEndian(buffer, (ushort)((uint)longToEncode | TwoByteLengthMask))) { @@ -166,7 +166,7 @@ public static bool TryWrite(Span buffer, long longToEncode, out int bytesW return true; } } - else if (longToEncode < FourByteLimit) + else if (longToEncode <= FourByteLimit) { if (BinaryPrimitives.TryWriteUInt32BigEndian(buffer, (uint)longToEncode | FourByteLengthMask)) { @@ -200,9 +200,9 @@ public static int GetByteCount(long value) Debug.Assert(value <= EightByteLimit); return - value < OneByteLimit ? 1 : - value < TwoByteLimit ? 2 : - value < FourByteLimit ? 4 : + value <= OneByteLimit ? 1 : + value <= TwoByteLimit ? 2 : + value <= FourByteLimit ? 4 : 8; // EightByteLimit } } diff --git a/src/libraries/Common/tests/Common.Tests.csproj b/src/libraries/Common/tests/Common.Tests.csproj index 37bd031cc647aa..7273bddf885948 100644 --- a/src/libraries/Common/tests/Common.Tests.csproj +++ b/src/libraries/Common/tests/Common.Tests.csproj @@ -67,6 +67,8 @@ Link="Common\System\Net\Http\aspnetcore\Http2\Hpack\H2StaticTable.Http2.cs" /> + + readOnlySpan = new ReadOnlySpan(); + bool isSuccess = VariableLengthIntegerHelper.TryRead(readOnlySpan, + out long value, out int bytesRead); + + Assert.False(isSuccess); + Assert.Equal(0, value); + Assert.Equal(0, bytesRead); + } + + [Fact] + public void TryRead_FromReadOnlySpan_BufferNotEmpty_InitialOneByteLengthMask() + { + ReadOnlySpan readOnlySpan = new ReadOnlySpan(new byte[] + { + 1 + }); + bool isSuccess = VariableLengthIntegerHelper.TryRead(readOnlySpan, + out long value, out int bytesRead); + + Assert.True(isSuccess); + Assert.Equal(1, value); + Assert.Equal(1, bytesRead); + } + + [Fact] + public void TryRead_FromReadOnlySpan_BufferNotEmpty_InitialTwoByteLengthMask_Buffer16BigEndian() + { + ReadOnlySpan readOnlySpan = new ReadOnlySpan(new byte[] + { + 64, + 1 + }); + bool isSuccess = VariableLengthIntegerHelper.TryRead(readOnlySpan, + out long value, out int bytesRead); + + Assert.True(isSuccess); + Assert.Equal(1, value); + Assert.Equal(2, bytesRead); + } + + [Fact] + public void TryRead_FromReadOnlySpan_BufferNotEmpty_InitialTwoByteLengthMask_BufferNot16BigEndian() + { + ReadOnlySpan readOnlySpan = new ReadOnlySpan(new byte[] + { + 64 + }); + bool isSuccess = VariableLengthIntegerHelper.TryRead(readOnlySpan, + out long value, out int bytesRead); + + Assert.False(isSuccess); + Assert.Equal(0, value); + Assert.Equal(0, bytesRead); + } + + [Fact] + public void TryRead_FromReadOnlySpan_BufferNotEmpty_InitialFourByteLengthMask_TryReadUInt32BigEndian() + { + ReadOnlySpan readOnlySpan = new ReadOnlySpan(new byte[] + { + 128, + 0, + 0, + 2 + }); + bool isSuccess = VariableLengthIntegerHelper.TryRead(readOnlySpan, + out long value, out int bytesRead); + + Assert.True(isSuccess); + Assert.Equal(2, value); + Assert.Equal(4, bytesRead); + } + + [Fact] + public void TryRead_FromReadOnlySpan_BufferNotEmpty_InitialFourByteLengthMask_TryReadNotUInt32BigEndian() + { + ReadOnlySpan readOnlySpan = new ReadOnlySpan(new byte[] + { + 128 + }); + bool isSuccess = VariableLengthIntegerHelper.TryRead(readOnlySpan, + out long value, out int bytesRead); + + Assert.False(isSuccess); + Assert.Equal(0, value); + Assert.Equal(0, bytesRead); + } + + [Fact] + public void TryRead_FromReadOnlySpan_BufferNotEmpty_InitialEightByteLengthMask_TryReadUInt64BigEndian() + { + ReadOnlySpan readOnlySpan = new ReadOnlySpan( + new byte[] + { + 192, 0, 0, 0, + 0, 0, 0, 4 + }); + bool isSuccess = VariableLengthIntegerHelper.TryRead(readOnlySpan, + out long value, out int bytesRead); + + Assert.True(isSuccess); + Assert.Equal(4, value); + Assert.Equal(8, bytesRead); + } + + [Fact] + public void TryRead_FromReadOnlySpan_BufferNotEmpty_InitialEightByteLengthMask_TryReadNotUInt64BigEndian() + { + ReadOnlySpan readOnlySpan = new ReadOnlySpan(new byte[] + { + 192 + }); + bool isSuccess = VariableLengthIntegerHelper.TryRead(readOnlySpan, + out long value, out int bytesRead); + + Assert.False(isSuccess); + Assert.Equal(0, value); + Assert.Equal(0, bytesRead); + } + + [Fact] + public void TryRead_FromSequenceReader_NotSegmentedSequence() + { + ReadOnlySequence readOnlySequence = new ReadOnlySequence(new byte[] + { + 1 + }); + SequenceReader sequenceReader = new SequenceReader(readOnlySequence); + bool isSuccess = VariableLengthIntegerHelper.TryRead(ref sequenceReader, + out long value); + + Assert.True(isSuccess); + Assert.Equal(1, value); + Assert.Equal(1, sequenceReader.CurrentSpanIndex); + } + + internal class MemorySegment : ReadOnlySequenceSegment + { + internal MemorySegment(ReadOnlyMemory memory) + { + Memory = memory; + } + + internal MemorySegment Append(ReadOnlyMemory memory) + { + var segment = new MemorySegment(memory) + { + RunningIndex = RunningIndex + Memory.Length + }; + + Next = segment; + + return segment; + } + } + + [Fact] + public void TryRead_FromSequenceReader_InitialTwoByteLengthMask_SegmentedSequence() + { + MemorySegment memorySegment1 = new MemorySegment(new byte[] { 64 }); + MemorySegment memorySegment2 = memorySegment1.Append(new byte[] { 1 }); + ReadOnlySequence readOnlySequence = new ReadOnlySequence( + memorySegment1, 0, memorySegment2, memorySegment2.Memory.Length); + SequenceReader sequenceReader = new SequenceReader(readOnlySequence); + bool isSuccess = VariableLengthIntegerHelper.TryRead(ref sequenceReader, + out long value); + + Assert.True(isSuccess); + Assert.Equal(1, value); + Assert.Equal(1, sequenceReader.CurrentSpanIndex); + } + + [Fact] + public void TryRead_FromSequenceReader_InitialFourByteLengthMask_SegmentedSequence() + { + MemorySegment memorySegment1 = new MemorySegment(new byte[] { 192 }); + MemorySegment memorySegment2 = memorySegment1.Append(new byte[] { 0, 0, 0, 0, 0, 0, 2 }); + ReadOnlySequence readOnlySequence = new ReadOnlySequence( + memorySegment1, 0, memorySegment2, memorySegment2.Memory.Length); + SequenceReader sequenceReader = new SequenceReader(readOnlySequence); + bool isSuccess = VariableLengthIntegerHelper.TryRead(ref sequenceReader, + out long value); + + Assert.True(isSuccess); + Assert.Equal(2, value); + Assert.Equal(7, sequenceReader.CurrentSpanIndex); + } + + [Fact] + public void TryRead_FromSequenceReader_NotValidSegmentedSequence() + { + MemorySegment memorySegment1 = new MemorySegment(new byte[] { 192 }); + MemorySegment memorySegment2 = memorySegment1.Append(new byte[] { 0, 0, 0, 0, 0, 2 }); + ReadOnlySequence readOnlySequence = new ReadOnlySequence( + memorySegment1, 0, memorySegment2, memorySegment2.Memory.Length); + SequenceReader sequenceReader = new SequenceReader(readOnlySequence); + bool isSuccess = VariableLengthIntegerHelper.TryRead(ref sequenceReader, + out long value); + + Assert.False(isSuccess); + Assert.Equal(0, value); + } + + [Fact] + public void GetInteger_ValidSegmentedSequence() + { + MemorySegment memorySegment1 = new MemorySegment(new byte[] { 192 }); + MemorySegment memorySegment2 = memorySegment1.Append(new byte[] { 0, 0, 0, 0, 0, 0, 2 }); + ReadOnlySequence readOnlySequence = new ReadOnlySequence( + memorySegment1, 0, memorySegment2, memorySegment2.Memory.Length); + long result = VariableLengthIntegerHelper.GetInteger(readOnlySequence, + out SequencePosition consumed, out SequencePosition examined); + + Assert.Equal(2, result); + Assert.Equal(7, consumed.GetInteger()); + Assert.Equal(7, examined.GetInteger()); + } + + [Fact] + public void GetInteger_NotValidSegmentedSequence() + { + MemorySegment memorySegment1 = new MemorySegment(new byte[] { 192 }); + MemorySegment memorySegment2 = memorySegment1.Append(new byte[] { 0, 0, 0, 0, 0, 2 }); + ReadOnlySequence readOnlySequence = new ReadOnlySequence( + memorySegment1, 0, memorySegment2, memorySegment2.Memory.Length); + long result = VariableLengthIntegerHelper.GetInteger(readOnlySequence, + out SequencePosition consumed, out SequencePosition examined); + + Assert.Equal(-1, result); + Assert.Equal(0, consumed.GetInteger()); + Assert.Equal(6, examined.GetInteger()); + } + + [Fact] + public void TryWrite_BufferEmpty() + { + Span span = new Span(); + long longToEncode = 1; + bool isSuccess = VariableLengthIntegerHelper.TryWrite(span, + longToEncode, out int bytesWritten); + + Assert.False(isSuccess); + Assert.Equal(0, bytesWritten); + } + + [Theory] + [InlineData(0)] + [InlineData(2)] + [InlineData(63)] + public void TryWrite_BufferNotEmpty_OneByteLimit(long longToEncode) + { + Span span = new Span(new byte[1]); + bool isSuccess = VariableLengthIntegerHelper.TryWrite(span, + longToEncode, out int bytesWritten); + + Assert.True(isSuccess); + Assert.Equal(1, bytesWritten); + Assert.Equal(longToEncode, span[0]); + } + + [Theory] + [InlineData(64, new byte[] { 64, 64 })] + [InlineData(66, new byte[] { 64, 66 })] + [InlineData(16383, new byte[] { 127, 255})] + public void TryWrite_BufferNotEmpty_TwoByteLimit(long longToEncode, + byte[] expected) + { + Span span = new Span(new byte[2]); + bool isSuccess = VariableLengthIntegerHelper.TryWrite(span, + longToEncode, out int bytesWritten); + + Assert.True(isSuccess); + Assert.Equal(2, bytesWritten); + Assert.Equal(expected, span.ToArray()); + } + + [Fact] + public void TryWrite_BufferNotSizedCorrectly_TwoByteLimit() + { + long longToEncode = 64; + Span span = new Span(new byte[1]); + bool isSuccess = VariableLengthIntegerHelper.TryWrite(span, + longToEncode, out int bytesWritten); + + Assert.False(isSuccess); + Assert.Equal(0, bytesWritten); + } + + [Theory] + [InlineData(16384, new byte[] {128, 0, 64, 0})] + [InlineData(16386, new byte[] { 128, 0, 64, 2 })] + [InlineData(1073741823, new byte[] { 191, 255, 255, 255 })] + public void TryWrite_BufferNotEmpty_FourByteLimit(long longToEncode, + byte[] expected) + { + Span span = new Span(new byte[4]); + bool isSuccess = VariableLengthIntegerHelper.TryWrite(span, + longToEncode, out int bytesWritten); + + Assert.True(isSuccess); + Assert.Equal(4, bytesWritten); + Assert.Equal(expected, span.ToArray()); + } + + [Fact] + public void TryWrite_BufferNotSizedCorrectly_FourByteLimit() + { + long longToEncode = 16384; + Span span = new Span(new byte[1]); + bool isSuccess = VariableLengthIntegerHelper.TryWrite(span, + longToEncode, out int bytesWritten); + + Assert.False(isSuccess); + Assert.Equal(0, bytesWritten); + } + + [Theory] + [InlineData(1073741824, new byte[] { 192, 0, 0, 0, 64, 0, 0, 0 })] + [InlineData(1073741826, new byte[] { 192, 0, 0, 0, 64, 0, 0, 2 })] + [InlineData(4611686018427387903, new byte[] { 255, 255, 255, 255, 255, 255, 255, 255 })] + public void TryWrite_BufferNotEmpty_EightByteLimit(long longToEncode, + byte[] expected) + { + Span span = new Span(new byte[8]); + bool isSuccess = VariableLengthIntegerHelper.TryWrite(span, + longToEncode, out int bytesWritten); + + Assert.True(isSuccess); + Assert.Equal(8, bytesWritten); + Assert.Equal(expected, span.ToArray()); + } + + [Fact] + public void TryWrite_BufferNotSizedCorrectly_EightByteLimit() + { + long longToEncode = 1073741824; + Span span = new Span(new byte[1]); + bool isSuccess = VariableLengthIntegerHelper.TryWrite(span, + longToEncode, out int bytesWritten); + + Assert.False(isSuccess); + Assert.Equal(0, bytesWritten); + } + + [Theory] + [InlineData(1, 1)] + [InlineData(64, 2)] + [InlineData(16384, 4)] + [InlineData(1073741824, 8)] + public void GetByteCountTest(long longToEncode, int expectedLimit) + { + int result = VariableLengthIntegerHelper.GetByteCount(longToEncode); + + Assert.Equal(expectedLimit, result); + } + } +}