• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 Square, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.grpc.okhttp.internal.framed;
18 
19 import java.io.IOException;
20 import java.util.Arrays;
21 import java.util.ArrayList;
22 import java.util.List;
23 import okio.Buffer;
24 import okio.ByteString;
25 import org.junit.Before;
26 import org.junit.Test;
27 import org.junit.runner.RunWith;
28 import org.junit.runners.JUnit4;
29 
30 import static okio.ByteString.decodeHex;
31 import static org.junit.Assert.assertEquals;
32 import static org.junit.Assert.fail;
33 
34 @RunWith(JUnit4.class)
35 public class HpackTest {
36 
headerEntries(String... elements)37   private static List<Header> headerEntries(String... elements) {
38     List<Header> result = new ArrayList<>(elements.length / 2);
39     for (int i = 0; i < elements.length; i += 2) {
40       result.add(new Header(elements[i], elements[i + 1]));
41     }
42     return result;
43   }
44 
45   private final Buffer bytesIn = new Buffer();
46   private Hpack.Reader hpackReader;
47   private Buffer bytesOut = new Buffer();
48   private Hpack.Writer hpackWriter;
49 
reset()50   @Before public void reset() {
51     hpackReader = newReader(bytesIn);
52     hpackWriter = new Hpack.Writer(bytesOut);
53   }
54 
55   /**
56    * Variable-length quantity special cases strings which are longer than 127
57    * bytes.  Values such as cookies can be 4KiB, and should be possible to send.
58    *
59    * <p> http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-5.2
60    */
largeHeaderValue()61   @Test public void largeHeaderValue() throws IOException {
62     char[] value = new char[4096];
63     Arrays.fill(value, '!');
64     List<Header> headerBlock = headerEntries("cookie", new String(value));
65 
66     hpackWriter.writeHeaders(headerBlock);
67     bytesIn.writeAll(bytesOut);
68     hpackReader.readHeaders();
69 
70     assertEquals(0, hpackReader.dynamicTableHeaderCount);
71 
72     assertEquals(headerBlock, hpackReader.getAndResetHeaderList());
73   }
74 
75   /**
76    * HPACK has a max header table size, which can be smaller than the max header message.
77    * Ensure the larger header content is not lost.
78    */
tooLargeToHPackIsStillEmitted()79   @Test public void tooLargeToHPackIsStillEmitted() throws IOException {
80     bytesIn.writeByte(0x00); // Literal indexed
81     bytesIn.writeByte(0x0a); // Literal name (len = 10)
82     bytesIn.writeUtf8("custom-key");
83 
84     bytesIn.writeByte(0x0d); // Literal value (len = 13)
85     bytesIn.writeUtf8("custom-header");
86 
87     hpackReader.headerTableSizeSetting(1);
88     hpackReader.readHeaders();
89 
90     assertEquals(0, hpackReader.dynamicTableHeaderCount);
91 
92     assertEquals(headerEntries("custom-key", "custom-header"), hpackReader.getAndResetHeaderList());
93   }
94 
setMaxDynamicTableToZeroDoesNotClearHeaderList()95   @Test public void setMaxDynamicTableToZeroDoesNotClearHeaderList() throws IOException {
96     bytesIn.writeByte(0x40); // Literal indexed
97     bytesIn.writeByte(0x0a); // Literal name (len = 10)
98     bytesIn.writeUtf8("custom-key");
99 
100     bytesIn.writeByte(0x0d); // Literal value (len = 13)
101     bytesIn.writeUtf8("custom-header");
102 
103     bytesIn.writeByte(0x20); // Set max table size to 0
104 
105     hpackReader.readHeaders();
106 
107     assertEquals(0, hpackReader.maxDynamicTableByteCount());
108     assertEquals(headerEntries("custom-key", "custom-header"), hpackReader.getAndResetHeaderList());
109   }
110 
111   /** Oldest entries are evicted to support newer ones. */
testEviction()112   @Test public void testEviction() throws IOException {
113     bytesIn.writeByte(0x40); // Literal indexed
114     bytesIn.writeByte(0x0a); // Literal name (len = 10)
115     bytesIn.writeUtf8("custom-foo");
116 
117     bytesIn.writeByte(0x0d); // Literal value (len = 13)
118     bytesIn.writeUtf8("custom-header");
119 
120     bytesIn.writeByte(0x40); // Literal indexed
121     bytesIn.writeByte(0x0a); // Literal name (len = 10)
122     bytesIn.writeUtf8("custom-bar");
123 
124     bytesIn.writeByte(0x0d); // Literal value (len = 13)
125     bytesIn.writeUtf8("custom-header");
126 
127     bytesIn.writeByte(0x40); // Literal indexed
128     bytesIn.writeByte(0x0a); // Literal name (len = 10)
129     bytesIn.writeUtf8("custom-baz");
130 
131     bytesIn.writeByte(0x0d); // Literal value (len = 13)
132     bytesIn.writeUtf8("custom-header");
133 
134     // Set to only support 110 bytes (enough for 2 headers).
135     hpackReader.headerTableSizeSetting(110);
136     hpackReader.readHeaders();
137 
138     assertEquals(2, hpackReader.dynamicTableHeaderCount);
139 
140     Header entry = hpackReader.dynamicTable[headerTableLength() - 1];
141     checkEntry(entry, "custom-bar", "custom-header", 55);
142 
143     entry = hpackReader.dynamicTable[headerTableLength() - 2];
144     checkEntry(entry, "custom-baz", "custom-header", 55);
145 
146     // Once a header field is decoded and added to the reconstructed header
147     // list, it cannot be removed from it. Hence, foo is here.
148     assertEquals(
149         headerEntries(
150             "custom-foo", "custom-header",
151             "custom-bar", "custom-header",
152             "custom-baz", "custom-header"),
153         hpackReader.getAndResetHeaderList());
154 
155     // Simulate receiving a small settings frame, that implies eviction.
156     hpackReader.headerTableSizeSetting(55);
157     assertEquals(1, hpackReader.dynamicTableHeaderCount);
158   }
159 
160   /** Header table backing array is initially 8 long, let's ensure it grows. */
dynamicallyGrowsBeyond64Entries()161   @Test public void dynamicallyGrowsBeyond64Entries() throws IOException {
162     for (int i = 0; i < 256; i++) {
163       bytesIn.writeByte(0x40); // Literal indexed
164       bytesIn.writeByte(0x0a); // Literal name (len = 10)
165       bytesIn.writeUtf8("custom-foo");
166 
167       bytesIn.writeByte(0x0d); // Literal value (len = 13)
168       bytesIn.writeUtf8("custom-header");
169     }
170 
171     hpackReader.headerTableSizeSetting(16384); // Lots of headers need more room!
172     hpackReader.readHeaders();
173 
174     assertEquals(256, hpackReader.dynamicTableHeaderCount);
175   }
176 
huffmanDecodingSupported()177   @Test public void huffmanDecodingSupported() throws IOException {
178     bytesIn.writeByte(0x44); // == Literal indexed ==
179     // Indexed name (idx = 4) -> :path
180     bytesIn.writeByte(0x8c); // Literal value Huffman encoded 12 bytes
181     // decodes to www.example.com which is length 15
182     bytesIn.write(decodeHex("f1e3c2e5f23a6ba0ab90f4ff"));
183 
184     hpackReader.readHeaders();
185 
186     assertEquals(1, hpackReader.dynamicTableHeaderCount);
187     assertEquals(52, hpackReader.dynamicTableByteCount);
188 
189     Header entry = hpackReader.dynamicTable[headerTableLength() - 1];
190     checkEntry(entry, ":path", "www.example.com", 52);
191   }
192 
193   /**
194    * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C.2.1
195    */
readLiteralHeaderFieldWithIndexing()196   @Test public void readLiteralHeaderFieldWithIndexing() throws IOException {
197     bytesIn.writeByte(0x40); // Literal indexed
198     bytesIn.writeByte(0x0a); // Literal name (len = 10)
199     bytesIn.writeUtf8("custom-key");
200 
201     bytesIn.writeByte(0x0d); // Literal value (len = 13)
202     bytesIn.writeUtf8("custom-header");
203 
204     hpackReader.readHeaders();
205 
206     assertEquals(1, hpackReader.dynamicTableHeaderCount);
207     assertEquals(55, hpackReader.dynamicTableByteCount);
208 
209     Header entry = hpackReader.dynamicTable[headerTableLength() - 1];
210     checkEntry(entry, "custom-key", "custom-header", 55);
211 
212     assertEquals(headerEntries("custom-key", "custom-header"), hpackReader.getAndResetHeaderList());
213   }
214 
215   /**
216    * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C.2.2
217    */
literalHeaderFieldWithoutIndexingIndexedName()218   @Test public void literalHeaderFieldWithoutIndexingIndexedName() throws IOException {
219     List<Header> headerBlock = headerEntries(":path", "/sample/path");
220 
221     bytesIn.writeByte(0x04); // == Literal not indexed ==
222     // Indexed name (idx = 4) -> :path
223     bytesIn.writeByte(0x0c); // Literal value (len = 12)
224     bytesIn.writeUtf8("/sample/path");
225 
226     hpackWriter.writeHeaders(headerBlock);
227     assertEquals(bytesIn, bytesOut);
228 
229     hpackReader.readHeaders();
230 
231     assertEquals(0, hpackReader.dynamicTableHeaderCount);
232 
233     assertEquals(headerBlock, hpackReader.getAndResetHeaderList());
234   }
235 
literalHeaderFieldWithoutIndexingNewName()236   @Test public void literalHeaderFieldWithoutIndexingNewName() throws IOException {
237     List<Header> headerBlock = headerEntries("custom-key", "custom-header");
238 
239     bytesIn.writeByte(0x00); // Not indexed
240     bytesIn.writeByte(0x0a); // Literal name (len = 10)
241     bytesIn.writeUtf8("custom-key");
242 
243     bytesIn.writeByte(0x0d); // Literal value (len = 13)
244     bytesIn.writeUtf8("custom-header");
245 
246     hpackWriter.writeHeaders(headerBlock);
247     assertEquals(bytesIn, bytesOut);
248 
249     hpackReader.readHeaders();
250 
251     assertEquals(0, hpackReader.dynamicTableHeaderCount);
252 
253     assertEquals(headerBlock, hpackReader.getAndResetHeaderList());
254   }
255 
literalHeaderFieldNeverIndexedIndexedName()256   @Test public void literalHeaderFieldNeverIndexedIndexedName() throws IOException {
257     bytesIn.writeByte(0x14); // == Literal never indexed ==
258     // Indexed name (idx = 4) -> :path
259     bytesIn.writeByte(0x0c); // Literal value (len = 12)
260     bytesIn.writeUtf8("/sample/path");
261 
262     hpackReader.readHeaders();
263 
264     assertEquals(0, hpackReader.dynamicTableHeaderCount);
265 
266     assertEquals(headerEntries(":path", "/sample/path"), hpackReader.getAndResetHeaderList());
267   }
268 
literalHeaderFieldNeverIndexedNewName()269   @Test public void literalHeaderFieldNeverIndexedNewName() throws IOException {
270     bytesIn.writeByte(0x10); // Never indexed
271     bytesIn.writeByte(0x0a); // Literal name (len = 10)
272     bytesIn.writeUtf8("custom-key");
273 
274     bytesIn.writeByte(0x0d); // Literal value (len = 13)
275     bytesIn.writeUtf8("custom-header");
276 
277     hpackReader.readHeaders();
278 
279     assertEquals(0, hpackReader.dynamicTableHeaderCount);
280 
281     assertEquals(headerEntries("custom-key", "custom-header"), hpackReader.getAndResetHeaderList());
282   }
283 
staticHeaderIsNotCopiedIntoTheIndexedTable()284   @Test public void staticHeaderIsNotCopiedIntoTheIndexedTable() throws IOException {
285     bytesIn.writeByte(0x82); // == Indexed - Add ==
286     // idx = 2 -> :method: GET
287 
288     hpackReader.readHeaders();
289 
290     assertEquals(0, hpackReader.dynamicTableHeaderCount);
291     assertEquals(0, hpackReader.dynamicTableByteCount);
292 
293     assertEquals(null, hpackReader.dynamicTable[headerTableLength() - 1]);
294 
295     assertEquals(headerEntries(":method", "GET"), hpackReader.getAndResetHeaderList());
296   }
297 
298   // Example taken from twitter/hpack DecoderTest.testUnusedIndex
readIndexedHeaderFieldIndex0()299   @Test public void readIndexedHeaderFieldIndex0() throws IOException {
300     bytesIn.writeByte(0x80); // == Indexed - Add idx = 0
301 
302     try {
303       hpackReader.readHeaders();
304       fail();
305     } catch (IOException e) {
306       assertEquals("index == 0", e.getMessage());
307     }
308   }
309 
310   // Example taken from twitter/hpack DecoderTest.testIllegalIndex
readIndexedHeaderFieldTooLargeIndex()311   @Test public void readIndexedHeaderFieldTooLargeIndex() throws IOException {
312     bytesIn.writeShort(0xff00); // == Indexed - Add idx = 127
313 
314     try {
315       hpackReader.readHeaders();
316       fail();
317     } catch (IOException e) {
318       assertEquals("Header index too large 127", e.getMessage());
319     }
320   }
321 
322   // Example taken from twitter/hpack DecoderTest.testInsidiousIndex
readIndexedHeaderFieldInsidiousIndex()323   @Test public void readIndexedHeaderFieldInsidiousIndex() throws IOException {
324     bytesIn.writeByte(0xff); // == Indexed - Add ==
325     bytesIn.write(decodeHex("8080808008")); // idx = -2147483521
326 
327     try {
328       hpackReader.readHeaders();
329       fail();
330     } catch (IOException e) {
331       assertEquals("Header index too large -2147483521", e.getMessage());
332     }
333   }
334 
335   // Example taken from twitter/hpack DecoderTest.testHeaderTableSizeUpdate
minMaxHeaderTableSize()336   @Test public void minMaxHeaderTableSize() throws IOException {
337     bytesIn.writeByte(0x20);
338     hpackReader.readHeaders();
339 
340     assertEquals(0, hpackReader.maxDynamicTableByteCount());
341 
342     bytesIn.writeByte(0x3f); // encode size 4096
343     bytesIn.writeByte(0xe1);
344     bytesIn.writeByte(0x1f);
345     hpackReader.readHeaders();
346 
347     assertEquals(4096, hpackReader.maxDynamicTableByteCount());
348   }
349 
350   // Example taken from twitter/hpack DecoderTest.testIllegalHeaderTableSizeUpdate
cannotSetTableSizeLargerThanSettingsValue()351   @Test public void cannotSetTableSizeLargerThanSettingsValue() throws IOException {
352     bytesIn.writeByte(0x3f); // encode size 4097
353     bytesIn.writeByte(0xe2);
354     bytesIn.writeByte(0x1f);
355 
356     try {
357       hpackReader.readHeaders();
358       fail();
359     } catch (IOException e) {
360       assertEquals("Invalid dynamic table size update 4097", e.getMessage());
361     }
362   }
363 
364   // Example taken from twitter/hpack DecoderTest.testInsidiousMaxHeaderSize
readHeaderTableStateChangeInsidiousMaxHeaderByteCount()365   @Test public void readHeaderTableStateChangeInsidiousMaxHeaderByteCount() throws IOException {
366     bytesIn.writeByte(0x3f);
367     bytesIn.write(decodeHex("e1ffffff07")); // count = -2147483648
368 
369     try {
370       hpackReader.readHeaders();
371       fail();
372     } catch (IOException e) {
373       assertEquals("Invalid dynamic table size update -2147483648", e.getMessage());
374     }
375   }
376 
377   /**
378    * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C.2.4
379    */
readIndexedHeaderFieldFromStaticTableWithoutBuffering()380   @Test public void readIndexedHeaderFieldFromStaticTableWithoutBuffering() throws IOException {
381     bytesIn.writeByte(0x82); // == Indexed - Add ==
382     // idx = 2 -> :method: GET
383 
384     hpackReader.headerTableSizeSetting(0); // SETTINGS_HEADER_TABLE_SIZE == 0
385     hpackReader.readHeaders();
386 
387     // Not buffered in header table.
388     assertEquals(0, hpackReader.dynamicTableHeaderCount);
389 
390     assertEquals(headerEntries(":method", "GET"), hpackReader.getAndResetHeaderList());
391   }
392 
393   /**
394    * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C.2
395    */
readRequestExamplesWithoutHuffman()396   @Test public void readRequestExamplesWithoutHuffman() throws IOException {
397     firstRequestWithoutHuffman();
398     hpackReader.readHeaders();
399     checkReadFirstRequestWithoutHuffman();
400 
401     secondRequestWithoutHuffman();
402     hpackReader.readHeaders();
403     checkReadSecondRequestWithoutHuffman();
404 
405     thirdRequestWithoutHuffman();
406     hpackReader.readHeaders();
407     checkReadThirdRequestWithoutHuffman();
408   }
409 
firstRequestWithoutHuffman()410   private void firstRequestWithoutHuffman() {
411     bytesIn.writeByte(0x82); // == Indexed - Add ==
412     // idx = 2 -> :method: GET
413     bytesIn.writeByte(0x86); // == Indexed - Add ==
414     // idx = 7 -> :scheme: http
415     bytesIn.writeByte(0x84); // == Indexed - Add ==
416     // idx = 6 -> :path: /
417     bytesIn.writeByte(0x41); // == Literal indexed ==
418     // Indexed name (idx = 4) -> :authority
419     bytesIn.writeByte(0x0f); // Literal value (len = 15)
420     bytesIn.writeUtf8("www.example.com");
421   }
422 
checkReadFirstRequestWithoutHuffman()423   private void checkReadFirstRequestWithoutHuffman() {
424     assertEquals(1, hpackReader.dynamicTableHeaderCount);
425 
426     // [  1] (s =  57) :authority: www.example.com
427     Header entry = hpackReader.dynamicTable[headerTableLength() - 1];
428     checkEntry(entry, ":authority", "www.example.com", 57);
429 
430     // Table size: 57
431     assertEquals(57, hpackReader.dynamicTableByteCount);
432 
433     // Decoded header list:
434     assertEquals(headerEntries(
435         ":method", "GET",
436         ":scheme", "http",
437         ":path", "/",
438         ":authority", "www.example.com"), hpackReader.getAndResetHeaderList());
439   }
440 
secondRequestWithoutHuffman()441   private void secondRequestWithoutHuffman() {
442     bytesIn.writeByte(0x82); // == Indexed - Add ==
443     // idx = 2 -> :method: GET
444     bytesIn.writeByte(0x86); // == Indexed - Add ==
445     // idx = 7 -> :scheme: http
446     bytesIn.writeByte(0x84); // == Indexed - Add ==
447     // idx = 6 -> :path: /
448     bytesIn.writeByte(0xbe); // == Indexed - Add ==
449     // Indexed name (idx = 62) -> :authority: www.example.com
450     bytesIn.writeByte(0x58); // == Literal indexed ==
451     // Indexed name (idx = 24) -> cache-control
452     bytesIn.writeByte(0x08); // Literal value (len = 8)
453     bytesIn.writeUtf8("no-cache");
454   }
455 
checkReadSecondRequestWithoutHuffman()456   private void checkReadSecondRequestWithoutHuffman() {
457     assertEquals(2, hpackReader.dynamicTableHeaderCount);
458 
459     // [  1] (s =  53) cache-control: no-cache
460     Header entry = hpackReader.dynamicTable[headerTableLength() - 2];
461     checkEntry(entry, "cache-control", "no-cache", 53);
462 
463     // [  2] (s =  57) :authority: www.example.com
464     entry = hpackReader.dynamicTable[headerTableLength() - 1];
465     checkEntry(entry, ":authority", "www.example.com", 57);
466 
467     // Table size: 110
468     assertEquals(110, hpackReader.dynamicTableByteCount);
469 
470     // Decoded header list:
471     assertEquals(headerEntries(
472         ":method", "GET",
473         ":scheme", "http",
474         ":path", "/",
475         ":authority", "www.example.com",
476         "cache-control", "no-cache"), hpackReader.getAndResetHeaderList());
477   }
478 
thirdRequestWithoutHuffman()479   private void thirdRequestWithoutHuffman() {
480     bytesIn.writeByte(0x82); // == Indexed - Add ==
481     // idx = 2 -> :method: GET
482     bytesIn.writeByte(0x87); // == Indexed - Add ==
483     // idx = 7 -> :scheme: http
484     bytesIn.writeByte(0x85); // == Indexed - Add ==
485     // idx = 5 -> :path: /index.html
486     bytesIn.writeByte(0xbf); // == Indexed - Add ==
487     // Indexed name (idx = 63) -> :authority: www.example.com
488     bytesIn.writeByte(0x40); // Literal indexed
489     bytesIn.writeByte(0x0a); // Literal name (len = 10)
490     bytesIn.writeUtf8("custom-key");
491     bytesIn.writeByte(0x0c); // Literal value (len = 12)
492     bytesIn.writeUtf8("custom-value");
493   }
494 
checkReadThirdRequestWithoutHuffman()495   private void checkReadThirdRequestWithoutHuffman() {
496     assertEquals(3, hpackReader.dynamicTableHeaderCount);
497 
498     // [  1] (s =  54) custom-key: custom-value
499     Header entry = hpackReader.dynamicTable[headerTableLength() - 3];
500     checkEntry(entry, "custom-key", "custom-value", 54);
501 
502     // [  2] (s =  53) cache-control: no-cache
503     entry = hpackReader.dynamicTable[headerTableLength() - 2];
504     checkEntry(entry, "cache-control", "no-cache", 53);
505 
506     // [  3] (s =  57) :authority: www.example.com
507     entry = hpackReader.dynamicTable[headerTableLength() - 1];
508     checkEntry(entry, ":authority", "www.example.com", 57);
509 
510     // Table size: 164
511     assertEquals(164, hpackReader.dynamicTableByteCount);
512 
513     // Decoded header list:
514     assertEquals(headerEntries(
515         ":method", "GET",
516         ":scheme", "https",
517         ":path", "/index.html",
518         ":authority", "www.example.com",
519         "custom-key", "custom-value"), hpackReader.getAndResetHeaderList());
520   }
521 
522   /**
523    * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C.4
524    */
readRequestExamplesWithHuffman()525   @Test public void readRequestExamplesWithHuffman() throws IOException {
526     firstRequestWithHuffman();
527     hpackReader.readHeaders();
528     checkReadFirstRequestWithHuffman();
529 
530     secondRequestWithHuffman();
531     hpackReader.readHeaders();
532     checkReadSecondRequestWithHuffman();
533 
534     thirdRequestWithHuffman();
535     hpackReader.readHeaders();
536     checkReadThirdRequestWithHuffman();
537   }
538 
firstRequestWithHuffman()539   private void firstRequestWithHuffman() {
540     bytesIn.writeByte(0x82); // == Indexed - Add ==
541     // idx = 2 -> :method: GET
542     bytesIn.writeByte(0x86); // == Indexed - Add ==
543     // idx = 6 -> :scheme: http
544     bytesIn.writeByte(0x84); // == Indexed - Add ==
545     // idx = 4 -> :path: /
546     bytesIn.writeByte(0x41); // == Literal indexed ==
547     // Indexed name (idx = 1) -> :authority
548     bytesIn.writeByte(0x8c); // Literal value Huffman encoded 12 bytes
549     // decodes to www.example.com which is length 15
550     bytesIn.write(decodeHex("f1e3c2e5f23a6ba0ab90f4ff"));
551   }
552 
checkReadFirstRequestWithHuffman()553   private void checkReadFirstRequestWithHuffman() {
554     assertEquals(1, hpackReader.dynamicTableHeaderCount);
555 
556     // [  1] (s =  57) :authority: www.example.com
557     Header entry = hpackReader.dynamicTable[headerTableLength() - 1];
558     checkEntry(entry, ":authority", "www.example.com", 57);
559 
560     // Table size: 57
561     assertEquals(57, hpackReader.dynamicTableByteCount);
562 
563     // Decoded header list:
564     assertEquals(headerEntries(
565         ":method", "GET",
566         ":scheme", "http",
567         ":path", "/",
568         ":authority", "www.example.com"), hpackReader.getAndResetHeaderList());
569   }
570 
secondRequestWithHuffman()571   private void secondRequestWithHuffman() {
572     bytesIn.writeByte(0x82); // == Indexed - Add ==
573     // idx = 2 -> :method: GET
574     bytesIn.writeByte(0x86); // == Indexed - Add ==
575     // idx = 6 -> :scheme: http
576     bytesIn.writeByte(0x84); // == Indexed - Add ==
577     // idx = 4 -> :path: /
578     bytesIn.writeByte(0xbe); // == Indexed - Add ==
579     // idx = 62 -> :authority: www.example.com
580     bytesIn.writeByte(0x58); // == Literal indexed ==
581     // Indexed name (idx = 24) -> cache-control
582     bytesIn.writeByte(0x86); // Literal value Huffman encoded 6 bytes
583     // decodes to no-cache which is length 8
584     bytesIn.write(decodeHex("a8eb10649cbf"));
585   }
586 
checkReadSecondRequestWithHuffman()587   private void checkReadSecondRequestWithHuffman() {
588     assertEquals(2, hpackReader.dynamicTableHeaderCount);
589 
590     // [  1] (s =  53) cache-control: no-cache
591     Header entry = hpackReader.dynamicTable[headerTableLength() - 2];
592     checkEntry(entry, "cache-control", "no-cache", 53);
593 
594     // [  2] (s =  57) :authority: www.example.com
595     entry = hpackReader.dynamicTable[headerTableLength() - 1];
596     checkEntry(entry, ":authority", "www.example.com", 57);
597 
598     // Table size: 110
599     assertEquals(110, hpackReader.dynamicTableByteCount);
600 
601     // Decoded header list:
602     assertEquals(headerEntries(
603         ":method", "GET",
604         ":scheme", "http",
605         ":path", "/",
606         ":authority", "www.example.com",
607         "cache-control", "no-cache"), hpackReader.getAndResetHeaderList());
608   }
609 
thirdRequestWithHuffman()610   private void thirdRequestWithHuffman() {
611     bytesIn.writeByte(0x82); // == Indexed - Add ==
612     // idx = 2 -> :method: GET
613     bytesIn.writeByte(0x87); // == Indexed - Add ==
614     // idx = 7 -> :scheme: https
615     bytesIn.writeByte(0x85); // == Indexed - Add ==
616     // idx = 5 -> :path: /index.html
617     bytesIn.writeByte(0xbf); // == Indexed - Add ==
618     // idx = 63 -> :authority: www.example.com
619     bytesIn.writeByte(0x40); // Literal indexed
620     bytesIn.writeByte(0x88); // Literal name Huffman encoded 8 bytes
621     // decodes to custom-key which is length 10
622     bytesIn.write(decodeHex("25a849e95ba97d7f"));
623     bytesIn.writeByte(0x89); // Literal value Huffman encoded 9 bytes
624     // decodes to custom-value which is length 12
625     bytesIn.write(decodeHex("25a849e95bb8e8b4bf"));
626   }
627 
checkReadThirdRequestWithHuffman()628   private void checkReadThirdRequestWithHuffman() {
629     assertEquals(3, hpackReader.dynamicTableHeaderCount);
630 
631     // [  1] (s =  54) custom-key: custom-value
632     Header entry = hpackReader.dynamicTable[headerTableLength() - 3];
633     checkEntry(entry, "custom-key", "custom-value", 54);
634 
635     // [  2] (s =  53) cache-control: no-cache
636     entry = hpackReader.dynamicTable[headerTableLength() - 2];
637     checkEntry(entry, "cache-control", "no-cache", 53);
638 
639     // [  3] (s =  57) :authority: www.example.com
640     entry = hpackReader.dynamicTable[headerTableLength() - 1];
641     checkEntry(entry, ":authority", "www.example.com", 57);
642 
643     // Table size: 164
644     assertEquals(164, hpackReader.dynamicTableByteCount);
645 
646     // Decoded header list:
647     assertEquals(headerEntries(
648         ":method", "GET",
649         ":scheme", "https",
650         ":path", "/index.html",
651         ":authority", "www.example.com",
652         "custom-key", "custom-value"), hpackReader.getAndResetHeaderList());
653   }
654 
readSingleByteInt()655   @Test public void readSingleByteInt() throws IOException {
656     assertEquals(10, newReader(byteStream()).readInt(10, 31));
657     assertEquals(10, newReader(byteStream()).readInt(0xe0 | 10, 31));
658   }
659 
readMultibyteInt()660   @Test public void readMultibyteInt() throws IOException {
661     assertEquals(1337, newReader(byteStream(154, 10)).readInt(31, 31));
662   }
663 
writeSingleByteInt()664   @Test public void writeSingleByteInt() throws IOException {
665     hpackWriter.writeInt(10, 31, 0);
666     assertBytes(10);
667     hpackWriter.writeInt(10, 31, 0xe0);
668     assertBytes(0xe0 | 10);
669   }
670 
writeMultibyteInt()671   @Test public void writeMultibyteInt() throws IOException {
672     hpackWriter.writeInt(1337, 31, 0);
673     assertBytes(31, 154, 10);
674     hpackWriter.writeInt(1337, 31, 0xe0);
675     assertBytes(0xe0 | 31, 154, 10);
676   }
677 
max31BitValue()678   @Test public void max31BitValue() throws IOException {
679     hpackWriter.writeInt(0x7fffffff, 31, 0);
680     assertBytes(31, 224, 255, 255, 255, 7);
681     assertEquals(0x7fffffff,
682         newReader(byteStream(224, 255, 255, 255, 7)).readInt(31, 31));
683   }
684 
prefixMask()685   @Test public void prefixMask() throws IOException {
686     hpackWriter.writeInt(31, 31, 0);
687     assertBytes(31, 0);
688     assertEquals(31, newReader(byteStream(0)).readInt(31, 31));
689   }
690 
prefixMaskMinusOne()691   @Test public void prefixMaskMinusOne() throws IOException {
692     hpackWriter.writeInt(30, 31, 0);
693     assertBytes(30);
694     assertEquals(31, newReader(byteStream(0)).readInt(31, 31));
695   }
696 
zero()697   @Test public void zero() throws IOException {
698     hpackWriter.writeInt(0, 31, 0);
699     assertBytes(0);
700     assertEquals(0, newReader(byteStream()).readInt(0, 31));
701   }
702 
lowercaseHeaderNameBeforeEmit()703   @Test public void lowercaseHeaderNameBeforeEmit() throws IOException {
704     hpackWriter.writeHeaders(Arrays.asList(new Header("FoO", "BaR")));
705     assertBytes(0, 3, 'f', 'o', 'o', 3, 'B', 'a', 'R');
706   }
707 
mixedCaseHeaderNameIsMalformed()708   @Test public void mixedCaseHeaderNameIsMalformed() throws IOException {
709     try {
710       newReader(byteStream(0, 3, 'F', 'o', 'o', 3, 'B', 'a', 'R')).readHeaders();
711       fail();
712     } catch (IOException e) {
713       assertEquals("PROTOCOL_ERROR response malformed: mixed case name: Foo", e.getMessage());
714     }
715   }
716 
emptyHeaderName()717   @Test public void emptyHeaderName() throws IOException {
718     hpackWriter.writeByteString(ByteString.encodeUtf8(""));
719     assertBytes(0);
720     assertEquals(ByteString.EMPTY, newReader(byteStream(0)).readByteString());
721   }
722 
newReader(Buffer source)723   private Hpack.Reader newReader(Buffer source) {
724     return new Hpack.Reader(4096, source);
725   }
726 
byteStream(int... bytes)727   private Buffer byteStream(int... bytes) {
728     return new Buffer().write(intArrayToByteArray(bytes));
729   }
730 
checkEntry(Header entry, String name, String value, int size)731   private void checkEntry(Header entry, String name, String value, int size) {
732     assertEquals(name, entry.name.utf8());
733     assertEquals(value, entry.value.utf8());
734     assertEquals(size, entry.hpackSize);
735   }
736 
assertBytes(int... bytes)737   private void assertBytes(int... bytes) throws IOException {
738     ByteString expected = intArrayToByteArray(bytes);
739     ByteString actual = bytesOut.readByteString();
740     assertEquals(expected, actual);
741   }
742 
intArrayToByteArray(int[] bytes)743   private ByteString intArrayToByteArray(int[] bytes) {
744     byte[] data = new byte[bytes.length];
745     for (int i = 0; i < bytes.length; i++) {
746       data[i] = (byte) bytes[i];
747     }
748     return ByteString.of(data);
749   }
750 
headerTableLength()751   private int headerTableLength() {
752     return hpackReader.dynamicTable.length;
753   }
754 }
755