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