HpackDraft05Test.java revision 3c938a3f6b61ce5e2dba0d039b03fe73b89fd26c
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 */
16package com.squareup.okhttp.internal.spdy;
17
18import java.io.IOException;
19import java.util.Arrays;
20import java.util.List;
21import okio.ByteString;
22import okio.OkBuffer;
23import org.junit.Before;
24import org.junit.Test;
25
26import static com.squareup.okhttp.internal.Util.headerEntries;
27import static org.junit.Assert.assertEquals;
28import static org.junit.Assert.assertFalse;
29import static org.junit.Assert.assertTrue;
30
31public class HpackDraft05Test {
32
33  private final OkBuffer bytesIn = new OkBuffer();
34  private HpackDraft05.Reader hpackReader;
35  private OkBuffer bytesOut = new OkBuffer();
36  private HpackDraft05.Writer hpackWriter;
37
38  @Before public void reset() {
39    hpackReader = newReader(bytesIn);
40    hpackWriter = new HpackDraft05.Writer(bytesOut);
41  }
42
43  /**
44   * Variable-length quantity special cases strings which are longer than 127
45   * bytes.  Values such as cookies can be 4KiB, and should be possible to send.
46   *
47   * <p> http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#section-4.1.2
48   */
49  @Test public void largeHeaderValue() throws IOException {
50    char[] value = new char[4096];
51    Arrays.fill(value, '!');
52    List<Header> headerBlock = headerEntries("cookie", new String(value));
53
54    hpackWriter.writeHeaders(headerBlock);
55    bytesIn.write(bytesOut, bytesOut.size());
56    hpackReader.readHeaders();
57    hpackReader.emitReferenceSet();
58
59    assertEquals(0, hpackReader.headerCount);
60
61    assertEquals(headerBlock, hpackReader.getAndReset());
62  }
63
64  /**
65   * HPACK has a max header table size, which can be smaller than the max header message.
66   * Ensure the larger header content is not lost.
67   */
68  @Test public void tooLargeToHPackIsStillEmitted() throws IOException {
69    OkBuffer out = new OkBuffer();
70
71    out.writeByte(0x00); // Literal indexed
72    out.writeByte(0x0a); // Literal name (len = 10)
73    out.writeUtf8("custom-key");
74
75    out.writeByte(0x0d); // Literal value (len = 13)
76    out.writeUtf8("custom-header");
77
78    bytesIn.write(out, out.size());
79    hpackReader.maxHeaderTableByteCount(1);
80    hpackReader.readHeaders();
81    hpackReader.emitReferenceSet();
82
83    assertEquals(0, hpackReader.headerCount);
84
85    assertEquals(headerEntries("custom-key", "custom-header"), hpackReader.getAndReset());
86  }
87
88  /** Oldest entries are evicted to support newer ones. */
89  @Test public void testEviction() throws IOException {
90    OkBuffer out = new OkBuffer();
91
92    out.writeByte(0x00); // Literal indexed
93    out.writeByte(0x0a); // Literal name (len = 10)
94    out.writeUtf8("custom-foo");
95
96    out.writeByte(0x0d); // Literal value (len = 13)
97    out.writeUtf8("custom-header");
98
99    out.writeByte(0x00); // Literal indexed
100    out.writeByte(0x0a); // Literal name (len = 10)
101    out.writeUtf8("custom-bar");
102
103    out.writeByte(0x0d); // Literal value (len = 13)
104    out.writeUtf8("custom-header");
105
106    out.writeByte(0x00); // Literal indexed
107    out.writeByte(0x0a); // Literal name (len = 10)
108    out.writeUtf8("custom-baz");
109
110    out.writeByte(0x0d); // Literal value (len = 13)
111    out.writeUtf8("custom-header");
112
113    bytesIn.write(out, out.size());
114    // Set to only support 110 bytes (enough for 2 headers).
115    hpackReader.maxHeaderTableByteCount(110);
116    hpackReader.readHeaders();
117    hpackReader.emitReferenceSet();
118
119    assertEquals(2, hpackReader.headerCount);
120
121    Header entry = hpackReader.headerTable[headerTableLength() - 1];
122    checkEntry(entry, "custom-bar", "custom-header", 55);
123    assertHeaderReferenced(headerTableLength() - 1);
124
125    entry = hpackReader.headerTable[headerTableLength() - 2];
126    checkEntry(entry, "custom-baz", "custom-header", 55);
127    assertHeaderReferenced(headerTableLength() - 2);
128
129    // foo isn't here as it is no longer in the table.
130    // TODO: emit before eviction?
131    assertEquals(headerEntries("custom-bar", "custom-header", "custom-baz", "custom-header"),
132        hpackReader.getAndReset());
133
134    // Simulate receiving a small settings frame, that implies eviction.
135    hpackReader.maxHeaderTableByteCount(55);
136    assertEquals(1, hpackReader.headerCount);
137  }
138
139  /** Header table backing array is initially 8 long, let's ensure it grows. */
140  @Test public void dynamicallyGrowsBeyond64Entries() throws IOException {
141    OkBuffer out = new OkBuffer();
142
143    for (int i = 0; i < 256; i++) {
144      out.writeByte(0x00); // Literal indexed
145      out.writeByte(0x0a); // Literal name (len = 10)
146      out.writeUtf8("custom-foo");
147
148      out.writeByte(0x0d); // Literal value (len = 13)
149      out.writeUtf8("custom-header");
150    }
151
152    bytesIn.write(out, out.size());
153    hpackReader.maxHeaderTableByteCount(16384); // Lots of headers need more room!
154    hpackReader.readHeaders();
155    hpackReader.emitReferenceSet();
156
157    assertEquals(256, hpackReader.headerCount);
158    assertHeaderReferenced(headerTableLength() - 1);
159    assertHeaderReferenced(headerTableLength() - hpackReader.headerCount);
160  }
161
162  @Test public void huffmanDecodingSupported() throws IOException {
163    OkBuffer out = new OkBuffer();
164
165    out.writeByte(0x04); // == Literal indexed ==
166                         // Indexed name (idx = 4) -> :path
167    out.writeByte(0x8b); // Literal value Huffman encoded 11 bytes
168                         // decodes to www.example.com which is length 15
169    byte[] huffmanBytes = new byte[] {
170        (byte) 0xdb, (byte) 0x6d, (byte) 0x88, (byte) 0x3e,
171        (byte) 0x68, (byte) 0xd1, (byte) 0xcb, (byte) 0x12,
172        (byte) 0x25, (byte) 0xba, (byte) 0x7f};
173    out.write(huffmanBytes, 0, huffmanBytes.length);
174
175    bytesIn.write(out, out.size());
176    hpackReader.readHeaders();
177    hpackReader.emitReferenceSet();
178
179    assertEquals(1, hpackReader.headerCount);
180    assertEquals(52, hpackReader.headerTableByteCount);
181
182    Header entry = hpackReader.headerTable[headerTableLength() - 1];
183    checkEntry(entry, ":path", "www.example.com", 52);
184    assertHeaderReferenced(headerTableLength() - 1);
185  }
186
187  /**
188   * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.1.1
189   */
190  @Test public void readLiteralHeaderFieldWithIndexing() throws IOException {
191    OkBuffer out = new OkBuffer();
192
193    out.writeByte(0x00); // Literal indexed
194    out.writeByte(0x0a); // Literal name (len = 10)
195    out.writeUtf8("custom-key");
196
197    out.writeByte(0x0d); // Literal value (len = 13)
198    out.writeUtf8("custom-header");
199
200    bytesIn.write(out, out.size());
201    hpackReader.readHeaders();
202    hpackReader.emitReferenceSet();
203
204    assertEquals(1, hpackReader.headerCount);
205    assertEquals(55, hpackReader.headerTableByteCount);
206
207    Header entry = hpackReader.headerTable[headerTableLength() - 1];
208    checkEntry(entry, "custom-key", "custom-header", 55);
209    assertHeaderReferenced(headerTableLength() - 1);
210
211    assertEquals(headerEntries("custom-key", "custom-header"), hpackReader.getAndReset());
212  }
213
214  /**
215   * Literal Header Field without Indexing - New Name
216   */
217  @Test public void literalHeaderFieldWithoutIndexingNewName() throws IOException {
218    List<Header> headerBlock = headerEntries("custom-key", "custom-header");
219
220    OkBuffer expectedBytes = new OkBuffer();
221
222    expectedBytes.writeByte(0x40); // Not indexed
223    expectedBytes.writeByte(0x0a); // Literal name (len = 10)
224    expectedBytes.write("custom-key".getBytes(), 0, 10);
225
226    expectedBytes.writeByte(0x0d); // Literal value (len = 13)
227    expectedBytes.write("custom-header".getBytes(), 0, 13);
228
229    hpackWriter.writeHeaders(headerBlock);
230    assertEquals(expectedBytes, bytesOut);
231
232    bytesIn.write(bytesOut, bytesOut.size());
233    hpackReader.readHeaders();
234    hpackReader.emitReferenceSet();
235
236    assertEquals(0, hpackReader.headerCount);
237
238    assertEquals(headerBlock, hpackReader.getAndReset());
239  }
240
241  /**
242   * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.1.2
243   */
244  @Test public void literalHeaderFieldWithoutIndexingIndexedName() throws IOException {
245    List<Header> headerBlock = headerEntries(":path", "/sample/path");
246
247    OkBuffer expectedBytes = new OkBuffer();
248    expectedBytes.writeByte(0x44); // == Literal not indexed ==
249                                   // Indexed name (idx = 4) -> :path
250    expectedBytes.writeByte(0x0c); // Literal value (len = 12)
251    expectedBytes.write("/sample/path".getBytes(), 0, 12);
252
253    hpackWriter.writeHeaders(headerBlock);
254    assertEquals(expectedBytes, bytesOut);
255
256    bytesIn.write(bytesOut, bytesOut.size());
257    hpackReader.readHeaders();
258    hpackReader.emitReferenceSet();
259
260    assertEquals(0, hpackReader.headerCount);
261
262    assertEquals(headerBlock, hpackReader.getAndReset());
263  }
264
265  /**
266   * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.1.3
267   */
268  @Test public void readIndexedHeaderField() throws IOException {
269    bytesIn.writeByte(0x82); // == Indexed - Add ==
270                             // idx = 2 -> :method: GET
271
272    hpackReader.readHeaders();
273    hpackReader.emitReferenceSet();
274
275    assertEquals(1, hpackReader.headerCount);
276    assertEquals(42, hpackReader.headerTableByteCount);
277
278    Header entry = hpackReader.headerTable[headerTableLength() - 1];
279    checkEntry(entry, ":method", "GET", 42);
280    assertHeaderReferenced(headerTableLength() - 1);
281
282    assertEquals(headerEntries(":method", "GET"), hpackReader.getAndReset());
283  }
284
285  /**
286   * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#section-3.2.1
287   */
288  @Test public void toggleIndex() throws IOException {
289    // Static table entries are copied to the top of the reference set.
290    bytesIn.writeByte(0x82); // == Indexed - Add ==
291                             // idx = 2 -> :method: GET
292    // Specifying an index to an entry in the reference set removes it.
293    bytesIn.writeByte(0x81); // == Indexed - Remove ==
294                             // idx = 1 -> :method: GET
295
296    hpackReader.readHeaders();
297    hpackReader.emitReferenceSet();
298
299    assertEquals(1, hpackReader.headerCount);
300    assertEquals(42, hpackReader.headerTableByteCount);
301
302    Header entry = hpackReader.headerTable[headerTableLength() - 1];
303    checkEntry(entry, ":method", "GET", 42);
304    assertHeaderNotReferenced(headerTableLength() - 1);
305
306    assertTrue(hpackReader.getAndReset().isEmpty());
307  }
308
309  /**
310   * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.1.4
311   */
312  @Test public void readIndexedHeaderFieldFromStaticTableWithoutBuffering() throws IOException {
313    bytesIn.writeByte(0x82); // == Indexed - Add ==
314                             // idx = 2 -> :method: GET
315
316    hpackReader.maxHeaderTableByteCount(0); // SETTINGS_HEADER_TABLE_SIZE == 0
317    hpackReader.readHeaders();
318    hpackReader.emitReferenceSet();
319
320    // Not buffered in header table.
321    assertEquals(0, hpackReader.headerCount);
322
323    assertEquals(headerEntries(":method", "GET"), hpackReader.getAndReset());
324  }
325
326  /**
327   * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.2
328   */
329  @Test public void readRequestExamplesWithoutHuffman() throws IOException {
330    OkBuffer out = firstRequestWithoutHuffman();
331    bytesIn.write(out, out.size());
332    hpackReader.readHeaders();
333    hpackReader.emitReferenceSet();
334    checkReadFirstRequestWithoutHuffman();
335
336    out = secondRequestWithoutHuffman();
337    bytesIn.write(out, out.size());
338    hpackReader.readHeaders();
339    hpackReader.emitReferenceSet();
340    checkReadSecondRequestWithoutHuffman();
341
342    out = thirdRequestWithoutHuffman();
343    bytesIn.write(out, out.size());
344    hpackReader.readHeaders();
345    hpackReader.emitReferenceSet();
346    checkReadThirdRequestWithoutHuffman();
347  }
348
349  private OkBuffer firstRequestWithoutHuffman() {
350    OkBuffer out = new OkBuffer();
351
352    out.writeByte(0x82); // == Indexed - Add ==
353                         // idx = 2 -> :method: GET
354    out.writeByte(0x87); // == Indexed - Add ==
355                         // idx = 7 -> :scheme: http
356    out.writeByte(0x86); // == Indexed - Add ==
357                         // idx = 6 -> :path: /
358    out.writeByte(0x04); // == Literal indexed ==
359                         // Indexed name (idx = 4) -> :authority
360    out.writeByte(0x0f); // Literal value (len = 15)
361    out.writeUtf8("www.example.com");
362
363    return out;
364  }
365
366  private void checkReadFirstRequestWithoutHuffman() {
367    assertEquals(4, hpackReader.headerCount);
368
369    // [  1] (s =  57) :authority: www.example.com
370    Header entry = hpackReader.headerTable[headerTableLength() - 4];
371    checkEntry(entry, ":authority", "www.example.com", 57);
372    assertHeaderReferenced(headerTableLength() - 4);
373
374    // [  2] (s =  38) :path: /
375    entry = hpackReader.headerTable[headerTableLength() - 3];
376    checkEntry(entry, ":path", "/", 38);
377    assertHeaderReferenced(headerTableLength() - 3);
378
379    // [  3] (s =  43) :scheme: http
380    entry = hpackReader.headerTable[headerTableLength() - 2];
381    checkEntry(entry, ":scheme", "http", 43);
382    assertHeaderReferenced(headerTableLength() - 2);
383
384    // [  4] (s =  42) :method: GET
385    entry = hpackReader.headerTable[headerTableLength() - 1];
386    checkEntry(entry, ":method", "GET", 42);
387    assertHeaderReferenced(headerTableLength() - 1);
388
389    // Table size: 180
390    assertEquals(180, hpackReader.headerTableByteCount);
391
392    // Decoded header set:
393    assertEquals(headerEntries(
394        ":method", "GET",
395        ":scheme", "http",
396        ":path", "/",
397        ":authority", "www.example.com"), hpackReader.getAndReset());
398  }
399
400  private OkBuffer secondRequestWithoutHuffman() {
401    OkBuffer out = new OkBuffer();
402
403    out.writeByte(0x1b); // == Literal indexed ==
404                         // Indexed name (idx = 27) -> cache-control
405    out.writeByte(0x08); // Literal value (len = 8)
406    out.writeUtf8("no-cache");
407
408    return out;
409  }
410
411  private void checkReadSecondRequestWithoutHuffman() {
412    assertEquals(5, hpackReader.headerCount);
413
414    // [  1] (s =  53) cache-control: no-cache
415    Header entry = hpackReader.headerTable[headerTableLength() - 5];
416    checkEntry(entry, "cache-control", "no-cache", 53);
417    assertHeaderReferenced(headerTableLength() - 5);
418
419    // [  2] (s =  57) :authority: www.example.com
420    entry = hpackReader.headerTable[headerTableLength() - 4];
421    checkEntry(entry, ":authority", "www.example.com", 57);
422    assertHeaderReferenced(headerTableLength() - 4);
423
424    // [  3] (s =  38) :path: /
425    entry = hpackReader.headerTable[headerTableLength() - 3];
426    checkEntry(entry, ":path", "/", 38);
427    assertHeaderReferenced(headerTableLength() - 3);
428
429    // [  4] (s =  43) :scheme: http
430    entry = hpackReader.headerTable[headerTableLength() - 2];
431    checkEntry(entry, ":scheme", "http", 43);
432    assertHeaderReferenced(headerTableLength() - 2);
433
434    // [  5] (s =  42) :method: GET
435    entry = hpackReader.headerTable[headerTableLength() - 1];
436    checkEntry(entry, ":method", "GET", 42);
437    assertHeaderReferenced(headerTableLength() - 1);
438
439    // Table size: 233
440    assertEquals(233, hpackReader.headerTableByteCount);
441
442    // Decoded header set:
443    assertEquals(headerEntries(
444        ":method", "GET",
445        ":scheme", "http",
446        ":path", "/",
447        ":authority", "www.example.com",
448        "cache-control", "no-cache"), hpackReader.getAndReset());
449  }
450
451  private OkBuffer thirdRequestWithoutHuffman() {
452    OkBuffer out = new OkBuffer();
453
454    out.writeByte(0x80); // == Empty reference set ==
455    out.writeByte(0x85); // == Indexed - Add ==
456                         // idx = 5 -> :method: GET
457    out.writeByte(0x8c); // == Indexed - Add ==
458                         // idx = 12 -> :scheme: https
459    out.writeByte(0x8b); // == Indexed - Add ==
460                         // idx = 11 -> :path: /index.html
461    out.writeByte(0x84); // == Indexed - Add ==
462                         // idx = 4 -> :authority: www.example.com
463    out.writeByte(0x00); // Literal indexed
464    out.writeByte(0x0a); // Literal name (len = 10)
465    out.writeUtf8("custom-key");
466    out.writeByte(0x0c); // Literal value (len = 12)
467    out.writeUtf8("custom-value");
468
469    return out;
470  }
471
472  private void checkReadThirdRequestWithoutHuffman() {
473    assertEquals(8, hpackReader.headerCount);
474
475    // [  1] (s =  54) custom-key: custom-value
476    Header entry = hpackReader.headerTable[headerTableLength() - 8];
477    checkEntry(entry, "custom-key", "custom-value", 54);
478    assertHeaderReferenced(headerTableLength() - 8);
479
480    // [  2] (s =  48) :path: /index.html
481    entry = hpackReader.headerTable[headerTableLength() - 7];
482    checkEntry(entry, ":path", "/index.html", 48);
483    assertHeaderReferenced(headerTableLength() - 7);
484
485    // [  3] (s =  44) :scheme: https
486    entry = hpackReader.headerTable[headerTableLength() - 6];
487    checkEntry(entry, ":scheme", "https", 44);
488    assertHeaderReferenced(headerTableLength() - 6);
489
490    // [  4] (s =  53) cache-control: no-cache
491    entry = hpackReader.headerTable[headerTableLength() - 5];
492    checkEntry(entry, "cache-control", "no-cache", 53);
493    assertHeaderNotReferenced(headerTableLength() - 5);
494
495    // [  5] (s =  57) :authority: www.example.com
496    entry = hpackReader.headerTable[headerTableLength() - 4];
497    checkEntry(entry, ":authority", "www.example.com", 57);
498    assertHeaderReferenced(headerTableLength() - 4);
499
500    // [  6] (s =  38) :path: /
501    entry = hpackReader.headerTable[headerTableLength() - 3];
502    checkEntry(entry, ":path", "/", 38);
503    assertHeaderNotReferenced(headerTableLength() - 3);
504
505    // [  7] (s =  43) :scheme: http
506    entry = hpackReader.headerTable[headerTableLength() - 2];
507    checkEntry(entry, ":scheme", "http", 43);
508    assertHeaderNotReferenced(headerTableLength() - 2);
509
510    // [  8] (s =  42) :method: GET
511    entry = hpackReader.headerTable[headerTableLength() - 1];
512    checkEntry(entry, ":method", "GET", 42);
513    assertHeaderReferenced(headerTableLength() - 1);
514
515    // Table size: 379
516    assertEquals(379, hpackReader.headerTableByteCount);
517
518    // Decoded header set:
519    // TODO: order is not correct per docs, but then again, the spec doesn't require ordering.
520    assertEquals(headerEntries(
521        ":method", "GET",
522        ":authority", "www.example.com",
523        ":scheme", "https",
524        ":path", "/index.html",
525        "custom-key", "custom-value"), hpackReader.getAndReset());
526  }
527
528  /**
529   * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.3
530   */
531  @Test public void readRequestExamplesWithHuffman() throws IOException {
532    OkBuffer out = firstRequestWithHuffman();
533    bytesIn.write(out, out.size());
534    hpackReader.readHeaders();
535    hpackReader.emitReferenceSet();
536    checkReadFirstRequestWithHuffman();
537
538    out = secondRequestWithHuffman();
539    bytesIn.write(out, out.size());
540    hpackReader.readHeaders();
541    hpackReader.emitReferenceSet();
542    checkReadSecondRequestWithHuffman();
543
544    out = thirdRequestWithHuffman();
545    bytesIn.write(out, out.size());
546    hpackReader.readHeaders();
547    hpackReader.emitReferenceSet();
548    checkReadThirdRequestWithHuffman();
549  }
550
551  private OkBuffer firstRequestWithHuffman() {
552    OkBuffer out = new OkBuffer();
553
554    out.writeByte(0x82); // == Indexed - Add ==
555                         // idx = 2 -> :method: GET
556    out.writeByte(0x87); // == Indexed - Add ==
557                         // idx = 7 -> :scheme: http
558    out.writeByte(0x86); // == Indexed - Add ==
559                         // idx = 6 -> :path: /
560    out.writeByte(0x04); // == Literal indexed ==
561                         // Indexed name (idx = 4) -> :authority
562    out.writeByte(0x8b); // Literal value Huffman encoded 11 bytes
563                         // decodes to www.example.com which is length 15
564    byte[] huffmanBytes = new byte[] {
565        (byte) 0xdb, (byte) 0x6d, (byte) 0x88, (byte) 0x3e,
566        (byte) 0x68, (byte) 0xd1, (byte) 0xcb, (byte) 0x12,
567        (byte) 0x25, (byte) 0xba, (byte) 0x7f};
568    out.write(huffmanBytes, 0, huffmanBytes.length);
569
570    return out;
571  }
572
573  private void checkReadFirstRequestWithHuffman() {
574    assertEquals(4, hpackReader.headerCount);
575
576    // [  1] (s =  57) :authority: www.example.com
577    Header entry = hpackReader.headerTable[headerTableLength() - 4];
578    checkEntry(entry, ":authority", "www.example.com", 57);
579    assertHeaderReferenced(headerTableLength() - 4);
580
581    // [  2] (s =  38) :path: /
582    entry = hpackReader.headerTable[headerTableLength() - 3];
583    checkEntry(entry, ":path", "/", 38);
584    assertHeaderReferenced(headerTableLength() - 3);
585
586    // [  3] (s =  43) :scheme: http
587    entry = hpackReader.headerTable[headerTableLength() - 2];
588    checkEntry(entry, ":scheme", "http", 43);
589    assertHeaderReferenced(headerTableLength() - 2);
590
591    // [  4] (s =  42) :method: GET
592    entry = hpackReader.headerTable[headerTableLength() - 1];
593    checkEntry(entry, ":method", "GET", 42);
594    assertHeaderReferenced(headerTableLength() - 1);
595
596    // Table size: 180
597    assertEquals(180, hpackReader.headerTableByteCount);
598
599    // Decoded header set:
600    assertEquals(headerEntries(
601        ":method", "GET",
602        ":scheme", "http",
603        ":path", "/",
604        ":authority", "www.example.com"), hpackReader.getAndReset());
605  }
606
607  private OkBuffer secondRequestWithHuffman() {
608    OkBuffer out = new OkBuffer();
609
610    out.writeByte(0x1b); // == Literal indexed ==
611                         // Indexed name (idx = 27) -> cache-control
612    out.writeByte(0x86); // Literal value Huffman encoded 6 bytes
613                         // decodes to no-cache which is length 8
614    byte[] huffmanBytes = new byte[] {
615        (byte) 0x63, (byte) 0x65, (byte) 0x4a, (byte) 0x13,
616        (byte) 0x98, (byte) 0xff};
617    out.write(huffmanBytes, 0, huffmanBytes.length);
618
619    return out;
620  }
621
622  private void checkReadSecondRequestWithHuffman() {
623    assertEquals(5, hpackReader.headerCount);
624
625    // [  1] (s =  53) cache-control: no-cache
626    Header entry = hpackReader.headerTable[headerTableLength() - 5];
627    checkEntry(entry, "cache-control", "no-cache", 53);
628    assertHeaderReferenced(headerTableLength() - 5);
629
630    // [  2] (s =  57) :authority: www.example.com
631    entry = hpackReader.headerTable[headerTableLength() - 4];
632    checkEntry(entry, ":authority", "www.example.com", 57);
633    assertHeaderReferenced(headerTableLength() - 4);
634
635    // [  3] (s =  38) :path: /
636    entry = hpackReader.headerTable[headerTableLength() - 3];
637    checkEntry(entry, ":path", "/", 38);
638    assertHeaderReferenced(headerTableLength() - 3);
639
640    // [  4] (s =  43) :scheme: http
641    entry = hpackReader.headerTable[headerTableLength() - 2];
642    checkEntry(entry, ":scheme", "http", 43);
643    assertHeaderReferenced(headerTableLength() - 2);
644
645    // [  5] (s =  42) :method: GET
646    entry = hpackReader.headerTable[headerTableLength() - 1];
647    checkEntry(entry, ":method", "GET", 42);
648    assertHeaderReferenced(headerTableLength() - 1);
649
650    // Table size: 233
651    assertEquals(233, hpackReader.headerTableByteCount);
652
653    // Decoded header set:
654    assertEquals(headerEntries(
655        ":method", "GET",
656        ":scheme", "http",
657        ":path", "/",
658        ":authority", "www.example.com",
659        "cache-control", "no-cache"), hpackReader.getAndReset());
660  }
661
662  private OkBuffer thirdRequestWithHuffman() {
663    OkBuffer out = new OkBuffer();
664
665    out.writeByte(0x80); // == Empty reference set ==
666    out.writeByte(0x85); // == Indexed - Add ==
667                         // idx = 5 -> :method: GET
668    out.writeByte(0x8c); // == Indexed - Add ==
669                         // idx = 12 -> :scheme: https
670    out.writeByte(0x8b); // == Indexed - Add ==
671                         // idx = 11 -> :path: /index.html
672    out.writeByte(0x84); // == Indexed - Add ==
673                         // idx = 4 -> :authority: www.example.com
674    out.writeByte(0x00); // Literal indexed
675    out.writeByte(0x88); // Literal name Huffman encoded 8 bytes
676                         // decodes to custom-key which is length 10
677    byte[] huffmanBytes = new byte[] {
678        (byte) 0x4e, (byte) 0xb0, (byte) 0x8b, (byte) 0x74,
679        (byte) 0x97, (byte) 0x90, (byte) 0xfa, (byte) 0x7f};
680    out.write(huffmanBytes, 0, huffmanBytes.length);
681    out.writeByte(0x89); // Literal value Huffman encoded 6 bytes
682                         // decodes to custom-value which is length 12
683    huffmanBytes = new byte[] {
684        (byte) 0x4e, (byte) 0xb0, (byte) 0x8b, (byte) 0x74,
685        (byte) 0x97, (byte) 0x9a, (byte) 0x17, (byte) 0xa8,
686        (byte) 0xff};
687    out.write(huffmanBytes, 0, huffmanBytes.length);
688
689    return out;
690  }
691
692  private void checkReadThirdRequestWithHuffman() {
693    assertEquals(8, hpackReader.headerCount);
694
695    // [  1] (s =  54) custom-key: custom-value
696    Header entry = hpackReader.headerTable[headerTableLength() - 8];
697    checkEntry(entry, "custom-key", "custom-value", 54);
698    assertHeaderReferenced(headerTableLength() - 8);
699
700    // [  2] (s =  48) :path: /index.html
701    entry = hpackReader.headerTable[headerTableLength() - 7];
702    checkEntry(entry, ":path", "/index.html", 48);
703    assertHeaderReferenced(headerTableLength() - 7);
704
705    // [  3] (s =  44) :scheme: https
706    entry = hpackReader.headerTable[headerTableLength() - 6];
707    checkEntry(entry, ":scheme", "https", 44);
708    assertHeaderReferenced(headerTableLength() - 6);
709
710    // [  4] (s =  53) cache-control: no-cache
711    entry = hpackReader.headerTable[headerTableLength() - 5];
712    checkEntry(entry, "cache-control", "no-cache", 53);
713    assertHeaderNotReferenced(headerTableLength() - 5);
714
715    // [  5] (s =  57) :authority: www.example.com
716    entry = hpackReader.headerTable[headerTableLength() - 4];
717    checkEntry(entry, ":authority", "www.example.com", 57);
718    assertHeaderReferenced(headerTableLength() - 4);
719
720    // [  6] (s =  38) :path: /
721    entry = hpackReader.headerTable[headerTableLength() - 3];
722    checkEntry(entry, ":path", "/", 38);
723    assertHeaderNotReferenced(headerTableLength() - 3);
724
725    // [  7] (s =  43) :scheme: http
726    entry = hpackReader.headerTable[headerTableLength() - 2];
727    checkEntry(entry, ":scheme", "http", 43);
728    assertHeaderNotReferenced(headerTableLength() - 2);
729
730    // [  8] (s =  42) :method: GET
731    entry = hpackReader.headerTable[headerTableLength() - 1];
732    checkEntry(entry, ":method", "GET", 42);
733    assertHeaderReferenced(headerTableLength() - 1);
734
735    // Table size: 379
736    assertEquals(379, hpackReader.headerTableByteCount);
737
738    // Decoded header set:
739    // TODO: order is not correct per docs, but then again, the spec doesn't require ordering.
740    assertEquals(headerEntries(
741        ":method", "GET",
742        ":authority", "www.example.com",
743        ":scheme", "https",
744        ":path", "/index.html",
745        "custom-key", "custom-value"), hpackReader.getAndReset());
746  }
747
748  @Test public void readSingleByteInt() throws IOException {
749    assertEquals(10, newReader(byteStream()).readInt(10, 31));
750    assertEquals(10, newReader(byteStream()).readInt(0xe0 | 10, 31));
751  }
752
753  @Test public void readMultibyteInt() throws IOException {
754    assertEquals(1337, newReader(byteStream(154, 10)).readInt(31, 31));
755  }
756
757  @Test public void writeSingleByteInt() throws IOException {
758    hpackWriter.writeInt(10, 31, 0);
759    assertBytes(10);
760    hpackWriter.writeInt(10, 31, 0xe0);
761    assertBytes(0xe0 | 10);
762  }
763
764  @Test public void writeMultibyteInt() throws IOException {
765    hpackWriter.writeInt(1337, 31, 0);
766    assertBytes(31, 154, 10);
767    hpackWriter.writeInt(1337, 31, 0xe0);
768    assertBytes(0xe0 | 31, 154, 10);
769  }
770
771  @Test public void max31BitValue() throws IOException {
772    hpackWriter.writeInt(0x7fffffff, 31, 0);
773    assertBytes(31, 224, 255, 255, 255, 7);
774    assertEquals(0x7fffffff,
775        newReader(byteStream(224, 255, 255, 255, 7)).readInt(31, 31));
776  }
777
778  @Test public void prefixMask() throws IOException {
779    hpackWriter.writeInt(31, 31, 0);
780    assertBytes(31, 0);
781    assertEquals(31, newReader(byteStream(0)).readInt(31, 31));
782  }
783
784  @Test public void prefixMaskMinusOne() throws IOException {
785    hpackWriter.writeInt(30, 31, 0);
786    assertBytes(30);
787    assertEquals(31, newReader(byteStream(0)).readInt(31, 31));
788  }
789
790  @Test public void zero() throws IOException {
791    hpackWriter.writeInt(0, 31, 0);
792    assertBytes(0);
793    assertEquals(0, newReader(byteStream()).readInt(0, 31));
794  }
795
796  @Test public void headerName() throws IOException {
797    hpackWriter.writeByteString(ByteString.encodeUtf8("foo"));
798    assertBytes(3, 'f', 'o', 'o');
799    assertEquals("foo", newReader(byteStream(3, 'F', 'o', 'o')).readByteString(true).utf8());
800  }
801
802  @Test public void emptyHeaderName() throws IOException {
803    hpackWriter.writeByteString(ByteString.encodeUtf8(""));
804    assertBytes(0);
805    assertEquals(ByteString.EMPTY, newReader(byteStream(0)).readByteString(true));
806    assertEquals(ByteString.EMPTY, newReader(byteStream(0)).readByteString(false));
807  }
808
809  private HpackDraft05.Reader newReader(OkBuffer source) {
810    return new HpackDraft05.Reader(false, 4096, source);
811  }
812
813  private OkBuffer byteStream(int... bytes) {
814    return new OkBuffer().write(intArrayToByteArray(bytes));
815  }
816
817  private void checkEntry(Header entry, String name, String value, int size) {
818    assertEquals(name, entry.name.utf8());
819    assertEquals(value, entry.value.utf8());
820    assertEquals(size, entry.hpackSize);
821  }
822
823  private void assertBytes(int... bytes) {
824    ByteString expected = intArrayToByteArray(bytes);
825    ByteString actual = bytesOut.readByteString(bytesOut.size());
826    assertEquals(expected, actual);
827  }
828
829  private ByteString intArrayToByteArray(int[] bytes) {
830    byte[] data = new byte[bytes.length];
831    for (int i = 0; i < bytes.length; i++) {
832      data[i] = (byte) bytes[i];
833    }
834    return ByteString.of(data);
835  }
836
837  private void assertHeaderReferenced(int index) {
838    assertTrue(hpackReader.referencedHeaders.get(index));
839  }
840
841  private void assertHeaderNotReferenced(int index) {
842    assertFalse(hpackReader.referencedHeaders.get(index));
843  }
844
845  private int headerTableLength() {
846    return hpackReader.headerTable.length;
847  }
848}
849