Add unit test for variable length encoding HTTP/3 (#60766)
authorBadre BSAILA <54767641+pedrobsaila@users.noreply.github.com>
Tue, 9 Nov 2021 19:51:38 +0000 (20:51 +0100)
committerGitHub <noreply@github.com>
Tue, 9 Nov 2021 19:51:38 +0000 (20:51 +0100)
Fixes #51519

src/libraries/Common/src/System/Net/Http/aspnetcore/Http3/Helpers/VariableLengthIntegerHelper.cs
src/libraries/Common/tests/Common.Tests.csproj
src/libraries/Common/tests/Tests/System/Net/aspnetcore/Http3/VariableLengthIntegerHelperTests.cs [new file with mode: 0644]

index 33869c9..fb08f76 100644 (file)
@@ -149,7 +149,7 @@ namespace System.Net.Http
             Debug.Assert(longToEncode >= 0);
             Debug.Assert(longToEncode <= EightByteLimit);
 
-            if (longToEncode < OneByteLimit)
+            if (longToEncode <= OneByteLimit)
             {
                 if (buffer.Length != 0)
                 {
@@ -158,7 +158,7 @@ namespace System.Net.Http
                     return true;
                 }
             }
-            else if (longToEncode < TwoByteLimit)
+            else if (longToEncode <= TwoByteLimit)
             {
                 if (BinaryPrimitives.TryWriteUInt16BigEndian(buffer, (ushort)((uint)longToEncode | TwoByteLengthMask)))
                 {
@@ -166,7 +166,7 @@ namespace System.Net.Http
                     return true;
                 }
             }
-            else if (longToEncode < FourByteLimit)
+            else if (longToEncode <= FourByteLimit)
             {
                 if (BinaryPrimitives.TryWriteUInt32BigEndian(buffer, (uint)longToEncode | FourByteLengthMask))
                 {
@@ -200,9 +200,9 @@ namespace System.Net.Http
             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
         }
     }
index 37bd031..7273bdd 100644 (file)
@@ -67,6 +67,8 @@
              Link="Common\System\Net\Http\aspnetcore\Http2\Hpack\H2StaticTable.Http2.cs" />
     <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Http2\Hpack\StatusCodes.cs"
              Link="Common\System\Net\Http\aspnetcore\Http2\Hpack\StatusCodes.cs" />
+    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Http3\Helpers\VariableLengthIntegerHelper.cs"
+             Link="Common\System\Net\Http\aspnetcore\Http3\Helpers\VariableLengthIntegerHelper.cs" />
     <Compile Include="$(CommonPath)System\Text\SimpleRegex.cs"
              Link="Common\System\Text\SimpleRegex.cs" />
     <Compile Include="$(CommonPath)System\Text\ValueStringBuilder.cs"
@@ -95,6 +97,7 @@
     <Compile Include="Tests\System\Net\aspnetcore\Http2\HPackDecoderTest.cs" />
     <Compile Include="Tests\System\Net\aspnetcore\Http2\HPackIntegerTest.cs" />
     <Compile Include="Tests\System\Net\aspnetcore\Http2\HuffmanDecodingTests.cs" />
+    <Compile Include="Tests\System\Net\aspnetcore\Http3\VariableLengthIntegerHelperTests.cs" />
     <Compile Include="System\Net\Sockets\Fletcher32.cs"
              Link="System\Net\Sockets\Fletcher32.cs" />
     <Compile Include="$(CommonPath)System\Net\Logging\NetEventSource.Common.cs"
diff --git a/src/libraries/Common/tests/Tests/System/Net/aspnetcore/Http3/VariableLengthIntegerHelperTests.cs b/src/libraries/Common/tests/Tests/System/Net/aspnetcore/Http3/VariableLengthIntegerHelperTests.cs
new file mode 100644 (file)
index 0000000..3fc9a0c
--- /dev/null
@@ -0,0 +1,372 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Buffers;
+using System.Net.Http;
+using Xunit;
+
+namespace Common.Tests.Tests.System.Net.aspnetcore.Http3
+{
+    public class VariableLengthIntegerHelperTests
+    {
+        [Fact]
+        public void TryRead_FromReadOnlySpan_BufferEmpty()
+        {
+            ReadOnlySpan<byte> readOnlySpan = new ReadOnlySpan<byte>();
+            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<byte> readOnlySpan = new ReadOnlySpan<byte>(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<byte> readOnlySpan = new ReadOnlySpan<byte>(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<byte> readOnlySpan = new ReadOnlySpan<byte>(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<byte> readOnlySpan = new ReadOnlySpan<byte>(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<byte> readOnlySpan = new ReadOnlySpan<byte>(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<byte> readOnlySpan = new ReadOnlySpan<byte>(
+                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<byte> readOnlySpan = new ReadOnlySpan<byte>(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<byte> readOnlySequence = new ReadOnlySequence<byte>(new byte[]
+            {
+                1
+            });
+            SequenceReader<byte> sequenceReader = new SequenceReader<byte>(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<T> : ReadOnlySequenceSegment<T>
+        {
+            internal MemorySegment(ReadOnlyMemory<T> memory)
+            {
+                Memory = memory;
+            }
+
+            internal MemorySegment<T> Append(ReadOnlyMemory<T> memory)
+            {
+                var segment = new MemorySegment<T>(memory)
+                {
+                    RunningIndex = RunningIndex + Memory.Length
+                };
+
+                Next = segment;
+
+                return segment;
+            }
+        }
+
+        [Fact]
+        public void TryRead_FromSequenceReader_InitialTwoByteLengthMask_SegmentedSequence()
+        {
+            MemorySegment<byte> memorySegment1 = new MemorySegment<byte>(new byte[] { 64 });
+            MemorySegment<byte> memorySegment2 = memorySegment1.Append(new byte[] { 1 });
+            ReadOnlySequence<byte> readOnlySequence = new ReadOnlySequence<byte>(
+                memorySegment1, 0, memorySegment2, memorySegment2.Memory.Length);
+            SequenceReader<byte> sequenceReader = new SequenceReader<byte>(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<byte> memorySegment1 = new MemorySegment<byte>(new byte[] { 192 });
+            MemorySegment<byte> memorySegment2 = memorySegment1.Append(new byte[] { 0, 0, 0, 0, 0, 0, 2 });
+            ReadOnlySequence<byte> readOnlySequence = new ReadOnlySequence<byte>(
+                memorySegment1, 0, memorySegment2, memorySegment2.Memory.Length);
+            SequenceReader<byte> sequenceReader = new SequenceReader<byte>(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<byte> memorySegment1 = new MemorySegment<byte>(new byte[] { 192 });
+            MemorySegment<byte> memorySegment2 = memorySegment1.Append(new byte[] { 0, 0, 0, 0, 0, 2 });
+            ReadOnlySequence<byte> readOnlySequence = new ReadOnlySequence<byte>(
+                memorySegment1, 0, memorySegment2, memorySegment2.Memory.Length);
+            SequenceReader<byte> sequenceReader = new SequenceReader<byte>(readOnlySequence);
+            bool isSuccess = VariableLengthIntegerHelper.TryRead(ref sequenceReader,
+                out long value);
+
+            Assert.False(isSuccess);
+            Assert.Equal(0, value);
+        }
+
+        [Fact]
+        public void GetInteger_ValidSegmentedSequence()
+        {
+            MemorySegment<byte> memorySegment1 = new MemorySegment<byte>(new byte[] { 192 });
+            MemorySegment<byte> memorySegment2 = memorySegment1.Append(new byte[] { 0, 0, 0, 0, 0, 0, 2 });
+            ReadOnlySequence<byte> readOnlySequence = new ReadOnlySequence<byte>(
+                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<byte> memorySegment1 = new MemorySegment<byte>(new byte[] { 192 });
+            MemorySegment<byte> memorySegment2 = memorySegment1.Append(new byte[] { 0, 0, 0, 0, 0, 2 });
+            ReadOnlySequence<byte> readOnlySequence = new ReadOnlySequence<byte>(
+                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<byte> span = new Span<byte>();
+            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<byte> span = new Span<byte>(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<byte> span = new Span<byte>(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<byte> span = new Span<byte>(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<byte> span = new Span<byte>(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<byte> span = new Span<byte>(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<byte> span = new Span<byte>(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<byte> span = new Span<byte>(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);
+        }
+    }
+}