1/*
2 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23package jdk.incubator.http.internal.hpack;
24
25import org.testng.annotations.Test;
26
27import java.io.UncheckedIOException;
28import java.net.ProtocolException;
29import java.nio.ByteBuffer;
30import java.util.Iterator;
31import java.util.LinkedList;
32import java.util.List;
33import java.util.function.Supplier;
34import java.util.stream.Collectors;
35
36import static org.testng.Assert.assertEquals;
37import static org.testng.Assert.assertNotNull;
38import static jdk.incubator.http.internal.hpack.TestHelper.*;
39
40//
41// Tests whose names start with "testX" are the ones captured from real HPACK
42// use cases
43//
44public final class DecoderTest {
45
46    //
47    // http://tools.ietf.org/html/rfc7541#appendix-C.2.1
48    //
49    @Test
50    public void example1() {
51        // @formatter:off
52        test("400a 6375 7374 6f6d 2d6b 6579 0d63 7573\n" +
53             "746f 6d2d 6865 6164 6572",
54
55             "[  1] (s =  55) custom-key: custom-header\n" +
56             "      Table size:  55",
57
58             "custom-key: custom-header");
59        // @formatter:on
60    }
61
62    //
63    // http://tools.ietf.org/html/rfc7541#appendix-C.2.2
64    //
65    @Test
66    public void example2() {
67        // @formatter:off
68        test("040c 2f73 616d 706c 652f 7061 7468",
69             "empty.",
70             ":path: /sample/path");
71        // @formatter:on
72    }
73
74    //
75    // http://tools.ietf.org/html/rfc7541#appendix-C.2.3
76    //
77    @Test
78    public void example3() {
79        // @formatter:off
80        test("1008 7061 7373 776f 7264 0673 6563 7265\n" +
81             "74",
82             "empty.",
83             "password: secret");
84        // @formatter:on
85    }
86
87    //
88    // http://tools.ietf.org/html/rfc7541#appendix-C.2.4
89    //
90    @Test
91    public void example4() {
92        // @formatter:off
93        test("82",
94             "empty.",
95             ":method: GET");
96        // @formatter:on
97    }
98
99    //
100    // http://tools.ietf.org/html/rfc7541#appendix-C.3
101    //
102    @Test
103    public void example5() {
104        // @formatter:off
105        Decoder d = new Decoder(256);
106
107        test(d, "8286 8441 0f77 7777 2e65 7861 6d70 6c65\n" +
108                "2e63 6f6d",
109
110                "[  1] (s =  57) :authority: www.example.com\n" +
111                "      Table size:  57",
112
113                ":method: GET\n" +
114                ":scheme: http\n" +
115                ":path: /\n" +
116                ":authority: www.example.com");
117
118        test(d, "8286 84be 5808 6e6f 2d63 6163 6865",
119
120                "[  1] (s =  53) cache-control: no-cache\n" +
121                "[  2] (s =  57) :authority: www.example.com\n" +
122                "      Table size: 110",
123
124                ":method: GET\n" +
125                ":scheme: http\n" +
126                ":path: /\n" +
127                ":authority: www.example.com\n" +
128                "cache-control: no-cache");
129
130        test(d, "8287 85bf 400a 6375 7374 6f6d 2d6b 6579\n" +
131                "0c63 7573 746f 6d2d 7661 6c75 65",
132
133                "[  1] (s =  54) custom-key: custom-value\n" +
134                "[  2] (s =  53) cache-control: no-cache\n" +
135                "[  3] (s =  57) :authority: www.example.com\n" +
136                "      Table size: 164",
137
138                ":method: GET\n" +
139                ":scheme: https\n" +
140                ":path: /index.html\n" +
141                ":authority: www.example.com\n" +
142                "custom-key: custom-value");
143
144        // @formatter:on
145    }
146
147    @Test
148    public void example5AllSplits() {
149        // @formatter:off
150        testAllSplits(
151                "8286 8441 0f77 7777 2e65 7861 6d70 6c65\n" +
152                "2e63 6f6d",
153
154                "[  1] (s =  57) :authority: www.example.com\n" +
155                "      Table size:  57",
156
157                ":method: GET\n" +
158                ":scheme: http\n" +
159                ":path: /\n" +
160                ":authority: www.example.com");
161        // @formatter:on
162    }
163
164    //
165    // http://tools.ietf.org/html/rfc7541#appendix-C.4
166    //
167    @Test
168    public void example6() {
169        // @formatter:off
170        Decoder d = new Decoder(256);
171
172        test(d, "8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4\n" +
173                "ff",
174
175                "[  1] (s =  57) :authority: www.example.com\n" +
176                "      Table size:  57",
177
178                ":method: GET\n" +
179                ":scheme: http\n" +
180                ":path: /\n" +
181                ":authority: www.example.com");
182
183        test(d, "8286 84be 5886 a8eb 1064 9cbf",
184
185                "[  1] (s =  53) cache-control: no-cache\n" +
186                "[  2] (s =  57) :authority: www.example.com\n" +
187                "      Table size: 110",
188
189                ":method: GET\n" +
190                ":scheme: http\n" +
191                ":path: /\n" +
192                ":authority: www.example.com\n" +
193                "cache-control: no-cache");
194
195        test(d, "8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925\n" +
196                "a849 e95b b8e8 b4bf",
197
198                "[  1] (s =  54) custom-key: custom-value\n" +
199                "[  2] (s =  53) cache-control: no-cache\n" +
200                "[  3] (s =  57) :authority: www.example.com\n" +
201                "      Table size: 164",
202
203                ":method: GET\n" +
204                ":scheme: https\n" +
205                ":path: /index.html\n" +
206                ":authority: www.example.com\n" +
207                "custom-key: custom-value");
208        // @formatter:on
209    }
210
211    //
212    // http://tools.ietf.org/html/rfc7541#appendix-C.5
213    //
214    @Test
215    public void example7() {
216        // @formatter:off
217        Decoder d = new Decoder(256);
218
219        test(d, "4803 3330 3258 0770 7269 7661 7465 611d\n" +
220                "4d6f 6e2c 2032 3120 4f63 7420 3230 3133\n" +
221                "2032 303a 3133 3a32 3120 474d 546e 1768\n" +
222                "7474 7073 3a2f 2f77 7777 2e65 7861 6d70\n" +
223                "6c65 2e63 6f6d",
224
225                "[  1] (s =  63) location: https://www.example.com\n" +
226                "[  2] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
227                "[  3] (s =  52) cache-control: private\n" +
228                "[  4] (s =  42) :status: 302\n" +
229                "      Table size: 222",
230
231                ":status: 302\n" +
232                "cache-control: private\n" +
233                "date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
234                "location: https://www.example.com");
235
236        test(d, "4803 3330 37c1 c0bf",
237
238                "[  1] (s =  42) :status: 307\n" +
239                "[  2] (s =  63) location: https://www.example.com\n" +
240                "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
241                "[  4] (s =  52) cache-control: private\n" +
242                "      Table size: 222",
243
244                ":status: 307\n" +
245                "cache-control: private\n" +
246                "date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
247                "location: https://www.example.com");
248
249        test(d, "88c1 611d 4d6f 6e2c 2032 3120 4f63 7420\n" +
250                "3230 3133 2032 303a 3133 3a32 3220 474d\n" +
251                "54c0 5a04 677a 6970 7738 666f 6f3d 4153\n" +
252                "444a 4b48 514b 425a 584f 5157 454f 5049\n" +
253                "5541 5851 5745 4f49 553b 206d 6178 2d61\n" +
254                "6765 3d33 3630 303b 2076 6572 7369 6f6e\n" +
255                "3d31",
256
257                "[  1] (s =  98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\n" +
258                "[  2] (s =  52) content-encoding: gzip\n" +
259                "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
260                "      Table size: 215",
261
262                ":status: 200\n" +
263                "cache-control: private\n" +
264                "date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
265                "location: https://www.example.com\n" +
266                "content-encoding: gzip\n" +
267                "set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1");
268        // @formatter:on
269    }
270
271    //
272    // http://tools.ietf.org/html/rfc7541#appendix-C.6
273    //
274    @Test
275    public void example8() {
276        // @formatter:off
277        Decoder d = new Decoder(256);
278
279        test(d, "4882 6402 5885 aec3 771a 4b61 96d0 7abe\n" +
280                "9410 54d4 44a8 2005 9504 0b81 66e0 82a6\n" +
281                "2d1b ff6e 919d 29ad 1718 63c7 8f0b 97c8\n" +
282                "e9ae 82ae 43d3",
283
284                "[  1] (s =  63) location: https://www.example.com\n" +
285                "[  2] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
286                "[  3] (s =  52) cache-control: private\n" +
287                "[  4] (s =  42) :status: 302\n" +
288                "      Table size: 222",
289
290                ":status: 302\n" +
291                "cache-control: private\n" +
292                "date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
293                "location: https://www.example.com");
294
295        test(d, "4883 640e ffc1 c0bf",
296
297                "[  1] (s =  42) :status: 307\n" +
298                "[  2] (s =  63) location: https://www.example.com\n" +
299                "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
300                "[  4] (s =  52) cache-control: private\n" +
301                "      Table size: 222",
302
303                ":status: 307\n" +
304                "cache-control: private\n" +
305                "date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
306                "location: https://www.example.com");
307
308        test(d, "88c1 6196 d07a be94 1054 d444 a820 0595\n" +
309                "040b 8166 e084 a62d 1bff c05a 839b d9ab\n" +
310                "77ad 94e7 821d d7f2 e6c7 b335 dfdf cd5b\n" +
311                "3960 d5af 2708 7f36 72c1 ab27 0fb5 291f\n" +
312                "9587 3160 65c0 03ed 4ee5 b106 3d50 07",
313
314                "[  1] (s =  98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\n" +
315                "[  2] (s =  52) content-encoding: gzip\n" +
316                "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
317                "      Table size: 215",
318
319                ":status: 200\n" +
320                "cache-control: private\n" +
321                "date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
322                "location: https://www.example.com\n" +
323                "content-encoding: gzip\n" +
324                "set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1");
325        // @formatter:on
326    }
327
328    @Test
329    // One of responses from Apache Server that helped to catch a bug
330    public void testX() {
331        Decoder d = new Decoder(4096);
332        // @formatter:off
333        test(d, "3fe1 1f88 6196 d07a be94 03ea 693f 7504\n" +
334                "00b6 a05c b827 2e32 fa98 b46f 769e 86b1\n" +
335                "9272 b025 da5c 2ea9 fd70 a8de 7fb5 3556\n" +
336                "5ab7 6ece c057 02e2 2ad2 17bf 6c96 d07a\n" +
337                "be94 0854 cb6d 4a08 0075 40bd 71b6 6e05\n" +
338                "a531 68df 0f13 8efe 4522 cd32 21b6 5686\n" +
339                "eb23 781f cf52 848f d24a 8f0f 0d02 3435\n" +
340                "5f87 497c a589 d34d 1f",
341
342                "[  1] (s =  53) content-type: text/html\n" +
343                "[  2] (s =  50) accept-ranges: bytes\n" +
344                "[  3] (s =  74) last-modified: Mon, 11 Jun 2007 18:53:14 GMT\n" +
345                "[  4] (s =  77) server: Apache/2.4.17 (Unix) OpenSSL/1.0.2e-dev\n" +
346                "[  5] (s =  65) date: Mon, 09 Nov 2015 16:26:39 GMT\n" +
347                "      Table size: 319",
348
349                ":status: 200\n" +
350                "date: Mon, 09 Nov 2015 16:26:39 GMT\n" +
351                "server: Apache/2.4.17 (Unix) OpenSSL/1.0.2e-dev\n" +
352                "last-modified: Mon, 11 Jun 2007 18:53:14 GMT\n" +
353                "etag: \"2d-432a5e4a73a80\"\n" +
354                "accept-ranges: bytes\n" +
355                "content-length: 45\n" +
356                "content-type: text/html");
357        // @formatter:on
358    }
359
360    @Test
361    public void testX1() {
362        // Supplier of a decoder with a particular state
363        Supplier<Decoder> s = () -> {
364            Decoder d = new Decoder(4096);
365            // @formatter:off
366            test(d, "88 76 92 ca 54 a7 d7 f4 fa ec af ed 6d da 61 d7 bb 1e ad ff" +
367                    "df 61 97 c3 61 be 94 13 4a 65 b6 a5 04 00 b8 a0 5a b8 db 77" +
368                    "1b 71 4c 5a 37 ff 0f 0d 84 08 00 00 03",
369
370                    "[  1] (s =  65) date: Fri, 24 Jun 2016 14:55:56 GMT\n" +
371                    "[  2] (s =  59) server: Jetty(9.3.z-SNAPSHOT)\n" +
372                    "      Table size: 124",
373
374                    ":status: 200\n" +
375                    "server: Jetty(9.3.z-SNAPSHOT)\n" +
376                    "date: Fri, 24 Jun 2016 14:55:56 GMT\n" +
377                    "content-length: 100000"
378            );
379            // @formatter:on
380            return d;
381        };
382        // For all splits of the following data fed to the supplied decoder we
383        // must get what's expected
384        // @formatter:off
385        testAllSplits(s,
386                "88 bf be 0f 0d 84 08 00 00 03",
387
388                "[  1] (s =  65) date: Fri, 24 Jun 2016 14:55:56 GMT\n" +
389                "[  2] (s =  59) server: Jetty(9.3.z-SNAPSHOT)\n" +
390                "      Table size: 124",
391
392                ":status: 200\n" +
393                "server: Jetty(9.3.z-SNAPSHOT)\n" +
394                "date: Fri, 24 Jun 2016 14:55:56 GMT\n" +
395                "content-length: 100000");
396        // @formatter:on
397    }
398
399    //
400    // This test is missing in the spec
401    //
402    @Test
403    public void sizeUpdate() {
404        Decoder d = new Decoder(4096);
405        assertEquals(d.getTable().maxSize(), 4096);
406        d.decode(ByteBuffer.wrap(new byte[]{0b00111110}), true, nopCallback()); // newSize = 30
407        assertEquals(d.getTable().maxSize(), 30);
408    }
409
410    @Test
411    public void incorrectSizeUpdate() {
412        ByteBuffer b = ByteBuffer.allocate(8);
413        Encoder e = new Encoder(8192) {
414            @Override
415            protected int calculateCapacity(int maxCapacity) {
416                return maxCapacity;
417            }
418        };
419        e.header("a", "b");
420        e.encode(b);
421        b.flip();
422        {
423            Decoder d = new Decoder(4096);
424            UncheckedIOException ex = assertVoidThrows(UncheckedIOException.class,
425                    () -> d.decode(b, true, (name, value) -> { }));
426
427            assertNotNull(ex.getCause());
428            assertEquals(ex.getCause().getClass(), ProtocolException.class);
429        }
430        b.flip();
431        {
432            Decoder d = new Decoder(4096);
433            UncheckedIOException ex = assertVoidThrows(UncheckedIOException.class,
434                    () -> d.decode(b, false, (name, value) -> { }));
435
436            assertNotNull(ex.getCause());
437            assertEquals(ex.getCause().getClass(), ProtocolException.class);
438        }
439    }
440
441    @Test
442    public void corruptedHeaderBlockInteger() {
443        Decoder d = new Decoder(4096);
444        ByteBuffer data = ByteBuffer.wrap(new byte[]{
445                (byte) 0b11111111, // indexed
446                (byte) 0b10011010  // 25 + ...
447        });
448        UncheckedIOException e = assertVoidThrows(UncheckedIOException.class,
449                () -> d.decode(data, true, nopCallback()));
450        assertNotNull(e.getCause());
451        assertEquals(e.getCause().getClass(), ProtocolException.class);
452        assertExceptionMessageContains(e, "Unexpected end of header block");
453    }
454
455    // 5.1.  Integer Representation
456    // ...
457    // Integer encodings that exceed implementation limits -- in value or octet
458    // length -- MUST be treated as decoding errors. Different limits can
459    // be set for each of the different uses of integers, based on
460    // implementation constraints.
461    @Test
462    public void headerBlockIntegerNoOverflow() {
463        Decoder d = new Decoder(4096);
464        ByteBuffer data = ByteBuffer.wrap(new byte[]{
465                (byte) 0b11111111, // indexed + 127
466                // Integer.MAX_VALUE - 127 (base 128, little-endian):
467                (byte) 0b10000000,
468                (byte) 0b11111111,
469                (byte) 0b11111111,
470                (byte) 0b11111111,
471                (byte) 0b00000111
472        });
473
474        IllegalArgumentException e = assertVoidThrows(IllegalArgumentException.class,
475                () -> d.decode(data, true, nopCallback()));
476
477        assertExceptionMessageContains(e, "index=2147483647");
478    }
479
480    @Test
481    public void headerBlockIntegerOverflow() {
482        Decoder d = new Decoder(4096);
483        ByteBuffer data = ByteBuffer.wrap(new byte[]{
484                (byte) 0b11111111, // indexed + 127
485                // Integer.MAX_VALUE - 127 + 1 (base 128, little endian):
486                (byte) 0b10000001,
487                (byte) 0b11111111,
488                (byte) 0b11111111,
489                (byte) 0b11111111,
490                (byte) 0b00000111
491        });
492
493        IllegalArgumentException e = assertVoidThrows(IllegalArgumentException.class,
494                () -> d.decode(data, true, nopCallback()));
495
496        assertExceptionMessageContains(e, "Integer overflow");
497    }
498
499    @Test
500    public void corruptedHeaderBlockString1() {
501        Decoder d = new Decoder(4096);
502        ByteBuffer data = ByteBuffer.wrap(new byte[]{
503                0b00001111, // literal, index=15
504                0b00000000,
505                0b00001000, // huffman=false, length=8
506                0b00000000, // \
507                0b00000000, //  but only 3 octets available...
508                0b00000000  // /
509        });
510        UncheckedIOException e = assertVoidThrows(UncheckedIOException.class,
511                () -> d.decode(data, true, nopCallback()));
512        assertNotNull(e.getCause());
513        assertEquals(e.getCause().getClass(), ProtocolException.class);
514        assertExceptionMessageContains(e, "Unexpected end of header block");
515    }
516
517    @Test
518    public void corruptedHeaderBlockString2() {
519        Decoder d = new Decoder(4096);
520        ByteBuffer data = ByteBuffer.wrap(new byte[]{
521                0b00001111, // literal, index=15
522                0b00000000,
523                (byte) 0b10001000, // huffman=true, length=8
524                0b00000000, // \
525                0b00000000, //  \
526                0b00000000, //   but only 5 octets available...
527                0b00000000, //  /
528                0b00000000  // /
529        });
530        UncheckedIOException e = assertVoidThrows(UncheckedIOException.class,
531                () -> d.decode(data, true, nopCallback()));
532        assertNotNull(e.getCause());
533        assertEquals(e.getCause().getClass(), ProtocolException.class);
534        assertExceptionMessageContains(e, "Unexpected end of header block");
535    }
536
537    // 5.2.  String Literal Representation
538    // ...A Huffman-encoded string literal containing the EOS symbol MUST be
539    // treated as a decoding error...
540    @Test
541    public void corruptedHeaderBlockHuffmanStringEOS() {
542        Decoder d = new Decoder(4096);
543        ByteBuffer data = ByteBuffer.wrap(new byte[]{
544                0b00001111, // literal, index=15
545                0b00000000,
546                (byte) 0b10000110, // huffman=true, length=6
547                0b00011001, 0b01001101, (byte) 0b11111111,
548                (byte) 0b11111111, (byte) 0b11111111, (byte) 0b11111100
549        });
550        IllegalArgumentException e = assertVoidThrows(IllegalArgumentException.class,
551                () -> d.decode(data, true, nopCallback()));
552
553        assertExceptionMessageContains(e, "Encountered EOS");
554    }
555
556    // 5.2.  String Literal Representation
557    // ...A padding strictly longer than 7 bits MUST be treated as a decoding
558    // error...
559    @Test
560    public void corruptedHeaderBlockHuffmanStringLongPadding1() {
561        Decoder d = new Decoder(4096);
562        ByteBuffer data = ByteBuffer.wrap(new byte[]{
563                0b00001111, // literal, index=15
564                0b00000000,
565                (byte) 0b10000011, // huffman=true, length=3
566                0b00011001, 0b01001101, (byte) 0b11111111
567                // len("aei") + len(padding) = (5 + 5 + 5) + (9)
568        });
569        IllegalArgumentException e = assertVoidThrows(IllegalArgumentException.class,
570                () -> d.decode(data, true, nopCallback()));
571
572        assertExceptionMessageContains(e, "Padding is too long", "len=9");
573    }
574
575    @Test
576    public void corruptedHeaderBlockHuffmanStringLongPadding2() {
577        Decoder d = new Decoder(4096);
578        ByteBuffer data = ByteBuffer.wrap(new byte[]{
579                0b00001111, // literal, index=15
580                0b00000000,
581                (byte) 0b10000011, // huffman=true, length=3
582                0b00011001, 0b01111010, (byte) 0b11111111
583                // len("aek") + len(padding) = (5 + 5 + 7) + (7)
584        });
585        assertVoidDoesNotThrow(() -> d.decode(data, true, nopCallback()));
586    }
587
588    // 5.2.  String Literal Representation
589    // ...A padding not corresponding to the most significant bits of the code
590    // for the EOS symbol MUST be treated as a decoding error...
591    @Test
592    public void corruptedHeaderBlockHuffmanStringNotEOSPadding() {
593        Decoder d = new Decoder(4096);
594        ByteBuffer data = ByteBuffer.wrap(new byte[]{
595                0b00001111, // literal, index=15
596                0b00000000,
597                (byte) 0b10000011, // huffman=true, length=3
598                0b00011001, 0b01111010, (byte) 0b11111110
599        });
600        IllegalArgumentException e = assertVoidThrows(IllegalArgumentException.class,
601                () -> d.decode(data, true, nopCallback()));
602
603        assertExceptionMessageContains(e, "Not a EOS prefix");
604    }
605
606    @Test
607    public void argsTestBiConsumerIsNull() {
608        Decoder decoder = new Decoder(4096);
609        assertVoidThrows(NullPointerException.class,
610                () -> decoder.decode(ByteBuffer.allocate(16), true, null));
611    }
612
613    @Test
614    public void argsTestByteBufferIsNull() {
615        Decoder decoder = new Decoder(4096);
616        assertVoidThrows(NullPointerException.class,
617                () -> decoder.decode(null, true, nopCallback()));
618    }
619
620    @Test
621    public void argsTestBothAreNull() {
622        Decoder decoder = new Decoder(4096);
623        assertVoidThrows(NullPointerException.class,
624                () -> decoder.decode(null, true, null));
625    }
626
627    private static void test(String hexdump,
628                             String headerTable, String headerList) {
629        test(new Decoder(4096), hexdump, headerTable, headerList);
630    }
631
632    private static void testAllSplits(String hexdump,
633                                      String expectedHeaderTable,
634                                      String expectedHeaderList) {
635        testAllSplits(() -> new Decoder(256), hexdump, expectedHeaderTable, expectedHeaderList);
636    }
637
638    private static void testAllSplits(Supplier<Decoder> supplier, String hexdump,
639                                      String expectedHeaderTable, String expectedHeaderList) {
640        ByteBuffer source = SpecHelper.toBytes(hexdump);
641
642        BuffersTestingKit.forEachSplit(source, iterable -> {
643            List<String> actual = new LinkedList<>();
644            Iterator<? extends ByteBuffer> i = iterable.iterator();
645            if (!i.hasNext()) {
646                return;
647            }
648            Decoder d = supplier.get();
649            do {
650                ByteBuffer n = i.next();
651                d.decode(n, !i.hasNext(), (name, value) -> {
652                    if (value == null) {
653                        actual.add(name.toString());
654                    } else {
655                        actual.add(name + ": " + value);
656                    }
657                });
658            } while (i.hasNext());
659            assertEquals(d.getTable().getStateString(), expectedHeaderTable);
660            assertEquals(actual.stream().collect(Collectors.joining("\n")), expectedHeaderList);
661        });
662    }
663
664    //
665    // Sometimes we need to keep the same decoder along several runs,
666    // as it models the same connection
667    //
668    private static void test(Decoder d, String hexdump,
669                             String expectedHeaderTable, String expectedHeaderList) {
670
671        ByteBuffer source = SpecHelper.toBytes(hexdump);
672
673        List<String> actual = new LinkedList<>();
674        d.decode(source, true, (name, value) -> {
675            if (value == null) {
676                actual.add(name.toString());
677            } else {
678                actual.add(name + ": " + value);
679            }
680        });
681
682        assertEquals(d.getTable().getStateString(), expectedHeaderTable);
683        assertEquals(actual.stream().collect(Collectors.joining("\n")), expectedHeaderList);
684    }
685
686    private static DecodingCallback nopCallback() {
687        return (t, u) -> { };
688    }
689}
690