View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.core5.http2.hpack;
29  
30  import java.nio.ByteBuffer;
31  import java.nio.charset.Charset;
32  import java.nio.charset.StandardCharsets;
33  import java.util.Arrays;
34  import java.util.List;
35  
36  import org.apache.hc.core5.http.Header;
37  import org.apache.hc.core5.http.message.BasicHeader;
38  import org.apache.hc.core5.util.ByteArrayBuffer;
39  import org.hamcrest.CoreMatchers;
40  import org.hamcrest.MatcherAssert;
41  import org.junit.Assert;
42  import org.junit.Test;
43  
44  public class TestHPackCoding {
45  
46      @Test
47      public void testIntegerEncodingRFC7541Examples() throws Exception {
48  
49          final ByteArrayBuffer buffer = new ByteArrayBuffer(16);
50          HPackEncoder.encodeInt(buffer, 5, 10, 0x0);
51  
52          Assert.assertEquals(1, buffer.length());
53          Assert.assertEquals(0b00001010, buffer.byteAt(0) & 0xFF);
54  
55          buffer.clear();
56          HPackEncoder.encodeInt(buffer, 5, 1337, 0x0);
57  
58          Assert.assertEquals(3, buffer.length());
59          Assert.assertEquals(0b00011111, buffer.byteAt(0) & 0xFF);
60          Assert.assertEquals(0b10011010, buffer.byteAt(1) & 0xFF);
61          Assert.assertEquals(0b00001010, buffer.byteAt(2) & 0xFF);
62  
63          buffer.clear();
64          HPackEncoder.encodeInt(buffer, 8, 42, 0x0);
65          Assert.assertEquals(1, buffer.length());
66          Assert.assertEquals(0b00101010, buffer.byteAt(0) & 0xFF);
67      }
68  
69      static ByteBuffer wrap(final ByteArrayBuffer src) {
70          // Use buffers with array offsets to verify correcness in additional cases
71          final byte[] originalArray = src.array();
72          final byte[] newArray = new byte[originalArray.length + 2];
73          System.arraycopy(originalArray, 0, newArray, 1, src.length());
74          return ByteBuffer.wrap(newArray, 1, src.length()).slice();
75      }
76  
77      private static byte[] toArray(final ByteBuffer buffer) {
78          final byte[] result = new byte[buffer.remaining()];
79          buffer.get(result);
80          return result;
81      }
82  
83      @Test
84      public void testIntegerCoding() throws Exception {
85  
86          final ByteArrayBuffer buffer = new ByteArrayBuffer(16);
87  
88          for (int n = 4; n <= 8; n++) {
89  
90              buffer.clear();
91  
92              HPackEncoder.encodeInt(buffer, n, 10, 0x0);
93              Assert.assertEquals(10, HPackDecoder.decodeInt(wrap(buffer), n));
94  
95              buffer.clear();
96  
97              HPackEncoder.encodeInt(buffer, n, 123456, 0x0);
98              Assert.assertEquals(123456, HPackDecoder.decodeInt(wrap(buffer), n));
99  
100             buffer.clear();
101 
102             HPackEncoder.encodeInt(buffer, n, Integer.MAX_VALUE, 0x0);
103             Assert.assertEquals(Integer.MAX_VALUE, HPackDecoder.decodeInt(wrap(buffer), n));
104         }
105 
106     }
107 
108     @Test
109     public void testIntegerCodingLimit() throws Exception {
110 
111         final ByteBuffer src1 = createByteBuffer(0x7f, 0x80, 0xff, 0xff, 0xff, 0x07);
112         Assert.assertEquals(Integer.MAX_VALUE, HPackDecoder.decodeInt(src1, 7));
113 
114         final ByteBuffer src2 = createByteBuffer(0x7f, 0x80, 0xff, 0xff, 0xff, 0x08);
115         try {
116             HPackDecoder.decodeInt(src2, 7);
117         } catch (final HPackException expected) {
118         }
119         final ByteBuffer src3 = createByteBuffer(0x7f, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01);
120         try {
121             HPackDecoder.decodeInt(src3, 7);
122         } catch (final HPackException expected) {
123         }
124     }
125 
126     private static ByteBuffer createByteBuffer(final int... bytes) {
127 
128         final ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
129         for (final int b : bytes) {
130             buffer.put((byte) b);
131         }
132         buffer.flip();
133         return buffer;
134     }
135 
136     @Test
137     public void testPlainStringDecoding() throws Exception {
138 
139         final ByteBuffer src = createByteBuffer(
140                 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79);
141 
142         final ByteArrayBuffer buffer = new ByteArrayBuffer(16);
143         HPackDecoder.decodePlainString(buffer, src);
144         Assert.assertEquals("custom-key", new String(buffer.array(), 0, buffer.length(), StandardCharsets.US_ASCII));
145         Assert.assertFalse("Decoding completed", src.hasRemaining());
146     }
147 
148     @Test
149     public void testPlainStringDecodingRemainingContent() throws Exception {
150 
151         final ByteBuffer src = createByteBuffer(
152                 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x01, 0x01, 0x01, 0x01);
153 
154         final ByteArrayBuffer buffer = new ByteArrayBuffer(16);
155         HPackDecoder.decodePlainString(buffer, src);
156         Assert.assertEquals("custom-key", new String(buffer.array(), 0, buffer.length(), StandardCharsets.US_ASCII));
157         Assert.assertEquals(4, src.remaining());
158     }
159 
160     @Test
161     public void testPlainStringDecodingReadOnly() throws Exception {
162 
163         final ByteBuffer src = createByteBuffer(
164                 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x50, 0x50, 0x50, 0x50);
165 
166         final ByteBuffer srcRO = src.asReadOnlyBuffer();
167         final ByteArrayBuffer buffer = new ByteArrayBuffer(16);
168         HPackDecoder.decodePlainString(buffer, srcRO);
169         Assert.assertEquals("custom-key", new String(buffer.array(), 0, buffer.length(), StandardCharsets.US_ASCII));
170         Assert.assertEquals(4, srcRO.remaining());
171     }
172 
173     @Test(expected = HPackException.class)
174     public void testPlainStringDecodingTruncated() throws Exception {
175 
176         final ByteBuffer src = createByteBuffer(
177                 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65);
178 
179         final ByteArrayBuffer buffer = new ByteArrayBuffer(16);
180         HPackDecoder.decodePlainString(buffer, src);
181     }
182 
183     @Test
184     public void testHuffmanDecodingRFC7541Examples() throws Exception {
185         final ByteBuffer src = createByteBuffer(
186                 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff);
187 
188         final ByteArrayBuffer buffer = new ByteArrayBuffer(16);
189         HPackDecoder.decodeHuffman(buffer, src);
190         Assert.assertEquals("www.example.com", new String(buffer.array(), 0, buffer.length(), StandardCharsets.US_ASCII));
191         Assert.assertFalse("Decoding completed", src.hasRemaining());
192     }
193 
194     private static ByteBuffer createByteBuffer(final String s, final Charset charset) {
195 
196         return ByteBuffer.wrap(s.getBytes(charset));
197     }
198 
199     @Test
200     public void testHuffmanEncoding() throws Exception {
201         final ByteArrayBuffer buffer = new ByteArrayBuffer(16);
202         HPackEncoder.encodeHuffman(buffer, createByteBuffer("www.example.com", StandardCharsets.US_ASCII));
203         final ByteBuffer expected = createByteBuffer(
204                 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff);
205         Assert.assertArrayEquals(toArray(expected), buffer.toByteArray());
206     }
207 
208     @Test
209     public void testBasicStringCoding() throws Exception {
210 
211         final HPackEncoder encoder = new HPackEncoder(StandardCharsets.US_ASCII);
212         final HPackDecoder decoder = new HPackDecoder(StandardCharsets.US_ASCII);
213 
214         final ByteArrayBuffer buffer = new ByteArrayBuffer(16);
215         encoder.encodeString(buffer, "this and that", false);
216 
217         final StringBuilder strBuf = new StringBuilder();
218         decoder.decodeString(wrap(buffer), strBuf);
219         Assert.assertEquals("this and that", strBuf.toString());
220 
221         buffer.clear();
222         strBuf.setLength(0);
223         encoder.encodeString(buffer, "this and that and Huffman", true);
224         decoder.decodeString(wrap(buffer), strBuf);
225         Assert.assertEquals("this and that and Huffman", strBuf.toString());
226     }
227 
228     static final int SWISS_GERMAN_HELLO[] = {
229             0x47, 0x72, 0xFC, 0x65, 0x7A, 0x69, 0x5F, 0x7A, 0xE4, 0x6D, 0xE4
230     };
231 
232     static final int RUSSIAN_HELLO[] = {
233             0x412, 0x441, 0x435, 0x43C, 0x5F, 0x43F, 0x440, 0x438,
234             0x432, 0x435, 0x442
235     };
236 
237     private static String constructHelloString(final int[] raw, final int n) {
238         final StringBuilder buffer = new StringBuilder();
239         for (int j = 0; j < n; j++) {
240             if (j > 0) {
241                 buffer.append("; ");
242             }
243             for (int i = 0; i < raw.length; i++) {
244                 buffer.append((char) raw[i]);
245             }
246         }
247         return buffer.toString();
248     }
249 
250     @Test
251     public void testComplexStringCoding1() throws Exception {
252 
253         for (final Charset charset : new Charset[]{StandardCharsets.ISO_8859_1, StandardCharsets.UTF_8, StandardCharsets.UTF_16}) {
254 
255             final ByteArrayBuffer buffer = new ByteArrayBuffer(16);
256             final StringBuilder strBuf = new StringBuilder();
257 
258             final HPackEncoder encoder = new HPackEncoder(charset);
259             final HPackDecoder decoder = new HPackDecoder(charset);
260 
261             for (int n = 0; n < 10; n++) {
262 
263                 final String hello = constructHelloString(SWISS_GERMAN_HELLO, 1 + 10 * n);
264 
265                 for (final boolean b : new boolean[]{false, true}) {
266 
267                     buffer.clear();
268                     encoder.encodeString(buffer, hello, b);
269                     strBuf.setLength(0);
270                     decoder.decodeString(wrap(buffer), strBuf);
271                     final String helloBack = strBuf.toString();
272                     Assert.assertEquals("charset: " + charset + "; huffman: " + b, hello, helloBack);
273                 }
274             }
275         }
276     }
277 
278     @Test
279     public void testComplexStringCoding2() throws Exception {
280 
281         for (final Charset charset : new Charset[]{Charset.forName("KOI8-R"), StandardCharsets.UTF_8, StandardCharsets.UTF_16}) {
282 
283             final ByteArrayBuffer buffer = new ByteArrayBuffer(16);
284             final StringBuilder strBuf = new StringBuilder();
285 
286             final HPackEncoder encoder = new HPackEncoder(charset);
287             final HPackDecoder decoder = new HPackDecoder(charset);
288 
289             for (int n = 0; n < 10; n++) {
290 
291                 final String hello = constructHelloString(RUSSIAN_HELLO, 1 + 10 * n);
292 
293                 for (final boolean b : new boolean[]{false, true}) {
294 
295                     buffer.clear();
296                     strBuf.setLength(0);
297                     encoder.encodeString(buffer, hello, b);
298                     decoder.decodeString(wrap(buffer), strBuf);
299                     final String helloBack = strBuf.toString();
300                     Assert.assertEquals("charset: " + charset + "; huffman: " + b, hello, helloBack);
301                 }
302             }
303         }
304     }
305 
306     private static void assertHeaderEquals(final Header expected, final Header actual) {
307 
308         Assert.assertNotNull(actual);
309         Assert.assertEquals("Header name", expected.getName(), actual.getName());
310         Assert.assertEquals("Header value", expected.getValue(), actual.getValue());
311         Assert.assertEquals("Header sensitive flag", expected.isSensitive(), actual.isSensitive());
312     }
313 
314     @Test
315     public void testLiteralHeaderWithIndexingDecodingRFC7541Examples() throws Exception {
316 
317         final ByteBuffer src = createByteBuffer(
318                 0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x0d, 0x63, 0x75, 0x73,
319                 0x74, 0x6f, 0x6d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72);
320 
321         final InboundDynamicTable dynamicTable = new InboundDynamicTable();
322         final HPackDecoder decoder = new HPackDecoder(dynamicTable, StandardCharsets.US_ASCII);
323         final Header header = decoder.decodeLiteralHeader(src, HPackRepresentation.WITH_INDEXING);
324         assertHeaderEquals(new BasicHeader("custom-key", "custom-header"), header);
325         Assert.assertFalse("Decoding completed", src.hasRemaining());
326 
327         Assert.assertEquals(1, dynamicTable.dynamicLength());
328         assertHeaderEquals(header, dynamicTable.getDynamicEntry(0));
329     }
330 
331     @Test
332     public void testLiteralHeaderWithoutIndexingDecodingRFC7541Examples() throws Exception {
333 
334         final ByteBuffer src = createByteBuffer(
335                 0x04, 0x0c, 0x2f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68);
336 
337         final InboundDynamicTable dynamicTable = new InboundDynamicTable();
338         final HPackDecoder decoder = new HPackDecoder(dynamicTable, StandardCharsets.US_ASCII);
339         final Header header = decoder.decodeLiteralHeader(src, HPackRepresentation.WITHOUT_INDEXING);
340         assertHeaderEquals(new BasicHeader(":path", "/sample/path"), header);
341         Assert.assertFalse("Decoding completed", src.hasRemaining());
342 
343         Assert.assertEquals(0, dynamicTable.dynamicLength());
344     }
345 
346     @Test
347     public void testLiteralHeaderNeverIndexedDecodingRFC7541Examples() throws Exception {
348 
349         final ByteBuffer src = createByteBuffer(
350                 0x10, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74);
351 
352         final InboundDynamicTable dynamicTable = new InboundDynamicTable();
353         final HPackDecoder decoder = new HPackDecoder(dynamicTable, StandardCharsets.US_ASCII);
354         final Header header = decoder.decodeLiteralHeader(src, HPackRepresentation.NEVER_INDEXED);
355         assertHeaderEquals(new BasicHeader("password", "secret", true), header);
356         Assert.assertFalse("Decoding completed", src.hasRemaining());
357 
358         Assert.assertEquals(0, dynamicTable.dynamicLength());
359     }
360 
361     @Test
362     public void testIndexedHeaderDecodingRFC7541Examples() throws Exception {
363 
364         final ByteBuffer src = createByteBuffer(0x82);
365 
366         final InboundDynamicTable dynamicTable = new InboundDynamicTable();
367         final HPackDecoder decoder = new HPackDecoder(dynamicTable, StandardCharsets.US_ASCII);
368         final Header header = decoder.decodeIndexedHeader(src);
369         assertHeaderEquals(new BasicHeader(":method", "GET"), header);
370         Assert.assertFalse("Decoding completed", src.hasRemaining());
371 
372         Assert.assertEquals(0, dynamicTable.dynamicLength());
373     }
374 
375     @Test
376     public void testRequestDecodingWithoutHuffmanRFC7541Examples() throws Exception {
377 
378         final ByteBuffer src1 = createByteBuffer(
379                 0x82, 0x86, 0x84, 0x41, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e,
380                 0x63, 0x6f, 0x6d);
381 
382         final InboundDynamicTable dynamicTable = new InboundDynamicTable();
383         final HPackDecoder decoder = new HPackDecoder(dynamicTable, StandardCharsets.US_ASCII);
384         final List<Header> headers1 = decoder.decodeHeaders(src1);
385 
386         Assert.assertEquals(4, headers1.size());
387         assertHeaderEquals(new BasicHeader(":method", "GET"), headers1.get(0));
388         assertHeaderEquals(new BasicHeader(":scheme", "http"), headers1.get(1));
389         assertHeaderEquals(new BasicHeader(":path", "/"), headers1.get(2));
390         assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), headers1.get(3));
391 
392         Assert.assertEquals(1, dynamicTable.dynamicLength());
393         assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(0));
394         Assert.assertEquals(57, dynamicTable.getCurrentSize());
395 
396         final ByteBuffer src2 = createByteBuffer(
397                 0x82, 0x86, 0x84, 0xbe, 0x58, 0x08, 0x6e, 0x6f, 0x2d, 0x63, 0x61, 0x63, 0x68, 0x65);
398 
399         final List<Header> headers2 = decoder.decodeHeaders(src2);
400 
401         Assert.assertEquals(5, headers2.size());
402         assertHeaderEquals(new BasicHeader(":method", "GET"), headers2.get(0));
403         assertHeaderEquals(new BasicHeader(":scheme", "http"), headers2.get(1));
404         assertHeaderEquals(new BasicHeader(":path", "/"), headers2.get(2));
405         assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), headers2.get(3));
406         assertHeaderEquals(new BasicHeader("cache-control", "no-cache"), headers2.get(4));
407 
408         Assert.assertEquals(2, dynamicTable.dynamicLength());
409         assertHeaderEquals(new BasicHeader("cache-control", "no-cache"), dynamicTable.getDynamicEntry(0));
410         assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(1));
411         Assert.assertEquals(110, dynamicTable.getCurrentSize());
412 
413         final ByteBuffer src3 = createByteBuffer(
414                 0x82, 0x87, 0x85, 0xbf, 0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79,
415                 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65);
416 
417         final List<Header> headers3 = decoder.decodeHeaders(src3);
418 
419         Assert.assertEquals(5, headers3.size());
420         assertHeaderEquals(new BasicHeader(":method", "GET"), headers3.get(0));
421         assertHeaderEquals(new BasicHeader(":scheme", "https"), headers3.get(1));
422         assertHeaderEquals(new BasicHeader(":path", "/index.html"), headers3.get(2));
423         assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), headers3.get(3));
424         assertHeaderEquals(new BasicHeader("custom-key", "custom-value"), headers3.get(4));
425 
426         Assert.assertEquals(3, dynamicTable.dynamicLength());
427         assertHeaderEquals(new BasicHeader("custom-key", "custom-value"), dynamicTable.getDynamicEntry(0));
428         assertHeaderEquals(new BasicHeader("cache-control", "no-cache"), dynamicTable.getDynamicEntry(1));
429         assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(2));
430         Assert.assertEquals(164, dynamicTable.getCurrentSize());
431     }
432 
433     @Test
434     public void testRequestDecodingWithHuffmanRFC7541Examples() throws Exception {
435 
436         final ByteBuffer src1 = createByteBuffer(
437                 0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff);
438 
439         final InboundDynamicTable dynamicTable = new InboundDynamicTable();
440         final HPackDecoder decoder = new HPackDecoder(dynamicTable, StandardCharsets.US_ASCII);
441         final List<Header> headers1 = decoder.decodeHeaders(src1);
442 
443         Assert.assertEquals(4, headers1.size());
444         assertHeaderEquals(new BasicHeader(":method", "GET"), headers1.get(0));
445         assertHeaderEquals(new BasicHeader(":scheme", "http"), headers1.get(1));
446         assertHeaderEquals(new BasicHeader(":path", "/"), headers1.get(2));
447         assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), headers1.get(3));
448 
449         Assert.assertEquals(1, dynamicTable.dynamicLength());
450         assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(0));
451         Assert.assertEquals(57, dynamicTable.getCurrentSize());
452 
453         final ByteBuffer src2 = createByteBuffer(
454                 0x82, 0x86, 0x84, 0xbe, 0x58, 0x86, 0xa8, 0xeb, 0x10, 0x64, 0x9c, 0xbf);
455 
456         final List<Header> headers2 = decoder.decodeHeaders(src2);
457 
458         Assert.assertEquals(5, headers2.size());
459         assertHeaderEquals(new BasicHeader(":method", "GET"), headers2.get(0));
460         assertHeaderEquals(new BasicHeader(":scheme", "http"), headers2.get(1));
461         assertHeaderEquals(new BasicHeader(":path", "/"), headers2.get(2));
462         assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), headers2.get(3));
463         assertHeaderEquals(new BasicHeader("cache-control", "no-cache"), headers2.get(4));
464 
465         Assert.assertEquals(2, dynamicTable.dynamicLength());
466         assertHeaderEquals(new BasicHeader("cache-control", "no-cache"), dynamicTable.getDynamicEntry(0));
467         assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(1));
468         Assert.assertEquals(110, dynamicTable.getCurrentSize());
469 
470         final ByteBuffer src3 = createByteBuffer(
471                 0x82, 0x87, 0x85, 0xbf, 0x40, 0x88, 0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xa9, 0x7d, 0x7f, 0x89, 0x25,
472                 0xa8, 0x49, 0xe9, 0x5b, 0xb8, 0xe8, 0xb4, 0xbf);
473 
474         final List<Header> headers3 = decoder.decodeHeaders(src3);
475 
476         Assert.assertEquals(5, headers3.size());
477         assertHeaderEquals(new BasicHeader(":method", "GET"), headers3.get(0));
478         assertHeaderEquals(new BasicHeader(":scheme", "https"), headers3.get(1));
479         assertHeaderEquals(new BasicHeader(":path", "/index.html"), headers3.get(2));
480         assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), headers3.get(3));
481         assertHeaderEquals(new BasicHeader("custom-key", "custom-value"), headers3.get(4));
482 
483         Assert.assertEquals(3, dynamicTable.dynamicLength());
484         assertHeaderEquals(new BasicHeader("custom-key", "custom-value"), dynamicTable.getDynamicEntry(0));
485         assertHeaderEquals(new BasicHeader("cache-control", "no-cache"), dynamicTable.getDynamicEntry(1));
486         assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(2));
487         Assert.assertEquals(164, dynamicTable.getCurrentSize());
488     }
489 
490     @Test
491     public void testResponseDecodingWithoutHuffmanRFC7541Examples() throws Exception {
492 
493         final ByteBuffer src1 = createByteBuffer(
494                 0x48, 0x03, 0x33, 0x30, 0x32, 0x58, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x61, 0x1d, 0x4d,
495                 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, 0x4f, 0x63, 0x74, 0x20, 0x32, 0x30, 0x31, 0x33, 0x20, 0x32,
496                 0x30, 0x3a, 0x31, 0x33, 0x3a, 0x32, 0x31, 0x20, 0x47, 0x4d, 0x54, 0x6e, 0x17, 0x68, 0x74, 0x74, 0x70,
497                 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63,
498                 0x6f, 0x6d);
499 
500         final InboundDynamicTable dynamicTable = new InboundDynamicTable();
501         dynamicTable.setMaxSize(256);
502         final HPackDecoder decoder = new HPackDecoder(dynamicTable, StandardCharsets.US_ASCII);
503         final List<Header> headers1 = decoder.decodeHeaders(src1);
504 
505         Assert.assertEquals(4, headers1.size());
506         assertHeaderEquals(new BasicHeader(":status", "302"), headers1.get(0));
507         assertHeaderEquals(new BasicHeader("cache-control", "private"), headers1.get(1));
508         assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), headers1.get(2));
509         assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), headers1.get(3));
510 
511         Assert.assertEquals(4, dynamicTable.dynamicLength());
512         assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), dynamicTable.getDynamicEntry(0));
513         assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), dynamicTable.getDynamicEntry(1));
514         assertHeaderEquals(new BasicHeader("cache-control", "private"), dynamicTable.getDynamicEntry(2));
515         assertHeaderEquals(new BasicHeader(":status", "302"), dynamicTable.getDynamicEntry(3));
516         Assert.assertEquals(222, dynamicTable.getCurrentSize());
517 
518         final ByteBuffer src2 = createByteBuffer(
519                 0x48, 0x03, 0x33, 0x30, 0x37, 0xc1, 0xc0, 0xbf);
520 
521         final List<Header> headers2 = decoder.decodeHeaders(src2);
522 
523         Assert.assertEquals(4, headers2.size());
524         assertHeaderEquals(new BasicHeader(":status", "307"), headers2.get(0));
525         assertHeaderEquals(new BasicHeader("cache-control", "private"), headers2.get(1));
526         assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), headers2.get(2));
527         assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), headers2.get(3));
528 
529         Assert.assertEquals(4, dynamicTable.dynamicLength());
530         assertHeaderEquals(new BasicHeader(":status", "307"), dynamicTable.getDynamicEntry(0));
531         assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), dynamicTable.getDynamicEntry(1));
532         assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), dynamicTable.getDynamicEntry(2));
533         assertHeaderEquals(new BasicHeader("cache-control", "private"), dynamicTable.getDynamicEntry(3));
534 
535         Assert.assertEquals(222, dynamicTable.getCurrentSize());
536 
537         final ByteBuffer src3 = createByteBuffer(
538                 0x88, 0xc1, 0x61, 0x1d, 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, 0x4f, 0x63, 0x74, 0x20, 0x32,
539                 0x30, 0x31, 0x33, 0x20, 0x32, 0x30, 0x3a, 0x31, 0x33, 0x3a, 0x32, 0x32, 0x20, 0x47, 0x4d, 0x54, 0xc0,
540                 0x5a, 0x04, 0x67, 0x7a, 0x69, 0x70, 0x77, 0x38, 0x66, 0x6f, 0x6f, 0x3d, 0x41, 0x53, 0x44, 0x4a, 0x4b,
541                 0x48, 0x51, 0x4b, 0x42, 0x5a, 0x58, 0x4f, 0x51, 0x57, 0x45, 0x4f, 0x50, 0x49, 0x55, 0x41, 0x58, 0x51,
542                 0x57, 0x45, 0x4f, 0x49, 0x55, 0x3b, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, 0x3d, 0x33, 0x36,
543                 0x30, 0x30, 0x3b, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x31);
544 
545         final List<Header> headers3 = decoder.decodeHeaders(src3);
546 
547         Assert.assertEquals(6, headers3.size());
548         assertHeaderEquals(new BasicHeader(":status", "200"), headers3.get(0));
549         assertHeaderEquals(new BasicHeader("cache-control", "private"), headers3.get(1));
550         assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:22 GMT"), headers3.get(2));
551         assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), headers3.get(3));
552         assertHeaderEquals(new BasicHeader("content-encoding", "gzip"), headers3.get(4));
553         assertHeaderEquals(new BasicHeader("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"), headers3.get(5));
554 
555         Assert.assertEquals(3, dynamicTable.dynamicLength());
556         assertHeaderEquals(new BasicHeader("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"), dynamicTable.getDynamicEntry(0));
557         assertHeaderEquals(new BasicHeader("content-encoding", "gzip"), dynamicTable.getDynamicEntry(1));
558         assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:22 GMT"), dynamicTable.getDynamicEntry(2));
559 
560         Assert.assertEquals(215, dynamicTable.getCurrentSize());
561     }
562 
563     @Test
564     public void testResponseDecodingWithHuffmanRFC7541Examples() throws Exception {
565 
566         final ByteBuffer src1 = createByteBuffer(
567                 0x48, 0x82, 0x64, 0x02, 0x58, 0x85, 0xae, 0xc3, 0x77, 0x1a, 0x4b, 0x61, 0x96, 0xd0, 0x7a, 0xbe, 0x94,
568                 0x10, 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, 0x95, 0x04, 0x0b, 0x81, 0x66, 0xe0, 0x82, 0xa6, 0x2d, 0x1b,
569                 0xff, 0x6e, 0x91, 0x9d, 0x29, 0xad, 0x17, 0x18, 0x63, 0xc7, 0x8f, 0x0b, 0x97, 0xc8, 0xe9, 0xae, 0x82,
570                 0xae, 0x43, 0xd3);
571 
572         final InboundDynamicTable dynamicTable = new InboundDynamicTable();
573         dynamicTable.setMaxSize(256);
574         final HPackDecoder decoder = new HPackDecoder(dynamicTable, StandardCharsets.US_ASCII);
575         final List<Header> headers1 = decoder.decodeHeaders(src1);
576 
577         Assert.assertEquals(4, headers1.size());
578         assertHeaderEquals(new BasicHeader(":status", "302"), headers1.get(0));
579         assertHeaderEquals(new BasicHeader("cache-control", "private"), headers1.get(1));
580         assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), headers1.get(2));
581         assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), headers1.get(3));
582 
583         Assert.assertEquals(4, dynamicTable.dynamicLength());
584         assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), dynamicTable.getDynamicEntry(0));
585         assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), dynamicTable.getDynamicEntry(1));
586         assertHeaderEquals(new BasicHeader("cache-control", "private"), dynamicTable.getDynamicEntry(2));
587         assertHeaderEquals(new BasicHeader(":status", "302"), dynamicTable.getDynamicEntry(3));
588         Assert.assertEquals(222, dynamicTable.getCurrentSize());
589 
590         final ByteBuffer src2 = createByteBuffer(
591                 0x48, 0x83, 0x64, 0x0e, 0xff, 0xc1, 0xc0, 0xbf);
592 
593         final List<Header> headers2 = decoder.decodeHeaders(src2);
594 
595         Assert.assertEquals(4, headers2.size());
596         assertHeaderEquals(new BasicHeader(":status", "307"), headers2.get(0));
597         assertHeaderEquals(new BasicHeader("cache-control", "private"), headers2.get(1));
598         assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), headers2.get(2));
599         assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), headers2.get(3));
600 
601         Assert.assertEquals(4, dynamicTable.dynamicLength());
602         assertHeaderEquals(new BasicHeader(":status", "307"), dynamicTable.getDynamicEntry(0));
603         assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), dynamicTable.getDynamicEntry(1));
604         assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), dynamicTable.getDynamicEntry(2));
605         assertHeaderEquals(new BasicHeader("cache-control", "private"), dynamicTable.getDynamicEntry(3));
606 
607         Assert.assertEquals(222, dynamicTable.getCurrentSize());
608 
609         final ByteBuffer src3 = createByteBuffer(
610                 0x88, 0xc1, 0x61, 0x96, 0xd0, 0x7a, 0xbe, 0x94, 0x10, 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, 0x95, 0x04,
611                 0x0b, 0x81, 0x66, 0xe0, 0x84, 0xa6, 0x2d, 0x1b, 0xff, 0xc0, 0x5a, 0x83, 0x9b, 0xd9, 0xab, 0x77, 0xad,
612                 0x94, 0xe7, 0x82, 0x1d, 0xd7, 0xf2, 0xe6, 0xc7, 0xb3, 0x35, 0xdf, 0xdf, 0xcd, 0x5b, 0x39, 0x60, 0xd5,
613                 0xaf, 0x27, 0x08, 0x7f, 0x36, 0x72, 0xc1, 0xab, 0x27, 0x0f, 0xb5, 0x29, 0x1f, 0x95, 0x87, 0x31, 0x60,
614                 0x65, 0xc0, 0x03, 0xed, 0x4e, 0xe5, 0xb1, 0x06, 0x3d, 0x50, 0x07);
615 
616         final List<Header> headers3 = decoder.decodeHeaders(src3);
617 
618         Assert.assertEquals(6, headers3.size());
619         assertHeaderEquals(new BasicHeader(":status", "200"), headers3.get(0));
620         assertHeaderEquals(new BasicHeader("cache-control", "private"), headers3.get(1));
621         assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:22 GMT"), headers3.get(2));
622         assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), headers3.get(3));
623         assertHeaderEquals(new BasicHeader("content-encoding", "gzip"), headers3.get(4));
624         assertHeaderEquals(new BasicHeader("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"), headers3.get(5));
625 
626         Assert.assertEquals(3, dynamicTable.dynamicLength());
627         assertHeaderEquals(new BasicHeader("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"), dynamicTable.getDynamicEntry(0));
628         assertHeaderEquals(new BasicHeader("content-encoding", "gzip"), dynamicTable.getDynamicEntry(1));
629         assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:22 GMT"), dynamicTable.getDynamicEntry(2));
630 
631         Assert.assertEquals(215, dynamicTable.getCurrentSize());
632     }
633 
634     private static byte[] createByteArray(final int... bytes) {
635         final byte[] buffer = new byte[bytes.length];
636         for (int i = 0; i < bytes.length; i++) {
637             buffer[i] = (byte) bytes[i];
638         }
639         return buffer;
640     }
641 
642     @Test
643     public void testLiteralHeaderWithIndexingEncodingRFC7541Examples() throws Exception {
644 
645         final OutboundDynamicTable dynamicTable = new OutboundDynamicTable();
646         final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.US_ASCII);
647 
648         final ByteArrayBuffer buf = new ByteArrayBuffer(128);
649 
650         final Header header = new BasicHeader("custom-key", "custom-header");
651         encoder.encodeLiteralHeader(buf, null, header, HPackRepresentation.WITH_INDEXING, false);
652 
653         final byte[] expected = createByteArray(
654                 0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x0d, 0x63, 0x75, 0x73,
655                 0x74, 0x6f, 0x6d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72);
656 
657         Assert.assertArrayEquals(expected, buf.toByteArray());
658     }
659 
660     @Test
661     public void testLiteralHeaderWithoutIndexingEncodingRFC7541Examples() throws Exception {
662 
663         final OutboundDynamicTable dynamicTable = new OutboundDynamicTable();
664         final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.US_ASCII);
665 
666         final ByteArrayBuffer buf = new ByteArrayBuffer(128);
667 
668         final Header header = new BasicHeader(":path", "/sample/path");
669         encoder.encodeLiteralHeader(buf, new HPackEntry() {
670             @Override
671             public int getIndex() {
672                 return 4;
673             }
674 
675             @Override
676             public HPackHeader getHeader() {
677                 return new HPackHeader(header);
678             }
679         }, header, HPackRepresentation.WITHOUT_INDEXING, false);
680 
681         final byte[] expected = createByteArray(
682                 0x04, 0x0c, 0x2f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68);
683         Assert.assertArrayEquals(expected, buf.toByteArray());
684     }
685 
686     @Test
687     public void testLiteralHeaderNeverIndexedEncodingRFC7541Examples() throws Exception {
688 
689         final OutboundDynamicTable dynamicTable = new OutboundDynamicTable();
690         final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.US_ASCII);
691 
692         final ByteArrayBuffer buf = new ByteArrayBuffer(128);
693 
694         final Header header = new BasicHeader("password", "secret", true);
695         encoder.encodeLiteralHeader(buf, null, header, HPackRepresentation.NEVER_INDEXED, false);
696 
697         final byte[] expected = createByteArray(
698                 0x10, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74);
699         Assert.assertArrayEquals(expected, buf.toByteArray());
700     }
701 
702     @Test
703     public void testIndexedHeaderEncodingRFC7541Examples() throws Exception {
704 
705         final OutboundDynamicTable dynamicTable = new OutboundDynamicTable();
706         final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.US_ASCII);
707 
708         final ByteArrayBuffer buf = new ByteArrayBuffer(128);
709         encoder.encodeIndex(buf, 2);
710 
711         final byte[] expected = createByteArray(0x82);
712         Assert.assertArrayEquals(expected, buf.toByteArray());
713     }
714 
715     @Test
716     public void testRequestEncodingWithoutHuffmanRFC7541Examples() throws Exception {
717 
718         final OutboundDynamicTable dynamicTable = new OutboundDynamicTable();
719         final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.US_ASCII);
720 
721         final ByteArrayBuffer buf = new ByteArrayBuffer(256);
722         final List<Header> headers1 = Arrays.<Header>asList(
723                 new BasicHeader(":method", "GET"),
724                 new BasicHeader(":scheme", "http"),
725                 new BasicHeader(":path", "/"),
726                 new BasicHeader(":authority", "www.example.com"));
727 
728         encoder.encodeHeaders(buf, headers1, false, false);
729 
730         final byte[] expected1 = createByteArray(
731                 0x82, 0x86, 0x84, 0x41, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e,
732                 0x63, 0x6f, 0x6d);
733         Assert.assertArrayEquals(expected1, buf.toByteArray());
734 
735         Assert.assertEquals(1, dynamicTable.dynamicLength());
736         assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(0));
737         Assert.assertEquals(57, dynamicTable.getCurrentSize());
738 
739         final List<Header> headers2 = Arrays.<Header>asList(
740                 new BasicHeader(":method", "GET"),
741                 new BasicHeader(":scheme", "http"),
742                 new BasicHeader(":path", "/"),
743                 new BasicHeader(":authority", "www.example.com"),
744                 new BasicHeader("cache-control", "no-cache"));
745 
746         buf.clear();
747         encoder.encodeHeaders(buf, headers2, false, false);
748 
749         final byte[] expected2 = createByteArray(
750                 0x82, 0x86, 0x84, 0xbe, 0x58, 0x08, 0x6e, 0x6f, 0x2d, 0x63, 0x61, 0x63, 0x68, 0x65);
751         Assert.assertArrayEquals(expected2, buf.toByteArray());
752 
753         Assert.assertEquals(2, dynamicTable.dynamicLength());
754         assertHeaderEquals(new BasicHeader("cache-control", "no-cache"), dynamicTable.getDynamicEntry(0));
755         assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(1));
756         Assert.assertEquals(110, dynamicTable.getCurrentSize());
757 
758         final List<Header> headers3 = Arrays.<Header>asList(
759                 new BasicHeader(":method", "GET"),
760                 new BasicHeader(":scheme", "https"),
761                 new BasicHeader(":path", "/index.html"),
762                 new BasicHeader(":authority", "www.example.com"),
763                 new BasicHeader("custom-key", "custom-value"));
764 
765         buf.clear();
766         encoder.encodeHeaders(buf, headers3, false, false);
767 
768         final byte[] expected3 = createByteArray(
769                 0x82, 0x87, 0x85, 0xbf, 0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79,
770                 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65);
771         Assert.assertArrayEquals(expected3, buf.toByteArray());
772 
773         Assert.assertEquals(3, dynamicTable.dynamicLength());
774         assertHeaderEquals(new BasicHeader("custom-key", "custom-value"), dynamicTable.getDynamicEntry(0));
775         assertHeaderEquals(new BasicHeader("cache-control", "no-cache"), dynamicTable.getDynamicEntry(1));
776         assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(2));
777         Assert.assertEquals(164, dynamicTable.getCurrentSize());
778     }
779 
780     @Test
781     public void testRequestEncodingWithHuffmanRFC7541Examples() throws Exception {
782 
783         final OutboundDynamicTable dynamicTable = new OutboundDynamicTable();
784         final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.US_ASCII);
785 
786         final ByteArrayBuffer buf = new ByteArrayBuffer(256);
787         final List<Header> headers1 = Arrays.<Header>asList(
788                 new BasicHeader(":method", "GET"),
789                 new BasicHeader(":scheme", "http"),
790                 new BasicHeader(":path", "/"),
791                 new BasicHeader(":authority", "www.example.com"));
792 
793         encoder.encodeHeaders(buf, headers1, false, true);
794 
795         final byte[] expected1 = createByteArray(
796                 0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff);
797         Assert.assertArrayEquals(expected1, buf.toByteArray());
798 
799         Assert.assertEquals(1, dynamicTable.dynamicLength());
800         assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(0));
801         Assert.assertEquals(57, dynamicTable.getCurrentSize());
802 
803         final List<Header> headers2 = Arrays.<Header>asList(
804                 new BasicHeader(":method", "GET"),
805                 new BasicHeader(":scheme", "http"),
806                 new BasicHeader(":path", "/"),
807                 new BasicHeader(":authority", "www.example.com"),
808                 new BasicHeader("cache-control", "no-cache"));
809 
810         buf.clear();
811         encoder.encodeHeaders(buf, headers2, false, true);
812 
813         final byte[] expected2 = createByteArray(
814                 0x82, 0x86, 0x84, 0xbe, 0x58, 0x86, 0xa8, 0xeb, 0x10, 0x64, 0x9c, 0xbf);
815         Assert.assertArrayEquals(expected2, buf.toByteArray());
816 
817         Assert.assertEquals(2, dynamicTable.dynamicLength());
818         assertHeaderEquals(new BasicHeader("cache-control", "no-cache"), dynamicTable.getDynamicEntry(0));
819         assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(1));
820         Assert.assertEquals(110, dynamicTable.getCurrentSize());
821 
822         final List<Header> headers3 = Arrays.<Header>asList(
823                 new BasicHeader(":method", "GET"),
824                 new BasicHeader(":scheme", "https"),
825                 new BasicHeader(":path", "/index.html"),
826                 new BasicHeader(":authority", "www.example.com"),
827                 new BasicHeader("custom-key", "custom-value"));
828 
829         buf.clear();
830         encoder.encodeHeaders(buf, headers3, false, true);
831 
832         final byte[] expected3 = createByteArray(
833                 0x82, 0x87, 0x85, 0xbf, 0x40, 0x88, 0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xa9, 0x7d, 0x7f, 0x89, 0x25,
834                 0xa8, 0x49, 0xe9, 0x5b, 0xb8, 0xe8, 0xb4, 0xbf);
835         Assert.assertArrayEquals(expected3, buf.toByteArray());
836 
837         Assert.assertEquals(3, dynamicTable.dynamicLength());
838         assertHeaderEquals(new BasicHeader("custom-key", "custom-value"), dynamicTable.getDynamicEntry(0));
839         assertHeaderEquals(new BasicHeader("cache-control", "no-cache"), dynamicTable.getDynamicEntry(1));
840         assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(2));
841         Assert.assertEquals(164, dynamicTable.getCurrentSize());
842     }
843 
844     @Test
845     public void testResponseEncodingWithoutHuffmanRFC7541Examples() throws Exception {
846 
847         final OutboundDynamicTable dynamicTable = new OutboundDynamicTable();
848         dynamicTable.setMaxSize(256);
849         final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.US_ASCII);
850 
851         final ByteArrayBuffer buf = new ByteArrayBuffer(256);
852         final List<Header> headers1 = Arrays.<Header>asList(
853                 new BasicHeader(":status", "302"),
854                 new BasicHeader("cache-control", "private"),
855                 new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
856                 new BasicHeader("location", "https://www.example.com"));
857 
858         encoder.encodeHeaders(buf, headers1, false, false);
859 
860         final byte[] expected1 = createByteArray(
861                 0x48, 0x03, 0x33, 0x30, 0x32, 0x58, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x61, 0x1d, 0x4d,
862                 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, 0x4f, 0x63, 0x74, 0x20, 0x32, 0x30, 0x31, 0x33, 0x20, 0x32,
863                 0x30, 0x3a, 0x31, 0x33, 0x3a, 0x32, 0x31, 0x20, 0x47, 0x4d, 0x54, 0x6e, 0x17, 0x68, 0x74, 0x74, 0x70,
864                 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63,
865                 0x6f, 0x6d);
866         Assert.assertArrayEquals(expected1, buf.toByteArray());
867 
868         Assert.assertEquals(4, dynamicTable.dynamicLength());
869         assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), dynamicTable.getDynamicEntry(0));
870         assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), dynamicTable.getDynamicEntry(1));
871         assertHeaderEquals(new BasicHeader("cache-control", "private"), dynamicTable.getDynamicEntry(2));
872         assertHeaderEquals(new BasicHeader(":status", "302"), dynamicTable.getDynamicEntry(3));
873         Assert.assertEquals(222, dynamicTable.getCurrentSize());
874 
875         final List<Header> headers2 = Arrays.<Header>asList(
876                 new BasicHeader(":status", "307"),
877                 new BasicHeader("cache-control", "private"),
878                 new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
879                 new BasicHeader("location", "https://www.example.com"));
880 
881         buf.clear();
882         encoder.encodeHeaders(buf, headers2, false, false);
883 
884         final byte[] expected2 = createByteArray(
885                 0x48, 0x03, 0x33, 0x30, 0x37, 0xc1, 0xc0, 0xbf);
886         Assert.assertArrayEquals(expected2, buf.toByteArray());
887 
888         Assert.assertEquals(4, dynamicTable.dynamicLength());
889         assertHeaderEquals(new BasicHeader(":status", "307"), dynamicTable.getDynamicEntry(0));
890         assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), dynamicTable.getDynamicEntry(1));
891         assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), dynamicTable.getDynamicEntry(2));
892         assertHeaderEquals(new BasicHeader("cache-control", "private"), dynamicTable.getDynamicEntry(3));
893 
894         Assert.assertEquals(222, dynamicTable.getCurrentSize());
895 
896         final List<Header> headers3 = Arrays.<Header>asList(
897         new BasicHeader(":status", "200"),
898                 new BasicHeader("cache-control", "private"),
899                 new BasicHeader("date", "Mon, 21 Oct 2013 20:13:22 GMT"),
900                 new BasicHeader("location", "https://www.example.com"),
901                 new BasicHeader("content-encoding", "gzip"),
902                 new BasicHeader("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"));
903 
904         buf.clear();
905         encoder.encodeHeaders(buf, headers3, false, false);
906 
907         final byte[] expected3 = createByteArray(
908                 0x88, 0xc1, 0x61, 0x1d, 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, 0x4f, 0x63, 0x74, 0x20, 0x32,
909                 0x30, 0x31, 0x33, 0x20, 0x32, 0x30, 0x3a, 0x31, 0x33, 0x3a, 0x32, 0x32, 0x20, 0x47, 0x4d, 0x54, 0xc0,
910                 0x5a, 0x04, 0x67, 0x7a, 0x69, 0x70, 0x77, 0x38, 0x66, 0x6f, 0x6f, 0x3d, 0x41, 0x53, 0x44, 0x4a, 0x4b,
911                 0x48, 0x51, 0x4b, 0x42, 0x5a, 0x58, 0x4f, 0x51, 0x57, 0x45, 0x4f, 0x50, 0x49, 0x55, 0x41, 0x58, 0x51,
912                 0x57, 0x45, 0x4f, 0x49, 0x55, 0x3b, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, 0x3d, 0x33, 0x36,
913                 0x30, 0x30, 0x3b, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x31);
914         Assert.assertArrayEquals(expected3, buf.toByteArray());
915 
916         Assert.assertEquals(3, dynamicTable.dynamicLength());
917         assertHeaderEquals(new BasicHeader("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"), dynamicTable.getDynamicEntry(0));
918         assertHeaderEquals(new BasicHeader("content-encoding", "gzip"), dynamicTable.getDynamicEntry(1));
919         assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:22 GMT"), dynamicTable.getDynamicEntry(2));
920 
921         Assert.assertEquals(215, dynamicTable.getCurrentSize());
922     }
923 
924     @Test
925     public void testResponseEncodingWithHuffmanRFC7541Examples() throws Exception {
926 
927         final OutboundDynamicTable dynamicTable = new OutboundDynamicTable();
928         dynamicTable.setMaxSize(256);
929         final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.US_ASCII);
930 
931         final ByteArrayBuffer buf = new ByteArrayBuffer(256);
932         final List<Header> headers1 = Arrays.<Header>asList(
933                 new BasicHeader(":status", "302"),
934                 new BasicHeader("cache-control", "private"),
935                 new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
936                 new BasicHeader("location", "https://www.example.com"));
937 
938         encoder.encodeHeaders(buf, headers1, false, true);
939 
940         final byte[] expected1 = createByteArray(
941                 0x48, 0x82, 0x64, 0x02, 0x58, 0x85, 0xae, 0xc3, 0x77, 0x1a, 0x4b, 0x61, 0x96, 0xd0, 0x7a, 0xbe, 0x94,
942                 0x10, 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, 0x95, 0x04, 0x0b, 0x81, 0x66, 0xe0, 0x82, 0xa6, 0x2d, 0x1b,
943                 0xff, 0x6e, 0x91, 0x9d, 0x29, 0xad, 0x17, 0x18, 0x63, 0xc7, 0x8f, 0x0b, 0x97, 0xc8, 0xe9, 0xae, 0x82,
944                 0xae, 0x43, 0xd3);
945         Assert.assertArrayEquals(expected1, buf.toByteArray());
946 
947         Assert.assertEquals(4, dynamicTable.dynamicLength());
948         assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), dynamicTable.getDynamicEntry(0));
949         assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), dynamicTable.getDynamicEntry(1));
950         assertHeaderEquals(new BasicHeader("cache-control", "private"), dynamicTable.getDynamicEntry(2));
951         assertHeaderEquals(new BasicHeader(":status", "302"), dynamicTable.getDynamicEntry(3));
952         Assert.assertEquals(222, dynamicTable.getCurrentSize());
953 
954         final List<Header> headers2 = Arrays.<Header>asList(
955                 new BasicHeader(":status", "307"),
956                 new BasicHeader("cache-control", "private"),
957                 new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
958                 new BasicHeader("location", "https://www.example.com"));
959 
960         buf.clear();
961         encoder.encodeHeaders(buf, headers2, false, true);
962 
963         final byte[] expected2 = createByteArray(
964                 0x48, 0x83, 0x64, 0x0e, 0xff, 0xc1, 0xc0, 0xbf);
965         Assert.assertArrayEquals(expected2, buf.toByteArray());
966 
967         Assert.assertEquals(4, dynamicTable.dynamicLength());
968         assertHeaderEquals(new BasicHeader(":status", "307"), dynamicTable.getDynamicEntry(0));
969         assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), dynamicTable.getDynamicEntry(1));
970         assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), dynamicTable.getDynamicEntry(2));
971         assertHeaderEquals(new BasicHeader("cache-control", "private"), dynamicTable.getDynamicEntry(3));
972 
973         Assert.assertEquals(222, dynamicTable.getCurrentSize());
974 
975         final List<Header> headers3 = Arrays.<Header>asList(
976                 new BasicHeader(":status", "200"),
977                 new BasicHeader("cache-control", "private"),
978                 new BasicHeader("date", "Mon, 21 Oct 2013 20:13:22 GMT"),
979                 new BasicHeader("location", "https://www.example.com"),
980                 new BasicHeader("content-encoding", "gzip"),
981                 new BasicHeader("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"));
982 
983         buf.clear();
984         encoder.encodeHeaders(buf, headers3, false, true);
985 
986         final byte[] expected3 = createByteArray(
987                 0x88, 0xc1, 0x61, 0x96, 0xd0, 0x7a, 0xbe, 0x94, 0x10, 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, 0x95, 0x04,
988                 0x0b, 0x81, 0x66, 0xe0, 0x84, 0xa6, 0x2d, 0x1b, 0xff, 0xc0, 0x5a, 0x83, 0x9b, 0xd9, 0xab, 0x77, 0xad,
989                 0x94, 0xe7, 0x82, 0x1d, 0xd7, 0xf2, 0xe6, 0xc7, 0xb3, 0x35, 0xdf, 0xdf, 0xcd, 0x5b, 0x39, 0x60, 0xd5,
990                 0xaf, 0x27, 0x08, 0x7f, 0x36, 0x72, 0xc1, 0xab, 0x27, 0x0f, 0xb5, 0x29, 0x1f, 0x95, 0x87, 0x31, 0x60,
991                 0x65, 0xc0, 0x03, 0xed, 0x4e, 0xe5, 0xb1, 0x06, 0x3d, 0x50, 0x07);
992         Assert.assertArrayEquals(expected3, buf.toByteArray());
993 
994         Assert.assertEquals(3, dynamicTable.dynamicLength());
995         assertHeaderEquals(new BasicHeader("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"), dynamicTable.getDynamicEntry(0));
996         assertHeaderEquals(new BasicHeader("content-encoding", "gzip"), dynamicTable.getDynamicEntry(1));
997         assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:22 GMT"), dynamicTable.getDynamicEntry(2));
998 
999         Assert.assertEquals(215, dynamicTable.getCurrentSize());
1000     }
1001 
1002     @Test
1003     public void testHeaderEntrySizeNonAscii() throws Exception {
1004 
1005         final ByteArrayBuffer buffer = new ByteArrayBuffer(128);
1006         final Header header = new BasicHeader("hello", constructHelloString(SWISS_GERMAN_HELLO, 1));
1007 
1008         final OutboundDynamicTable outboundTable1 = new OutboundDynamicTable();
1009         final HPackEncoder encoder1 = new HPackEncoder(outboundTable1, StandardCharsets.ISO_8859_1);
1010         final InboundDynamicTable inboundTable1 = new InboundDynamicTable();
1011         final HPackDecoder decoder1 = new HPackDecoder(inboundTable1, StandardCharsets.ISO_8859_1);
1012 
1013         encoder1.setMaxTableSize(48);
1014         decoder1.setMaxTableSize(48);
1015 
1016         encoder1.encodeHeader(buffer, header);
1017         assertHeaderEquals(header, decoder1.decodeHeader(wrap(buffer)));
1018 
1019         Assert.assertEquals(1, outboundTable1.dynamicLength());
1020         Assert.assertEquals(1, inboundTable1.dynamicLength());
1021 
1022         assertHeaderEquals(header, outboundTable1.getDynamicEntry(0));
1023         assertHeaderEquals(header, inboundTable1.getDynamicEntry(0));
1024 
1025         buffer.clear();
1026 
1027         final OutboundDynamicTable outboundTable2 = new OutboundDynamicTable();
1028         final HPackEncoder encoder2 = new HPackEncoder(outboundTable2, StandardCharsets.UTF_8);
1029         final InboundDynamicTable inboundTable2 = new InboundDynamicTable();
1030         final HPackDecoder decoder2 = new HPackDecoder(inboundTable2, StandardCharsets.UTF_8);
1031 
1032         encoder2.setMaxTableSize(48);
1033         decoder2.setMaxTableSize(48);
1034 
1035         encoder2.encodeHeader(buffer, header);
1036         assertHeaderEquals(header, decoder2.decodeHeader(wrap(buffer)));
1037 
1038         Assert.assertEquals(0, outboundTable2.dynamicLength());
1039         Assert.assertEquals(0, inboundTable2.dynamicLength());
1040     }
1041 
1042     @Test(expected = HeaderListConstraintException.class)
1043     public void testHeaderSizeLimit() throws Exception {
1044 
1045         final HPackEncoder encoder = new HPackEncoder(StandardCharsets.US_ASCII);
1046         final HPackDecoder decoder = new HPackDecoder(StandardCharsets.US_ASCII);
1047 
1048         final ByteArrayBuffer buf = new ByteArrayBuffer(128);
1049 
1050         encoder.encodeHeaders(buf,
1051                 Arrays.asList(
1052                         new BasicHeader("regular-header", "blah"),
1053                         new BasicHeader("big-f-header", "12345678901234567890123456789012345678901234567890" +
1054                                 "123456789012345678901234567890123456789012345678901234567890")),
1055                 false);
1056 
1057         MatcherAssert.assertThat(decoder.decodeHeaders(wrap(buf)).size(), CoreMatchers.equalTo(2));
1058 
1059         decoder.setMaxListSize(1000000);
1060         MatcherAssert.assertThat(decoder.decodeHeaders(wrap(buf)).size(), CoreMatchers.equalTo(2));
1061 
1062         decoder.setMaxListSize(200);
1063         decoder.decodeHeaders(wrap(buf));
1064     }
1065 
1066 }
1067