fix Huffman decoding issue and add Huffman decoding tests
authorGeoff Kizer <geoffrek>
Fri, 31 Aug 2018 05:50:18 +0000 (22:50 -0700)
committerGeoff Kizer <geoffrek>
Thu, 6 Sep 2018 21:30:04 +0000 (14:30 -0700)
Commit migrated from https://github.com/dotnet/corefx/commit/e5388b2fd7f9ab29800426f843b0c026b49ba7e4

src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HPack/Huffman.cs
src/libraries/System.Net.Http/tests/FunctionalTests/HuffmanDecodingTests.cs [new file with mode: 0644]
src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj

index ba4d585..98d22e2 100644 (file)
@@ -313,10 +313,15 @@ namespace System.Net.Http.HPack
             int lastDecodedBits = 0;
             while (i < count)
             {
+                // Note that if lastDecodeBits is 3 or more, then we will only get 5 bits (or less)
+                // from src[i]. Thus we need to read 5 bytes here to ensure that we always have 
+                // at least 30 bits available for decoding.
+                // TODO ISSUE 31751: Rework this as part of Huffman perf improvements
                 uint next = (uint)(src[i] << 24 + lastDecodedBits);
                 next |= (i + 1 < src.Length ? (uint)(src[i + 1] << 16 + lastDecodedBits) : 0);
                 next |= (i + 2 < src.Length ? (uint)(src[i + 2] << 8 + lastDecodedBits) : 0);
                 next |= (i + 3 < src.Length ? (uint)(src[i + 3] << lastDecodedBits) : 0);
+                next |= (i + 4 < src.Length ? (uint)(src[i + 4] >> (8 - lastDecodedBits)) : 0);
 
                 uint ones = (uint)(int.MinValue >> (8 - lastDecodedBits - 1));
                 if (i == count - 1 && lastDecodedBits > 0 && (next & ones) == ones)
@@ -332,7 +337,7 @@ namespace System.Net.Http.HPack
                 // of the input, we need to make sure we pass the correct number of valid bits
                 // left, otherwise the trailing 0s in next may form a valid symbol.
                 int validBits = Math.Min(30, (8 - lastDecodedBits) + (count - i - 1) * 8);
-                int ch = Decode(next, validBits, out int decodedBits);
+                int ch = DecodeValue(next, validBits, out int decodedBits);
 
                 if (ch == -1)
                 {
@@ -377,7 +382,7 @@ namespace System.Net.Http.HPack
         /// </param>
         /// <param name="decodedBits">The number of bits decoded from <paramref name="data"/>.</param>
         /// <returns>The decoded symbol.</returns>
-        public static int Decode(uint data, int validBits, out int decodedBits)
+        public static int DecodeValue(uint data, int validBits, out int decodedBits)
         {
             // The code below implements the decoding logic for a canonical Huffman code.
             //
diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HuffmanDecodingTests.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HuffmanDecodingTests.cs
new file mode 100644 (file)
index 0000000..5eaa9d1
--- /dev/null
@@ -0,0 +1,442 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Xunit;
+
+namespace System.Net.Http.Functional.Tests
+{
+    public class HuffmanDecodingTests
+    {
+        private static readonly Func<byte[], int, int, byte[], int> s_decodeDelegate = GetDecodeDelegate();
+
+        private static Func<byte[], int, int, byte[], int> GetDecodeDelegate()
+        {
+            Assembly assembly = typeof(HttpClient).Assembly;
+            Type huffmanType = assembly.GetType("System.Net.Http.HPack.Huffman");
+            MethodInfo decodeMethod = huffmanType.GetMethod("Decode", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
+            return (Func<byte[], int, int, byte[], int>)Delegate.CreateDelegate(typeof(Func<byte[], int, int, byte[], int>), decodeMethod);
+        }
+
+        private static int Decode(byte[] source, int length, byte[] destination)
+        {
+            return s_decodeDelegate(source, 0, length, destination);
+        }
+
+        private static readonly (uint code, int bitLength)[] s_encodingTable = new (uint code, int bitLength)[]
+        {
+            (0b11111111_11000000_00000000_00000000, 13),
+            (0b11111111_11111111_10110000_00000000, 23),
+            (0b11111111_11111111_11111110_00100000, 28),
+            (0b11111111_11111111_11111110_00110000, 28),
+            (0b11111111_11111111_11111110_01000000, 28),
+            (0b11111111_11111111_11111110_01010000, 28),
+            (0b11111111_11111111_11111110_01100000, 28),
+            (0b11111111_11111111_11111110_01110000, 28),
+            (0b11111111_11111111_11111110_10000000, 28),
+            (0b11111111_11111111_11101010_00000000, 24),
+            (0b11111111_11111111_11111111_11110000, 30),
+            (0b11111111_11111111_11111110_10010000, 28),
+            (0b11111111_11111111_11111110_10100000, 28),
+            (0b11111111_11111111_11111111_11110100, 30),
+            (0b11111111_11111111_11111110_10110000, 28),
+            (0b11111111_11111111_11111110_11000000, 28),
+            (0b11111111_11111111_11111110_11010000, 28),
+            (0b11111111_11111111_11111110_11100000, 28),
+            (0b11111111_11111111_11111110_11110000, 28),
+            (0b11111111_11111111_11111111_00000000, 28),
+            (0b11111111_11111111_11111111_00010000, 28),
+            (0b11111111_11111111_11111111_00100000, 28),
+            (0b11111111_11111111_11111111_11111000, 30),
+            (0b11111111_11111111_11111111_00110000, 28),
+            (0b11111111_11111111_11111111_01000000, 28),
+            (0b11111111_11111111_11111111_01010000, 28),
+            (0b11111111_11111111_11111111_01100000, 28),
+            (0b11111111_11111111_11111111_01110000, 28),
+            (0b11111111_11111111_11111111_10000000, 28),
+            (0b11111111_11111111_11111111_10010000, 28),
+            (0b11111111_11111111_11111111_10100000, 28),
+            (0b11111111_11111111_11111111_10110000, 28),
+            (0b01010000_00000000_00000000_00000000, 6),
+            (0b11111110_00000000_00000000_00000000, 10),
+            (0b11111110_01000000_00000000_00000000, 10),
+            (0b11111111_10100000_00000000_00000000, 12),
+            (0b11111111_11001000_00000000_00000000, 13),
+            (0b01010100_00000000_00000000_00000000, 6),
+            (0b11111000_00000000_00000000_00000000, 8),
+            (0b11111111_01000000_00000000_00000000, 11),
+            (0b11111110_10000000_00000000_00000000, 10),
+            (0b11111110_11000000_00000000_00000000, 10),
+            (0b11111001_00000000_00000000_00000000, 8),
+            (0b11111111_01100000_00000000_00000000, 11),
+            (0b11111010_00000000_00000000_00000000, 8),
+            (0b01011000_00000000_00000000_00000000, 6),
+            (0b01011100_00000000_00000000_00000000, 6),
+            (0b01100000_00000000_00000000_00000000, 6),
+            (0b00000000_00000000_00000000_00000000, 5),
+            (0b00001000_00000000_00000000_00000000, 5),
+            (0b00010000_00000000_00000000_00000000, 5),
+            (0b01100100_00000000_00000000_00000000, 6),
+            (0b01101000_00000000_00000000_00000000, 6),
+            (0b01101100_00000000_00000000_00000000, 6),
+            (0b01110000_00000000_00000000_00000000, 6),
+            (0b01110100_00000000_00000000_00000000, 6),
+            (0b01111000_00000000_00000000_00000000, 6),
+            (0b01111100_00000000_00000000_00000000, 6),
+            (0b10111000_00000000_00000000_00000000, 7),
+            (0b11111011_00000000_00000000_00000000, 8),
+            (0b11111111_11111000_00000000_00000000, 15),
+            (0b10000000_00000000_00000000_00000000, 6),
+            (0b11111111_10110000_00000000_00000000, 12),
+            (0b11111111_00000000_00000000_00000000, 10),
+            (0b11111111_11010000_00000000_00000000, 13),
+            (0b10000100_00000000_00000000_00000000, 6),
+            (0b10111010_00000000_00000000_00000000, 7),
+            (0b10111100_00000000_00000000_00000000, 7),
+            (0b10111110_00000000_00000000_00000000, 7),
+            (0b11000000_00000000_00000000_00000000, 7),
+            (0b11000010_00000000_00000000_00000000, 7),
+            (0b11000100_00000000_00000000_00000000, 7),
+            (0b11000110_00000000_00000000_00000000, 7),
+            (0b11001000_00000000_00000000_00000000, 7),
+            (0b11001010_00000000_00000000_00000000, 7),
+            (0b11001100_00000000_00000000_00000000, 7),
+            (0b11001110_00000000_00000000_00000000, 7),
+            (0b11010000_00000000_00000000_00000000, 7),
+            (0b11010010_00000000_00000000_00000000, 7),
+            (0b11010100_00000000_00000000_00000000, 7),
+            (0b11010110_00000000_00000000_00000000, 7),
+            (0b11011000_00000000_00000000_00000000, 7),
+            (0b11011010_00000000_00000000_00000000, 7),
+            (0b11011100_00000000_00000000_00000000, 7),
+            (0b11011110_00000000_00000000_00000000, 7),
+            (0b11100000_00000000_00000000_00000000, 7),
+            (0b11100010_00000000_00000000_00000000, 7),
+            (0b11100100_00000000_00000000_00000000, 7),
+            (0b11111100_00000000_00000000_00000000, 8),
+            (0b11100110_00000000_00000000_00000000, 7),
+            (0b11111101_00000000_00000000_00000000, 8),
+            (0b11111111_11011000_00000000_00000000, 13),
+            (0b11111111_11111110_00000000_00000000, 19),
+            (0b11111111_11100000_00000000_00000000, 13),
+            (0b11111111_11110000_00000000_00000000, 14),
+            (0b10001000_00000000_00000000_00000000, 6),
+            (0b11111111_11111010_00000000_00000000, 15),
+            (0b00011000_00000000_00000000_00000000, 5),
+            (0b10001100_00000000_00000000_00000000, 6),
+            (0b00100000_00000000_00000000_00000000, 5),
+            (0b10010000_00000000_00000000_00000000, 6),
+            (0b00101000_00000000_00000000_00000000, 5),
+            (0b10010100_00000000_00000000_00000000, 6),
+            (0b10011000_00000000_00000000_00000000, 6),
+            (0b10011100_00000000_00000000_00000000, 6),
+            (0b00110000_00000000_00000000_00000000, 5),
+            (0b11101000_00000000_00000000_00000000, 7),
+            (0b11101010_00000000_00000000_00000000, 7),
+            (0b10100000_00000000_00000000_00000000, 6),
+            (0b10100100_00000000_00000000_00000000, 6),
+            (0b10101000_00000000_00000000_00000000, 6),
+            (0b00111000_00000000_00000000_00000000, 5),
+            (0b10101100_00000000_00000000_00000000, 6),
+            (0b11101100_00000000_00000000_00000000, 7),
+            (0b10110000_00000000_00000000_00000000, 6),
+            (0b01000000_00000000_00000000_00000000, 5),
+            (0b01001000_00000000_00000000_00000000, 5),
+            (0b10110100_00000000_00000000_00000000, 6),
+            (0b11101110_00000000_00000000_00000000, 7),
+            (0b11110000_00000000_00000000_00000000, 7),
+            (0b11110010_00000000_00000000_00000000, 7),
+            (0b11110100_00000000_00000000_00000000, 7),
+            (0b11110110_00000000_00000000_00000000, 7),
+            (0b11111111_11111100_00000000_00000000, 15),
+            (0b11111111_10000000_00000000_00000000, 11),
+            (0b11111111_11110100_00000000_00000000, 14),
+            (0b11111111_11101000_00000000_00000000, 13),
+            (0b11111111_11111111_11111111_11000000, 28),
+            (0b11111111_11111110_01100000_00000000, 20),
+            (0b11111111_11111111_01001000_00000000, 22),
+            (0b11111111_11111110_01110000_00000000, 20),
+            (0b11111111_11111110_10000000_00000000, 20),
+            (0b11111111_11111111_01001100_00000000, 22),
+            (0b11111111_11111111_01010000_00000000, 22),
+            (0b11111111_11111111_01010100_00000000, 22),
+            (0b11111111_11111111_10110010_00000000, 23),
+            (0b11111111_11111111_01011000_00000000, 22),
+            (0b11111111_11111111_10110100_00000000, 23),
+            (0b11111111_11111111_10110110_00000000, 23),
+            (0b11111111_11111111_10111000_00000000, 23),
+            (0b11111111_11111111_10111010_00000000, 23),
+            (0b11111111_11111111_10111100_00000000, 23),
+            (0b11111111_11111111_11101011_00000000, 24),
+            (0b11111111_11111111_10111110_00000000, 23),
+            (0b11111111_11111111_11101100_00000000, 24),
+            (0b11111111_11111111_11101101_00000000, 24),
+            (0b11111111_11111111_01011100_00000000, 22),
+            (0b11111111_11111111_11000000_00000000, 23),
+            (0b11111111_11111111_11101110_00000000, 24),
+            (0b11111111_11111111_11000010_00000000, 23),
+            (0b11111111_11111111_11000100_00000000, 23),
+            (0b11111111_11111111_11000110_00000000, 23),
+            (0b11111111_11111111_11001000_00000000, 23),
+            (0b11111111_11111110_11100000_00000000, 21),
+            (0b11111111_11111111_01100000_00000000, 22),
+            (0b11111111_11111111_11001010_00000000, 23),
+            (0b11111111_11111111_01100100_00000000, 22),
+            (0b11111111_11111111_11001100_00000000, 23),
+            (0b11111111_11111111_11001110_00000000, 23),
+            (0b11111111_11111111_11101111_00000000, 24),
+            (0b11111111_11111111_01101000_00000000, 22),
+            (0b11111111_11111110_11101000_00000000, 21),
+            (0b11111111_11111110_10010000_00000000, 20),
+            (0b11111111_11111111_01101100_00000000, 22),
+            (0b11111111_11111111_01110000_00000000, 22),
+            (0b11111111_11111111_11010000_00000000, 23),
+            (0b11111111_11111111_11010010_00000000, 23),
+            (0b11111111_11111110_11110000_00000000, 21),
+            (0b11111111_11111111_11010100_00000000, 23),
+            (0b11111111_11111111_01110100_00000000, 22),
+            (0b11111111_11111111_01111000_00000000, 22),
+            (0b11111111_11111111_11110000_00000000, 24),
+            (0b11111111_11111110_11111000_00000000, 21),
+            (0b11111111_11111111_01111100_00000000, 22),
+            (0b11111111_11111111_11010110_00000000, 23),
+            (0b11111111_11111111_11011000_00000000, 23),
+            (0b11111111_11111111_00000000_00000000, 21),
+            (0b11111111_11111111_00001000_00000000, 21),
+            (0b11111111_11111111_10000000_00000000, 22),
+            (0b11111111_11111111_00010000_00000000, 21),
+            (0b11111111_11111111_11011010_00000000, 23),
+            (0b11111111_11111111_10000100_00000000, 22),
+            (0b11111111_11111111_11011100_00000000, 23),
+            (0b11111111_11111111_11011110_00000000, 23),
+            (0b11111111_11111110_10100000_00000000, 20),
+            (0b11111111_11111111_10001000_00000000, 22),
+            (0b11111111_11111111_10001100_00000000, 22),
+            (0b11111111_11111111_10010000_00000000, 22),
+            (0b11111111_11111111_11100000_00000000, 23),
+            (0b11111111_11111111_10010100_00000000, 22),
+            (0b11111111_11111111_10011000_00000000, 22),
+            (0b11111111_11111111_11100010_00000000, 23),
+            (0b11111111_11111111_11111000_00000000, 26),
+            (0b11111111_11111111_11111000_01000000, 26),
+            (0b11111111_11111110_10110000_00000000, 20),
+            (0b11111111_11111110_00100000_00000000, 19),
+            (0b11111111_11111111_10011100_00000000, 22),
+            (0b11111111_11111111_11100100_00000000, 23),
+            (0b11111111_11111111_10100000_00000000, 22),
+            (0b11111111_11111111_11110110_00000000, 25),
+            (0b11111111_11111111_11111000_10000000, 26),
+            (0b11111111_11111111_11111000_11000000, 26),
+            (0b11111111_11111111_11111001_00000000, 26),
+            (0b11111111_11111111_11111011_11000000, 27),
+            (0b11111111_11111111_11111011_11100000, 27),
+            (0b11111111_11111111_11111001_01000000, 26),
+            (0b11111111_11111111_11110001_00000000, 24),
+            (0b11111111_11111111_11110110_10000000, 25),
+            (0b11111111_11111110_01000000_00000000, 19),
+            (0b11111111_11111111_00011000_00000000, 21),
+            (0b11111111_11111111_11111001_10000000, 26),
+            (0b11111111_11111111_11111100_00000000, 27),
+            (0b11111111_11111111_11111100_00100000, 27),
+            (0b11111111_11111111_11111001_11000000, 26),
+            (0b11111111_11111111_11111100_01000000, 27),
+            (0b11111111_11111111_11110010_00000000, 24),
+            (0b11111111_11111111_00100000_00000000, 21),
+            (0b11111111_11111111_00101000_00000000, 21),
+            (0b11111111_11111111_11111010_00000000, 26),
+            (0b11111111_11111111_11111010_01000000, 26),
+            (0b11111111_11111111_11111111_11010000, 28),
+            (0b11111111_11111111_11111100_01100000, 27),
+            (0b11111111_11111111_11111100_10000000, 27),
+            (0b11111111_11111111_11111100_10100000, 27),
+            (0b11111111_11111110_11000000_00000000, 20),
+            (0b11111111_11111111_11110011_00000000, 24),
+            (0b11111111_11111110_11010000_00000000, 20),
+            (0b11111111_11111111_00110000_00000000, 21),
+            (0b11111111_11111111_10100100_00000000, 22),
+            (0b11111111_11111111_00111000_00000000, 21),
+            (0b11111111_11111111_01000000_00000000, 21),
+            (0b11111111_11111111_11100110_00000000, 23),
+            (0b11111111_11111111_10101000_00000000, 22),
+            (0b11111111_11111111_10101100_00000000, 22),
+            (0b11111111_11111111_11110111_00000000, 25),
+            (0b11111111_11111111_11110111_10000000, 25),
+            (0b11111111_11111111_11110100_00000000, 24),
+            (0b11111111_11111111_11110101_00000000, 24),
+            (0b11111111_11111111_11111010_10000000, 26),
+            (0b11111111_11111111_11101000_00000000, 23),
+            (0b11111111_11111111_11111010_11000000, 26),
+            (0b11111111_11111111_11111100_11000000, 27),
+            (0b11111111_11111111_11111011_00000000, 26),
+            (0b11111111_11111111_11111011_01000000, 26),
+            (0b11111111_11111111_11111100_11100000, 27),
+            (0b11111111_11111111_11111101_00000000, 27),
+            (0b11111111_11111111_11111101_00100000, 27),
+            (0b11111111_11111111_11111101_01000000, 27),
+            (0b11111111_11111111_11111101_01100000, 27),
+            (0b11111111_11111111_11111111_11100000, 28),
+            (0b11111111_11111111_11111101_10000000, 27),
+            (0b11111111_11111111_11111101_10100000, 27),
+            (0b11111111_11111111_11111101_11000000, 27),
+            (0b11111111_11111111_11111101_11100000, 27),
+            (0b11111111_11111111_11111110_00000000, 27),
+            (0b11111111_11111111_11111011_10000000, 26)
+        };
+
+        // Encoded values are 30 bits at most, so are stored in the table in a uint.
+        // Convert to ulong here and put the encoded value in the most significant bits.
+        // This makes the encoding logic below simpler.
+        private static (ulong code, int bitLength) GetEncodedValue(byte b)
+        {
+            (uint code, int bitLength) = s_encodingTable[b];
+            return (((ulong)code) << 32, bitLength);
+        }
+
+        private static int Encode(byte[] source, byte[] destination)
+        {
+            ulong currentBits = 0;  // We can have 7 bits of rollover plus 30 bits for the next encoded value, so use a ulong
+            int currentBitCount = 0;
+            int dstOffset = 0;
+
+            for (int i = 0; i < source.Length; i++)
+            {
+                (ulong code, int bitLength) = GetEncodedValue(source[i]);
+
+                currentBits |= code >> currentBitCount;
+                currentBitCount += bitLength;
+
+                while (currentBitCount >= 8)
+                {
+                    destination[dstOffset++] = (byte)(currentBits >> 56);
+                    currentBits = currentBits << 8;
+                    currentBitCount -= 8;
+                }
+            }
+
+            // Fill any trailing bits with ones, per RFC
+            if (currentBitCount > 0)
+            {
+                currentBits |= 0xFFFFFFFFFFFFFFFF >> currentBitCount;
+                destination[dstOffset++] = (byte)(currentBits >> 56);
+            }
+
+            return dstOffset;
+        }
+
+        [Theory]
+        [MemberData(nameof(TestData))]
+        public void HuffmanDecoding_ValidEncoding_Succeeds(byte[] input)
+        {
+            // Worst case encoding is 30 bits per input byte, so make the encoded buffer 4 times as big
+            byte[] encoded = new byte[input.Length * 4];
+            int encodedByteCount = Encode(input, encoded);
+
+            // Worst case decoding is an output byte per 5 input bits, so make the encoded buffer 2 times as big
+            byte[] decoded = new byte[encoded.Length * 2];
+
+            int decodedByteCount = Decode(encoded, encodedByteCount, decoded);
+
+            Assert.Equal(input.Length, decodedByteCount);
+            Assert.Equal(input, decoded.Take(decodedByteCount));
+        }
+
+        private static readonly Type s_huffmanDecodingExceptionType = typeof(HttpClient).Assembly.GetType("System.Net.Http.HPack.HuffmanDecodingException");
+
+        [Theory]
+        [MemberData(nameof(InvalidEncodingData))]
+        public void HuffmanDecoding_InvalidEncoding_Throws(byte[] encoded)
+        {
+            // Worst case decoding is an output byte per 5 input bits, so make the encoded buffer 2 times as big
+            byte[] decoded = new byte[encoded.Length * 2];
+
+            Assert.Throws(s_huffmanDecodingExceptionType, () => Decode(encoded, encoded.Length, decoded));
+        }
+
+        // This input sequence will encode to 17 bits, thus offsetting the next character to encode
+        // by exactly one bit. We use this below to generate a prefix that encodes all of the possible starting 
+        // bit offsets for a character, from 0 to 7.
+        private static readonly byte[] s_offsetByOneBit = new byte[] { (byte)'c', (byte)'l', (byte)'r' };
+
+        public static IEnumerable<object[]> TestData()
+        {
+            // Single byte data
+            for (int i = 0; i < 256; i++)
+            {
+                yield return new object[] { new byte[] { (byte)i } };
+            }
+
+            // Ensure that decoding every possible value leaves the decoder in a corect state so that 
+            // a subsequent value can be decoded (here, 'a')
+            for (int i = 0; i < 256; i++)
+            {
+                yield return new object[] { new byte[] { (byte)i, (byte)'a' } };
+            }
+
+            // Ensure that every possible bit starting position for every value is encoded properly
+            // s_offsetByOneBit encodes to exactly 17 bits, leaving 1 bit for the next byte
+            // So by repeating this sequence, we can generate any starting bit position we want.
+            byte[] currentPrefix = new byte[0];
+            for (int prefixBits = 1; prefixBits <= 8; prefixBits++)
+            {
+                currentPrefix = currentPrefix.Concat(s_offsetByOneBit).ToArray();
+
+                // Make sure we're actually getting the correct number of prefix bits
+                int encodedBits = currentPrefix.Select(b => s_encodingTable[b].bitLength).Sum();
+                Assert.Equal(prefixBits % 8, encodedBits % 8);
+
+                for (int i = 0; i < 256; i++)
+                {
+                    yield return new object[] { currentPrefix.Concat(new byte[] { (byte)i }.Concat(currentPrefix)).ToArray() };
+                }
+            }
+
+            // Finally, one really big chunk of randomly generated data.
+            byte[] data = new byte[1024 * 1024];
+            new Random(42).NextBytes(data);
+            yield return new object[] { data };
+        }
+
+        public static IEnumerable<object[]> InvalidEncodingData()
+        {
+            // For encodings greater than 8 bits, truncate one or more bytes to generate an invalid encoding
+            byte[] source = new byte[1];
+            byte[] destination = new byte[10];
+            for (int i = 0; i < 256; i++)
+            {
+                source[0] = (byte)i;
+                int encodedByteCount = Encode(source, destination);
+                if (encodedByteCount > 1)
+                {
+                    yield return new object[] { destination.Take(encodedByteCount - 1).ToArray() };
+                    if (encodedByteCount > 2)
+                    {
+                        yield return new object[] { destination.Take(encodedByteCount - 2).ToArray() };
+                        if (encodedByteCount > 3)
+                        {
+                            yield return new object[] { destination.Take(encodedByteCount - 3).ToArray() };
+                        }
+                    }
+                }
+            }
+
+            // Pad encodings with invalid trailing one bits. This is disallowed.
+            byte[] pad1 = new byte[] { 0xFF };
+            byte[] pad2 = new byte[] { 0xFF, 0xFF, };
+            byte[] pad3 = new byte[] { 0xFF, 0xFF, 0xFF };
+            byte[] pad4 = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF };
+
+            for (int i = 0; i < 256; i++)
+            {
+                source[0] = (byte)i;
+                int encodedByteCount = Encode(source, destination);
+                yield return new object[] { destination.Take(encodedByteCount).Concat(pad1).ToArray() };
+                yield return new object[] { destination.Take(encodedByteCount).Concat(pad2).ToArray() };
+                yield return new object[] { destination.Take(encodedByteCount).Concat(pad3).ToArray() };
+                yield return new object[] { destination.Take(encodedByteCount).Concat(pad4).ToArray() };
+            }
+        }
+    }
+}
index e04302e..e5437ec 100644 (file)
     <Compile Include="HttpClientHandlerTest.Decompression.cs" />
     <Compile Include="HttpClientTest.netcoreapp.cs" />
     <Compile Include="HttpMethodTest.netcoreapp.cs" />
+    <Compile Include="HuffmanDecodingTests.cs" />
     <Compile Include="ReadOnlyMemoryContentTest.cs" />
     <Compile Include="SocketsHttpHandlerTest.cs" />
     <Compile Include="SocketsHttpHandlerTest.Http2.cs" />