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