diff options
author | Eric Gribkoff <ericgribkoff@google.com> | 2018-03-27 11:12:44 -0700 |
---|---|---|
committer | Eric Gribkoff <ericgribkoff@google.com> | 2018-03-28 14:53:19 -0700 |
commit | ecc36ac96d40a8d329eacfe9b20b89b55734a0ba (patch) | |
tree | 6b2b83cddc88c4fe4c7153866332480feae1ece4 /okhttp | |
parent | 1efb69539930a49107d70318b0defc540f4632fa (diff) | |
download | grpc-grpc-java-ecc36ac96d40a8d329eacfe9b20b89b55734a0ba.tar.gz |
okhttp: include tests in third_party/okhttp
Diffstat (limited to 'okhttp')
-rw-r--r-- | okhttp/BUILD.bazel | 2 | ||||
-rw-r--r-- | okhttp/build.gradle | 7 | ||||
-rw-r--r-- | okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/CipherSuite.java (renamed from okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/CipherSuite.java) | 0 | ||||
-rw-r--r-- | okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/ConnectionSpec.java (renamed from okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/ConnectionSpec.java) | 0 | ||||
-rw-r--r-- | okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/DistinguishedNameParser.java (renamed from okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/DistinguishedNameParser.java) | 0 | ||||
-rw-r--r-- | okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/OkHostnameVerifier.java (renamed from okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/OkHostnameVerifier.java) | 0 | ||||
-rw-r--r-- | okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/OptionalMethod.java (renamed from okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/OptionalMethod.java) | 0 | ||||
-rw-r--r-- | okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Platform.java (renamed from okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/Platform.java) | 0 | ||||
-rw-r--r-- | okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Protocol.java (renamed from okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/Protocol.java) | 0 | ||||
-rw-r--r-- | okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/TlsVersion.java (renamed from okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/TlsVersion.java) | 0 | ||||
-rw-r--r-- | okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Util.java (renamed from okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/Util.java) | 0 | ||||
-rw-r--r-- | okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/ErrorCode.java (renamed from okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/ErrorCode.java) | 0 | ||||
-rw-r--r-- | okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameReader.java (renamed from okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/FrameReader.java) | 0 | ||||
-rw-r--r-- | okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameWriter.java (renamed from okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/FrameWriter.java) | 0 | ||||
-rw-r--r-- | okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Header.java (renamed from okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/Header.java) | 0 | ||||
-rw-r--r-- | okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/HeadersMode.java (renamed from okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/HeadersMode.java) | 0 | ||||
-rw-r--r-- | okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Hpack.java (renamed from okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/Hpack.java) | 0 | ||||
-rw-r--r-- | okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Http2.java (renamed from okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/Http2.java) | 0 | ||||
-rw-r--r-- | okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Huffman.java (renamed from okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/Huffman.java) | 0 | ||||
-rw-r--r-- | okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Settings.java (renamed from okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/Settings.java) | 0 | ||||
-rw-r--r-- | okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Variant.java (renamed from okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/Variant.java) | 0 | ||||
-rw-r--r-- | okhttp/third_party/okhttp/test/java/io/grpc/okhttp/internal/framed/HpackTest.java | 738 |
22 files changed, 745 insertions, 2 deletions
diff --git a/okhttp/BUILD.bazel b/okhttp/BUILD.bazel index 75b672101..62a7afffc 100644 --- a/okhttp/BUILD.bazel +++ b/okhttp/BUILD.bazel @@ -1,7 +1,7 @@ java_library( name = "okhttp", srcs = glob([ - "third_party/okhttp/java/**/*.java", + "third_party/okhttp/main/java/**/*.java", "src/main/java/**/*.java", ]), resources = glob([ diff --git a/okhttp/build.gradle b/okhttp/build.gradle index 8d61f7332..9328a74ce 100644 --- a/okhttp/build.gradle +++ b/okhttp/build.gradle @@ -14,7 +14,12 @@ dependencies { project.sourceSets { main { java { - srcDir "${projectDir}/third_party/okhttp/java" + srcDir "${projectDir}/third_party/okhttp/main/java" + } + } + test { + java { + srcDir "${projectDir}/third_party/okhttp/test/java" } } } diff --git a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/CipherSuite.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/CipherSuite.java index bc2a9b7f0..bc2a9b7f0 100644 --- a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/CipherSuite.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/CipherSuite.java diff --git a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/ConnectionSpec.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/ConnectionSpec.java index 457e9c301..457e9c301 100644 --- a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/ConnectionSpec.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/ConnectionSpec.java diff --git a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/DistinguishedNameParser.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/DistinguishedNameParser.java index 166b09794..166b09794 100644 --- a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/DistinguishedNameParser.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/DistinguishedNameParser.java diff --git a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/OkHostnameVerifier.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/OkHostnameVerifier.java index b7b1d5367..b7b1d5367 100644 --- a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/OkHostnameVerifier.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/OkHostnameVerifier.java diff --git a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/OptionalMethod.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/OptionalMethod.java index a93c8384f..a93c8384f 100644 --- a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/OptionalMethod.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/OptionalMethod.java diff --git a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/Platform.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Platform.java index d6f88ba0e..d6f88ba0e 100644 --- a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/Platform.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Platform.java diff --git a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/Protocol.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Protocol.java index 7b5a3ae76..7b5a3ae76 100644 --- a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/Protocol.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Protocol.java diff --git a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/TlsVersion.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/TlsVersion.java index 548f4acbc..548f4acbc 100644 --- a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/TlsVersion.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/TlsVersion.java diff --git a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/Util.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Util.java index 7501c441d..7501c441d 100644 --- a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/Util.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Util.java diff --git a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/ErrorCode.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/ErrorCode.java index 38a545930..38a545930 100644 --- a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/ErrorCode.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/ErrorCode.java diff --git a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/FrameReader.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameReader.java index 20c50f7cc..20c50f7cc 100644 --- a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/FrameReader.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameReader.java diff --git a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/FrameWriter.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameWriter.java index 333e06c86..333e06c86 100644 --- a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/FrameWriter.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameWriter.java diff --git a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/Header.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Header.java index 00081d489..00081d489 100644 --- a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/Header.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Header.java diff --git a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/HeadersMode.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/HeadersMode.java index 3826252f4..3826252f4 100644 --- a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/HeadersMode.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/HeadersMode.java diff --git a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/Hpack.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Hpack.java index 5db4f0996..5db4f0996 100644 --- a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/Hpack.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Hpack.java diff --git a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/Http2.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Http2.java index 20eed3c3b..20eed3c3b 100644 --- a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/Http2.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Http2.java diff --git a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/Huffman.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Huffman.java index 5eb4a88c2..5eb4a88c2 100644 --- a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/Huffman.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Huffman.java diff --git a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/Settings.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Settings.java index 0d0ecce99..0d0ecce99 100644 --- a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/Settings.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Settings.java diff --git a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/Variant.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Variant.java index 2b707081b..2b707081b 100644 --- a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/framed/Variant.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Variant.java diff --git a/okhttp/third_party/okhttp/test/java/io/grpc/okhttp/internal/framed/HpackTest.java b/okhttp/third_party/okhttp/test/java/io/grpc/okhttp/internal/framed/HpackTest.java new file mode 100644 index 000000000..e512582e9 --- /dev/null +++ b/okhttp/third_party/okhttp/test/java/io/grpc/okhttp/internal/framed/HpackTest.java @@ -0,0 +1,738 @@ +/* + * Copyright (C) 2013 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.okhttp.internal.framed; + +import java.io.IOException; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; +import okio.Buffer; +import okio.ByteString; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static okio.ByteString.decodeHex; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +@RunWith(JUnit4.class) +public class HpackTest { + + private static List<Header> headerEntries(String... elements) { + List<Header> result = new ArrayList<Header>(elements.length / 2); + for (int i = 0; i < elements.length; i += 2) { + result.add(new Header(elements[i], elements[i + 1])); + } + return result; + } + + private final Buffer bytesIn = new Buffer(); + private Hpack.Reader hpackReader; + private Buffer bytesOut = new Buffer(); + private Hpack.Writer hpackWriter; + + @Before public void reset() { + hpackReader = newReader(bytesIn); + hpackWriter = new Hpack.Writer(bytesOut); + } + + /** + * Variable-length quantity special cases strings which are longer than 127 + * bytes. Values such as cookies can be 4KiB, and should be possible to send. + * + * <p> http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-5.2 + */ + @Test public void largeHeaderValue() throws IOException { + char[] value = new char[4096]; + Arrays.fill(value, '!'); + List<Header> headerBlock = headerEntries("cookie", new String(value)); + + hpackWriter.writeHeaders(headerBlock); + bytesIn.writeAll(bytesOut); + hpackReader.readHeaders(); + + assertEquals(0, hpackReader.dynamicTableHeaderCount); + + assertEquals(headerBlock, hpackReader.getAndResetHeaderList()); + } + + /** + * HPACK has a max header table size, which can be smaller than the max header message. + * Ensure the larger header content is not lost. + */ + @Test public void tooLargeToHPackIsStillEmitted() throws IOException { + bytesIn.writeByte(0x00); // Literal indexed + bytesIn.writeByte(0x0a); // Literal name (len = 10) + bytesIn.writeUtf8("custom-key"); + + bytesIn.writeByte(0x0d); // Literal value (len = 13) + bytesIn.writeUtf8("custom-header"); + + hpackReader.headerTableSizeSetting(1); + hpackReader.readHeaders(); + + assertEquals(0, hpackReader.dynamicTableHeaderCount); + + assertEquals(headerEntries("custom-key", "custom-header"), hpackReader.getAndResetHeaderList()); + } + + /** Oldest entries are evicted to support newer ones. */ + @Test public void testEviction() throws IOException { + bytesIn.writeByte(0x40); // Literal indexed + bytesIn.writeByte(0x0a); // Literal name (len = 10) + bytesIn.writeUtf8("custom-foo"); + + bytesIn.writeByte(0x0d); // Literal value (len = 13) + bytesIn.writeUtf8("custom-header"); + + bytesIn.writeByte(0x40); // Literal indexed + bytesIn.writeByte(0x0a); // Literal name (len = 10) + bytesIn.writeUtf8("custom-bar"); + + bytesIn.writeByte(0x0d); // Literal value (len = 13) + bytesIn.writeUtf8("custom-header"); + + bytesIn.writeByte(0x40); // Literal indexed + bytesIn.writeByte(0x0a); // Literal name (len = 10) + bytesIn.writeUtf8("custom-baz"); + + bytesIn.writeByte(0x0d); // Literal value (len = 13) + bytesIn.writeUtf8("custom-header"); + + // Set to only support 110 bytes (enough for 2 headers). + hpackReader.headerTableSizeSetting(110); + hpackReader.readHeaders(); + + assertEquals(2, hpackReader.dynamicTableHeaderCount); + + Header entry = hpackReader.dynamicTable[headerTableLength() - 1]; + checkEntry(entry, "custom-bar", "custom-header", 55); + + entry = hpackReader.dynamicTable[headerTableLength() - 2]; + checkEntry(entry, "custom-baz", "custom-header", 55); + + // Once a header field is decoded and added to the reconstructed header + // list, it cannot be removed from it. Hence, foo is here. + assertEquals( + headerEntries( + "custom-foo", "custom-header", + "custom-bar", "custom-header", + "custom-baz", "custom-header"), + hpackReader.getAndResetHeaderList()); + + // Simulate receiving a small settings frame, that implies eviction. + hpackReader.headerTableSizeSetting(55); + assertEquals(1, hpackReader.dynamicTableHeaderCount); + } + + /** Header table backing array is initially 8 long, let's ensure it grows. */ + @Test public void dynamicallyGrowsBeyond64Entries() throws IOException { + for (int i = 0; i < 256; i++) { + bytesIn.writeByte(0x40); // Literal indexed + bytesIn.writeByte(0x0a); // Literal name (len = 10) + bytesIn.writeUtf8("custom-foo"); + + bytesIn.writeByte(0x0d); // Literal value (len = 13) + bytesIn.writeUtf8("custom-header"); + } + + hpackReader.headerTableSizeSetting(16384); // Lots of headers need more room! + hpackReader.readHeaders(); + + assertEquals(256, hpackReader.dynamicTableHeaderCount); + } + + @Test public void huffmanDecodingSupported() throws IOException { + bytesIn.writeByte(0x44); // == Literal indexed == + // Indexed name (idx = 4) -> :path + bytesIn.writeByte(0x8c); // Literal value Huffman encoded 12 bytes + // decodes to www.example.com which is length 15 + bytesIn.write(decodeHex("f1e3c2e5f23a6ba0ab90f4ff")); + + hpackReader.readHeaders(); + + assertEquals(1, hpackReader.dynamicTableHeaderCount); + assertEquals(52, hpackReader.dynamicTableByteCount); + + Header entry = hpackReader.dynamicTable[headerTableLength() - 1]; + checkEntry(entry, ":path", "www.example.com", 52); + } + + /** + * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C.2.1 + */ + @Test public void readLiteralHeaderFieldWithIndexing() throws IOException { + bytesIn.writeByte(0x40); // Literal indexed + bytesIn.writeByte(0x0a); // Literal name (len = 10) + bytesIn.writeUtf8("custom-key"); + + bytesIn.writeByte(0x0d); // Literal value (len = 13) + bytesIn.writeUtf8("custom-header"); + + hpackReader.readHeaders(); + + assertEquals(1, hpackReader.dynamicTableHeaderCount); + assertEquals(55, hpackReader.dynamicTableByteCount); + + Header entry = hpackReader.dynamicTable[headerTableLength() - 1]; + checkEntry(entry, "custom-key", "custom-header", 55); + + assertEquals(headerEntries("custom-key", "custom-header"), hpackReader.getAndResetHeaderList()); + } + + /** + * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C.2.2 + */ + @Test public void literalHeaderFieldWithoutIndexingIndexedName() throws IOException { + List<Header> headerBlock = headerEntries(":path", "/sample/path"); + + bytesIn.writeByte(0x04); // == Literal not indexed == + // Indexed name (idx = 4) -> :path + bytesIn.writeByte(0x0c); // Literal value (len = 12) + bytesIn.writeUtf8("/sample/path"); + + hpackWriter.writeHeaders(headerBlock); + assertEquals(bytesIn, bytesOut); + + hpackReader.readHeaders(); + + assertEquals(0, hpackReader.dynamicTableHeaderCount); + + assertEquals(headerBlock, hpackReader.getAndResetHeaderList()); + } + + @Test public void literalHeaderFieldWithoutIndexingNewName() throws IOException { + List<Header> headerBlock = headerEntries("custom-key", "custom-header"); + + bytesIn.writeByte(0x00); // Not indexed + bytesIn.writeByte(0x0a); // Literal name (len = 10) + bytesIn.writeUtf8("custom-key"); + + bytesIn.writeByte(0x0d); // Literal value (len = 13) + bytesIn.writeUtf8("custom-header"); + + hpackWriter.writeHeaders(headerBlock); + assertEquals(bytesIn, bytesOut); + + hpackReader.readHeaders(); + + assertEquals(0, hpackReader.dynamicTableHeaderCount); + + assertEquals(headerBlock, hpackReader.getAndResetHeaderList()); + } + + @Test public void literalHeaderFieldNeverIndexedIndexedName() throws IOException { + bytesIn.writeByte(0x14); // == Literal never indexed == + // Indexed name (idx = 4) -> :path + bytesIn.writeByte(0x0c); // Literal value (len = 12) + bytesIn.writeUtf8("/sample/path"); + + hpackReader.readHeaders(); + + assertEquals(0, hpackReader.dynamicTableHeaderCount); + + assertEquals(headerEntries(":path", "/sample/path"), hpackReader.getAndResetHeaderList()); + } + + @Test public void literalHeaderFieldNeverIndexedNewName() throws IOException { + bytesIn.writeByte(0x10); // Never indexed + bytesIn.writeByte(0x0a); // Literal name (len = 10) + bytesIn.writeUtf8("custom-key"); + + bytesIn.writeByte(0x0d); // Literal value (len = 13) + bytesIn.writeUtf8("custom-header"); + + hpackReader.readHeaders(); + + assertEquals(0, hpackReader.dynamicTableHeaderCount); + + assertEquals(headerEntries("custom-key", "custom-header"), hpackReader.getAndResetHeaderList()); + } + + @Test public void staticHeaderIsNotCopiedIntoTheIndexedTable() throws IOException { + bytesIn.writeByte(0x82); // == Indexed - Add == + // idx = 2 -> :method: GET + + hpackReader.readHeaders(); + + assertEquals(0, hpackReader.dynamicTableHeaderCount); + assertEquals(0, hpackReader.dynamicTableByteCount); + + assertEquals(null, hpackReader.dynamicTable[headerTableLength() - 1]); + + assertEquals(headerEntries(":method", "GET"), hpackReader.getAndResetHeaderList()); + } + + // Example taken from twitter/hpack DecoderTest.testUnusedIndex + @Test public void readIndexedHeaderFieldIndex0() throws IOException { + bytesIn.writeByte(0x80); // == Indexed - Add idx = 0 + + try { + hpackReader.readHeaders(); + fail(); + } catch (IOException e) { + assertEquals("index == 0", e.getMessage()); + } + } + + // Example taken from twitter/hpack DecoderTest.testIllegalIndex + @Test public void readIndexedHeaderFieldTooLargeIndex() throws IOException { + bytesIn.writeShort(0xff00); // == Indexed - Add idx = 127 + + try { + hpackReader.readHeaders(); + fail(); + } catch (IOException e) { + assertEquals("Header index too large 127", e.getMessage()); + } + } + + // Example taken from twitter/hpack DecoderTest.testInsidiousIndex + @Test public void readIndexedHeaderFieldInsidiousIndex() throws IOException { + bytesIn.writeByte(0xff); // == Indexed - Add == + bytesIn.write(decodeHex("8080808008")); // idx = -2147483521 + + try { + hpackReader.readHeaders(); + fail(); + } catch (IOException e) { + assertEquals("Header index too large -2147483521", e.getMessage()); + } + } + + // Example taken from twitter/hpack DecoderTest.testHeaderTableSizeUpdate + @Test public void minMaxHeaderTableSize() throws IOException { + bytesIn.writeByte(0x20); + hpackReader.readHeaders(); + + assertEquals(0, hpackReader.maxDynamicTableByteCount()); + + bytesIn.writeByte(0x3f); // encode size 4096 + bytesIn.writeByte(0xe1); + bytesIn.writeByte(0x1f); + hpackReader.readHeaders(); + + assertEquals(4096, hpackReader.maxDynamicTableByteCount()); + } + + // Example taken from twitter/hpack DecoderTest.testIllegalHeaderTableSizeUpdate + @Test public void cannotSetTableSizeLargerThanSettingsValue() throws IOException { + bytesIn.writeByte(0x3f); // encode size 4097 + bytesIn.writeByte(0xe2); + bytesIn.writeByte(0x1f); + + try { + hpackReader.readHeaders(); + fail(); + } catch (IOException e) { + assertEquals("Invalid dynamic table size update 4097", e.getMessage()); + } + } + + // Example taken from twitter/hpack DecoderTest.testInsidiousMaxHeaderSize + @Test public void readHeaderTableStateChangeInsidiousMaxHeaderByteCount() throws IOException { + bytesIn.writeByte(0x3f); + bytesIn.write(decodeHex("e1ffffff07")); // count = -2147483648 + + try { + hpackReader.readHeaders(); + fail(); + } catch (IOException e) { + assertEquals("Invalid dynamic table size update -2147483648", e.getMessage()); + } + } + + /** + * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C.2.4 + */ + @Test public void readIndexedHeaderFieldFromStaticTableWithoutBuffering() throws IOException { + bytesIn.writeByte(0x82); // == Indexed - Add == + // idx = 2 -> :method: GET + + hpackReader.headerTableSizeSetting(0); // SETTINGS_HEADER_TABLE_SIZE == 0 + hpackReader.readHeaders(); + + // Not buffered in header table. + assertEquals(0, hpackReader.dynamicTableHeaderCount); + + assertEquals(headerEntries(":method", "GET"), hpackReader.getAndResetHeaderList()); + } + + /** + * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C.2 + */ + @Test public void readRequestExamplesWithoutHuffman() throws IOException { + firstRequestWithoutHuffman(); + hpackReader.readHeaders(); + checkReadFirstRequestWithoutHuffman(); + + secondRequestWithoutHuffman(); + hpackReader.readHeaders(); + checkReadSecondRequestWithoutHuffman(); + + thirdRequestWithoutHuffman(); + hpackReader.readHeaders(); + checkReadThirdRequestWithoutHuffman(); + } + + private void firstRequestWithoutHuffman() { + bytesIn.writeByte(0x82); // == Indexed - Add == + // idx = 2 -> :method: GET + bytesIn.writeByte(0x86); // == Indexed - Add == + // idx = 7 -> :scheme: http + bytesIn.writeByte(0x84); // == Indexed - Add == + // idx = 6 -> :path: / + bytesIn.writeByte(0x41); // == Literal indexed == + // Indexed name (idx = 4) -> :authority + bytesIn.writeByte(0x0f); // Literal value (len = 15) + bytesIn.writeUtf8("www.example.com"); + } + + private void checkReadFirstRequestWithoutHuffman() { + assertEquals(1, hpackReader.dynamicTableHeaderCount); + + // [ 1] (s = 57) :authority: www.example.com + Header entry = hpackReader.dynamicTable[headerTableLength() - 1]; + checkEntry(entry, ":authority", "www.example.com", 57); + + // Table size: 57 + assertEquals(57, hpackReader.dynamicTableByteCount); + + // Decoded header list: + assertEquals(headerEntries( + ":method", "GET", + ":scheme", "http", + ":path", "/", + ":authority", "www.example.com"), hpackReader.getAndResetHeaderList()); + } + + private void secondRequestWithoutHuffman() { + bytesIn.writeByte(0x82); // == Indexed - Add == + // idx = 2 -> :method: GET + bytesIn.writeByte(0x86); // == Indexed - Add == + // idx = 7 -> :scheme: http + bytesIn.writeByte(0x84); // == Indexed - Add == + // idx = 6 -> :path: / + bytesIn.writeByte(0xbe); // == Indexed - Add == + // Indexed name (idx = 62) -> :authority: www.example.com + bytesIn.writeByte(0x58); // == Literal indexed == + // Indexed name (idx = 24) -> cache-control + bytesIn.writeByte(0x08); // Literal value (len = 8) + bytesIn.writeUtf8("no-cache"); + } + + private void checkReadSecondRequestWithoutHuffman() { + assertEquals(2, hpackReader.dynamicTableHeaderCount); + + // [ 1] (s = 53) cache-control: no-cache + Header entry = hpackReader.dynamicTable[headerTableLength() - 2]; + checkEntry(entry, "cache-control", "no-cache", 53); + + // [ 2] (s = 57) :authority: www.example.com + entry = hpackReader.dynamicTable[headerTableLength() - 1]; + checkEntry(entry, ":authority", "www.example.com", 57); + + // Table size: 110 + assertEquals(110, hpackReader.dynamicTableByteCount); + + // Decoded header list: + assertEquals(headerEntries( + ":method", "GET", + ":scheme", "http", + ":path", "/", + ":authority", "www.example.com", + "cache-control", "no-cache"), hpackReader.getAndResetHeaderList()); + } + + private void thirdRequestWithoutHuffman() { + bytesIn.writeByte(0x82); // == Indexed - Add == + // idx = 2 -> :method: GET + bytesIn.writeByte(0x87); // == Indexed - Add == + // idx = 7 -> :scheme: http + bytesIn.writeByte(0x85); // == Indexed - Add == + // idx = 5 -> :path: /index.html + bytesIn.writeByte(0xbf); // == Indexed - Add == + // Indexed name (idx = 63) -> :authority: www.example.com + bytesIn.writeByte(0x40); // Literal indexed + bytesIn.writeByte(0x0a); // Literal name (len = 10) + bytesIn.writeUtf8("custom-key"); + bytesIn.writeByte(0x0c); // Literal value (len = 12) + bytesIn.writeUtf8("custom-value"); + } + + private void checkReadThirdRequestWithoutHuffman() { + assertEquals(3, hpackReader.dynamicTableHeaderCount); + + // [ 1] (s = 54) custom-key: custom-value + Header entry = hpackReader.dynamicTable[headerTableLength() - 3]; + checkEntry(entry, "custom-key", "custom-value", 54); + + // [ 2] (s = 53) cache-control: no-cache + entry = hpackReader.dynamicTable[headerTableLength() - 2]; + checkEntry(entry, "cache-control", "no-cache", 53); + + // [ 3] (s = 57) :authority: www.example.com + entry = hpackReader.dynamicTable[headerTableLength() - 1]; + checkEntry(entry, ":authority", "www.example.com", 57); + + // Table size: 164 + assertEquals(164, hpackReader.dynamicTableByteCount); + + // Decoded header list: + assertEquals(headerEntries( + ":method", "GET", + ":scheme", "https", + ":path", "/index.html", + ":authority", "www.example.com", + "custom-key", "custom-value"), hpackReader.getAndResetHeaderList()); + } + + /** + * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C.4 + */ + @Test public void readRequestExamplesWithHuffman() throws IOException { + firstRequestWithHuffman(); + hpackReader.readHeaders(); + checkReadFirstRequestWithHuffman(); + + secondRequestWithHuffman(); + hpackReader.readHeaders(); + checkReadSecondRequestWithHuffman(); + + thirdRequestWithHuffman(); + hpackReader.readHeaders(); + checkReadThirdRequestWithHuffman(); + } + + private void firstRequestWithHuffman() { + bytesIn.writeByte(0x82); // == Indexed - Add == + // idx = 2 -> :method: GET + bytesIn.writeByte(0x86); // == Indexed - Add == + // idx = 6 -> :scheme: http + bytesIn.writeByte(0x84); // == Indexed - Add == + // idx = 4 -> :path: / + bytesIn.writeByte(0x41); // == Literal indexed == + // Indexed name (idx = 1) -> :authority + bytesIn.writeByte(0x8c); // Literal value Huffman encoded 12 bytes + // decodes to www.example.com which is length 15 + bytesIn.write(decodeHex("f1e3c2e5f23a6ba0ab90f4ff")); + } + + private void checkReadFirstRequestWithHuffman() { + assertEquals(1, hpackReader.dynamicTableHeaderCount); + + // [ 1] (s = 57) :authority: www.example.com + Header entry = hpackReader.dynamicTable[headerTableLength() - 1]; + checkEntry(entry, ":authority", "www.example.com", 57); + + // Table size: 57 + assertEquals(57, hpackReader.dynamicTableByteCount); + + // Decoded header list: + assertEquals(headerEntries( + ":method", "GET", + ":scheme", "http", + ":path", "/", + ":authority", "www.example.com"), hpackReader.getAndResetHeaderList()); + } + + private void secondRequestWithHuffman() { + bytesIn.writeByte(0x82); // == Indexed - Add == + // idx = 2 -> :method: GET + bytesIn.writeByte(0x86); // == Indexed - Add == + // idx = 6 -> :scheme: http + bytesIn.writeByte(0x84); // == Indexed - Add == + // idx = 4 -> :path: / + bytesIn.writeByte(0xbe); // == Indexed - Add == + // idx = 62 -> :authority: www.example.com + bytesIn.writeByte(0x58); // == Literal indexed == + // Indexed name (idx = 24) -> cache-control + bytesIn.writeByte(0x86); // Literal value Huffman encoded 6 bytes + // decodes to no-cache which is length 8 + bytesIn.write(decodeHex("a8eb10649cbf")); + } + + private void checkReadSecondRequestWithHuffman() { + assertEquals(2, hpackReader.dynamicTableHeaderCount); + + // [ 1] (s = 53) cache-control: no-cache + Header entry = hpackReader.dynamicTable[headerTableLength() - 2]; + checkEntry(entry, "cache-control", "no-cache", 53); + + // [ 2] (s = 57) :authority: www.example.com + entry = hpackReader.dynamicTable[headerTableLength() - 1]; + checkEntry(entry, ":authority", "www.example.com", 57); + + // Table size: 110 + assertEquals(110, hpackReader.dynamicTableByteCount); + + // Decoded header list: + assertEquals(headerEntries( + ":method", "GET", + ":scheme", "http", + ":path", "/", + ":authority", "www.example.com", + "cache-control", "no-cache"), hpackReader.getAndResetHeaderList()); + } + + private void thirdRequestWithHuffman() { + bytesIn.writeByte(0x82); // == Indexed - Add == + // idx = 2 -> :method: GET + bytesIn.writeByte(0x87); // == Indexed - Add == + // idx = 7 -> :scheme: https + bytesIn.writeByte(0x85); // == Indexed - Add == + // idx = 5 -> :path: /index.html + bytesIn.writeByte(0xbf); // == Indexed - Add == + // idx = 63 -> :authority: www.example.com + bytesIn.writeByte(0x40); // Literal indexed + bytesIn.writeByte(0x88); // Literal name Huffman encoded 8 bytes + // decodes to custom-key which is length 10 + bytesIn.write(decodeHex("25a849e95ba97d7f")); + bytesIn.writeByte(0x89); // Literal value Huffman encoded 9 bytes + // decodes to custom-value which is length 12 + bytesIn.write(decodeHex("25a849e95bb8e8b4bf")); + } + + private void checkReadThirdRequestWithHuffman() { + assertEquals(3, hpackReader.dynamicTableHeaderCount); + + // [ 1] (s = 54) custom-key: custom-value + Header entry = hpackReader.dynamicTable[headerTableLength() - 3]; + checkEntry(entry, "custom-key", "custom-value", 54); + + // [ 2] (s = 53) cache-control: no-cache + entry = hpackReader.dynamicTable[headerTableLength() - 2]; + checkEntry(entry, "cache-control", "no-cache", 53); + + // [ 3] (s = 57) :authority: www.example.com + entry = hpackReader.dynamicTable[headerTableLength() - 1]; + checkEntry(entry, ":authority", "www.example.com", 57); + + // Table size: 164 + assertEquals(164, hpackReader.dynamicTableByteCount); + + // Decoded header list: + assertEquals(headerEntries( + ":method", "GET", + ":scheme", "https", + ":path", "/index.html", + ":authority", "www.example.com", + "custom-key", "custom-value"), hpackReader.getAndResetHeaderList()); + } + + @Test public void readSingleByteInt() throws IOException { + assertEquals(10, newReader(byteStream()).readInt(10, 31)); + assertEquals(10, newReader(byteStream()).readInt(0xe0 | 10, 31)); + } + + @Test public void readMultibyteInt() throws IOException { + assertEquals(1337, newReader(byteStream(154, 10)).readInt(31, 31)); + } + + @Test public void writeSingleByteInt() throws IOException { + hpackWriter.writeInt(10, 31, 0); + assertBytes(10); + hpackWriter.writeInt(10, 31, 0xe0); + assertBytes(0xe0 | 10); + } + + @Test public void writeMultibyteInt() throws IOException { + hpackWriter.writeInt(1337, 31, 0); + assertBytes(31, 154, 10); + hpackWriter.writeInt(1337, 31, 0xe0); + assertBytes(0xe0 | 31, 154, 10); + } + + @Test public void max31BitValue() throws IOException { + hpackWriter.writeInt(0x7fffffff, 31, 0); + assertBytes(31, 224, 255, 255, 255, 7); + assertEquals(0x7fffffff, + newReader(byteStream(224, 255, 255, 255, 7)).readInt(31, 31)); + } + + @Test public void prefixMask() throws IOException { + hpackWriter.writeInt(31, 31, 0); + assertBytes(31, 0); + assertEquals(31, newReader(byteStream(0)).readInt(31, 31)); + } + + @Test public void prefixMaskMinusOne() throws IOException { + hpackWriter.writeInt(30, 31, 0); + assertBytes(30); + assertEquals(31, newReader(byteStream(0)).readInt(31, 31)); + } + + @Test public void zero() throws IOException { + hpackWriter.writeInt(0, 31, 0); + assertBytes(0); + assertEquals(0, newReader(byteStream()).readInt(0, 31)); + } + + @Test public void lowercaseHeaderNameBeforeEmit() throws IOException { + hpackWriter.writeHeaders(Arrays.asList(new Header("FoO", "BaR"))); + assertBytes(0, 3, 'f', 'o', 'o', 3, 'B', 'a', 'R'); + } + + @Test public void mixedCaseHeaderNameIsMalformed() throws IOException { + try { + newReader(byteStream(0, 3, 'F', 'o', 'o', 3, 'B', 'a', 'R')).readHeaders(); + fail(); + } catch (IOException e) { + assertEquals("PROTOCOL_ERROR response malformed: mixed case name: Foo", e.getMessage()); + } + } + + @Test public void emptyHeaderName() throws IOException { + hpackWriter.writeByteString(ByteString.encodeUtf8("")); + assertBytes(0); + assertEquals(ByteString.EMPTY, newReader(byteStream(0)).readByteString()); + } + + private Hpack.Reader newReader(Buffer source) { + return new Hpack.Reader(4096, source); + } + + private Buffer byteStream(int... bytes) { + return new Buffer().write(intArrayToByteArray(bytes)); + } + + private void checkEntry(Header entry, String name, String value, int size) { + assertEquals(name, entry.name.utf8()); + assertEquals(value, entry.value.utf8()); + assertEquals(size, entry.hpackSize); + } + + private void assertBytes(int... bytes) throws IOException { + ByteString expected = intArrayToByteArray(bytes); + ByteString actual = bytesOut.readByteString(); + assertEquals(expected, actual); + } + + private ByteString intArrayToByteArray(int[] bytes) { + byte[] data = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + data[i] = (byte) bytes[i]; + } + return ByteString.of(data); + } + + private int headerTableLength() { + return hpackReader.dynamicTable.length; + } +} |