1/*
2 * Copyright (c) 2014, 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.nio.Buffer;
28import java.nio.ByteBuffer;
29import java.util.ArrayList;
30import java.util.Collections;
31import java.util.Iterator;
32import java.util.LinkedList;
33import java.util.List;
34import java.util.function.Consumer;
35import java.util.function.Function;
36
37import static jdk.incubator.http.internal.hpack.BuffersTestingKit.concat;
38import static jdk.incubator.http.internal.hpack.BuffersTestingKit.forEachSplit;
39import static jdk.incubator.http.internal.hpack.SpecHelper.toHexdump;
40import static jdk.incubator.http.internal.hpack.TestHelper.assertVoidThrows;
41import static java.util.Arrays.asList;
42import static org.testng.Assert.assertEquals;
43import static org.testng.Assert.assertTrue;
44
45// TODO: map textual representation of commands from the spec to actual
46// calls to encoder (actually, this is a good idea for decoder as well)
47public final class EncoderTest {
48
49    //
50    // http://tools.ietf.org/html/rfc7541#appendix-C.2.1
51    //
52    @Test
53    public void example1() {
54
55        Encoder e = newCustomEncoder(256);
56        drainInitialUpdate(e);
57
58        e.literalWithIndexing("custom-key", false, "custom-header", false);
59        // @formatter:off
60        test(e,
61
62             "400a 6375 7374 6f6d 2d6b 6579 0d63 7573\n" +
63             "746f 6d2d 6865 6164 6572",
64
65             "[  1] (s =  55) custom-key: custom-header\n" +
66             "      Table size:  55");
67        // @formatter:on
68    }
69
70    //
71    // http://tools.ietf.org/html/rfc7541#appendix-C.2.2
72    //
73    @Test
74    public void example2() {
75
76        Encoder e = newCustomEncoder(256);
77        drainInitialUpdate(e);
78
79        e.literal(4, "/sample/path", false);
80        // @formatter:off
81        test(e,
82
83             "040c 2f73 616d 706c 652f 7061 7468",
84
85             "empty.");
86        // @formatter:on
87    }
88
89    //
90    // http://tools.ietf.org/html/rfc7541#appendix-C.2.3
91    //
92    @Test
93    public void example3() {
94
95        Encoder e = newCustomEncoder(256);
96        drainInitialUpdate(e);
97
98        e.literalNeverIndexed("password", false, "secret", false);
99        // @formatter:off
100        test(e,
101
102             "1008 7061 7373 776f 7264 0673 6563 7265\n" +
103             "74",
104
105             "empty.");
106        // @formatter:on
107    }
108
109    //
110    // http://tools.ietf.org/html/rfc7541#appendix-C.2.4
111    //
112    @Test
113    public void example4() {
114
115        Encoder e = newCustomEncoder(256);
116        drainInitialUpdate(e);
117
118        e.indexed(2);
119        // @formatter:off
120        test(e,
121
122             "82",
123
124             "empty.");
125        // @formatter:on
126    }
127
128    //
129    // http://tools.ietf.org/html/rfc7541#appendix-C.3
130    //
131    @Test
132    public void example5() {
133        Encoder e = newCustomEncoder(256);
134        drainInitialUpdate(e);
135
136        ByteBuffer output = ByteBuffer.allocate(64);
137        e.indexed(2);
138        e.encode(output);
139        e.indexed(6);
140        e.encode(output);
141        e.indexed(4);
142        e.encode(output);
143        e.literalWithIndexing(1, "www.example.com", false);
144        e.encode(output);
145
146        output.flip();
147
148        // @formatter:off
149        test(e, output,
150
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        output.clear();
158
159        e.indexed( 2);
160        e.encode(output);
161        e.indexed( 6);
162        e.encode(output);
163        e.indexed( 4);
164        e.encode(output);
165        e.indexed(62);
166        e.encode(output);
167        e.literalWithIndexing(24, "no-cache", false);
168        e.encode(output);
169
170        output.flip();
171
172        test(e, output,
173
174             "8286 84be 5808 6e6f 2d63 6163 6865",
175
176             "[  1] (s =  53) cache-control: no-cache\n" +
177             "[  2] (s =  57) :authority: www.example.com\n" +
178             "      Table size: 110");
179
180        output.clear();
181
182        e.indexed( 2);
183        e.encode(output);
184        e.indexed( 7);
185        e.encode(output);
186        e.indexed( 5);
187        e.encode(output);
188        e.indexed(63);
189        e.encode(output);
190        e.literalWithIndexing("custom-key", false, "custom-value", false);
191        e.encode(output);
192
193        output.flip();
194
195        test(e, output,
196
197             "8287 85bf 400a 6375 7374 6f6d 2d6b 6579\n" +
198             "0c63 7573 746f 6d2d 7661 6c75 65",
199
200             "[  1] (s =  54) custom-key: custom-value\n" +
201             "[  2] (s =  53) cache-control: no-cache\n" +
202             "[  3] (s =  57) :authority: www.example.com\n" +
203             "      Table size: 164");
204        // @formatter:on
205    }
206
207    @Test
208    public void example5AllSplits() {
209
210        List<Consumer<Encoder>> actions = new LinkedList<>();
211        actions.add(e -> e.indexed(2));
212        actions.add(e -> e.indexed(6));
213        actions.add(e -> e.indexed(4));
214        actions.add(e -> e.literalWithIndexing(1, "www.example.com", false));
215
216        encodeAllSplits(
217                actions,
218
219                "8286 8441 0f77 7777 2e65 7861 6d70 6c65\n" +
220                "2e63 6f6d",
221
222                "[  1] (s =  57) :authority: www.example.com\n" +
223                "      Table size:  57");
224    }
225
226    private static void encodeAllSplits(Iterable<Consumer<Encoder>> consumers,
227                                        String expectedHexdump,
228                                        String expectedTableState) {
229        ByteBuffer buffer = SpecHelper.toBytes(expectedHexdump);
230        erase(buffer); // Zeroed buffer of size needed to hold the encoding
231        forEachSplit(buffer, iterable -> {
232            List<ByteBuffer> copy = new LinkedList<>();
233            iterable.forEach(b -> copy.add(ByteBuffer.allocate(b.remaining())));
234            Iterator<ByteBuffer> output = copy.iterator();
235            if (!output.hasNext()) {
236                throw new IllegalStateException("No buffers to encode to");
237            }
238            Encoder e = newCustomEncoder(256); // FIXME: pull up (as a parameter)
239            drainInitialUpdate(e);
240            boolean encoded;
241            ByteBuffer b = output.next();
242            for (Consumer<Encoder> c : consumers) {
243                c.accept(e);
244                do {
245                    encoded = e.encode(b);
246                    if (!encoded) {
247                        if (output.hasNext()) {
248                            b = output.next();
249                        } else {
250                            throw new IllegalStateException("No room for encoding");
251                        }
252                    }
253                }
254                while (!encoded);
255            }
256            copy.forEach(Buffer::flip);
257            ByteBuffer data = concat(copy);
258            test(e, data, expectedHexdump, expectedTableState);
259        });
260    }
261
262    //
263    // http://tools.ietf.org/html/rfc7541#appendix-C.4
264    //
265    @Test
266    public void example6() {
267        Encoder e = newCustomEncoder(256);
268        drainInitialUpdate(e);
269
270        ByteBuffer output = ByteBuffer.allocate(64);
271        e.indexed(2);
272        e.encode(output);
273        e.indexed(6);
274        e.encode(output);
275        e.indexed(4);
276        e.encode(output);
277        e.literalWithIndexing(1, "www.example.com", true);
278        e.encode(output);
279
280        output.flip();
281
282        // @formatter:off
283        test(e, output,
284
285             "8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4\n" +
286             "ff",
287
288             "[  1] (s =  57) :authority: www.example.com\n" +
289             "      Table size:  57");
290
291        output.clear();
292
293        e.indexed( 2);
294        e.encode(output);
295        e.indexed( 6);
296        e.encode(output);
297        e.indexed( 4);
298        e.encode(output);
299        e.indexed(62);
300        e.encode(output);
301        e.literalWithIndexing(24, "no-cache", true);
302        e.encode(output);
303
304        output.flip();
305
306        test(e, output,
307
308             "8286 84be 5886 a8eb 1064 9cbf",
309
310             "[  1] (s =  53) cache-control: no-cache\n" +
311             "[  2] (s =  57) :authority: www.example.com\n" +
312             "      Table size: 110");
313
314        output.clear();
315
316        e.indexed( 2);
317        e.encode(output);
318        e.indexed( 7);
319        e.encode(output);
320        e.indexed( 5);
321        e.encode(output);
322        e.indexed(63);
323        e.encode(output);
324        e.literalWithIndexing("custom-key", true, "custom-value", true);
325        e.encode(output);
326
327        output.flip();
328
329        test(e, output,
330
331             "8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925\n" +
332             "a849 e95b b8e8 b4bf",
333
334             "[  1] (s =  54) custom-key: custom-value\n" +
335             "[  2] (s =  53) cache-control: no-cache\n" +
336             "[  3] (s =  57) :authority: www.example.com\n" +
337             "      Table size: 164");
338        // @formatter:on
339    }
340
341    //
342    // http://tools.ietf.org/html/rfc7541#appendix-C.5
343    //
344    @Test
345    public void example7() {
346        Encoder e = newCustomEncoder(256);
347        drainInitialUpdate(e);
348
349        ByteBuffer output = ByteBuffer.allocate(128);
350        // @formatter:off
351        e.literalWithIndexing( 8, "302", false);
352        e.encode(output);
353        e.literalWithIndexing(24, "private", false);
354        e.encode(output);
355        e.literalWithIndexing(33, "Mon, 21 Oct 2013 20:13:21 GMT", false);
356        e.encode(output);
357        e.literalWithIndexing(46, "https://www.example.com", false);
358        e.encode(output);
359
360        output.flip();
361
362        test(e, output,
363
364             "4803 3330 3258 0770 7269 7661 7465 611d\n" +
365             "4d6f 6e2c 2032 3120 4f63 7420 3230 3133\n" +
366             "2032 303a 3133 3a32 3120 474d 546e 1768\n" +
367             "7474 7073 3a2f 2f77 7777 2e65 7861 6d70\n" +
368             "6c65 2e63 6f6d",
369
370             "[  1] (s =  63) location: https://www.example.com\n" +
371             "[  2] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
372             "[  3] (s =  52) cache-control: private\n" +
373             "[  4] (s =  42) :status: 302\n" +
374             "      Table size: 222");
375
376        output.clear();
377
378        e.literalWithIndexing( 8, "307", false);
379        e.encode(output);
380        e.indexed(65);
381        e.encode(output);
382        e.indexed(64);
383        e.encode(output);
384        e.indexed(63);
385        e.encode(output);
386
387        output.flip();
388
389        test(e, output,
390
391             "4803 3330 37c1 c0bf",
392
393             "[  1] (s =  42) :status: 307\n" +
394             "[  2] (s =  63) location: https://www.example.com\n" +
395             "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
396             "[  4] (s =  52) cache-control: private\n" +
397             "      Table size: 222");
398
399        output.clear();
400
401        e.indexed( 8);
402        e.encode(output);
403        e.indexed(65);
404        e.encode(output);
405        e.literalWithIndexing(33, "Mon, 21 Oct 2013 20:13:22 GMT", false);
406        e.encode(output);
407        e.indexed(64);
408        e.encode(output);
409        e.literalWithIndexing(26, "gzip", false);
410        e.encode(output);
411        e.literalWithIndexing(55, "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", false);
412        e.encode(output);
413
414        output.flip();
415
416        test(e, output,
417
418             "88c1 611d 4d6f 6e2c 2032 3120 4f63 7420\n" +
419             "3230 3133 2032 303a 3133 3a32 3220 474d\n" +
420             "54c0 5a04 677a 6970 7738 666f 6f3d 4153\n" +
421             "444a 4b48 514b 425a 584f 5157 454f 5049\n" +
422             "5541 5851 5745 4f49 553b 206d 6178 2d61\n" +
423             "6765 3d33 3630 303b 2076 6572 7369 6f6e\n" +
424             "3d31",
425
426             "[  1] (s =  98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\n" +
427             "[  2] (s =  52) content-encoding: gzip\n" +
428             "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
429             "      Table size: 215");
430        // @formatter:on
431    }
432
433    //
434    // http://tools.ietf.org/html/rfc7541#appendix-C.6
435    //
436    @Test
437    public void example8() {
438        Encoder e = newCustomEncoder(256);
439        drainInitialUpdate(e);
440
441        ByteBuffer output = ByteBuffer.allocate(128);
442        // @formatter:off
443        e.literalWithIndexing( 8, "302", true);
444        e.encode(output);
445        e.literalWithIndexing(24, "private", true);
446        e.encode(output);
447        e.literalWithIndexing(33, "Mon, 21 Oct 2013 20:13:21 GMT", true);
448        e.encode(output);
449        e.literalWithIndexing(46, "https://www.example.com", true);
450        e.encode(output);
451
452        output.flip();
453
454        test(e, output,
455
456             "4882 6402 5885 aec3 771a 4b61 96d0 7abe\n" +
457             "9410 54d4 44a8 2005 9504 0b81 66e0 82a6\n" +
458             "2d1b ff6e 919d 29ad 1718 63c7 8f0b 97c8\n" +
459             "e9ae 82ae 43d3",
460
461             "[  1] (s =  63) location: https://www.example.com\n" +
462             "[  2] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
463             "[  3] (s =  52) cache-control: private\n" +
464             "[  4] (s =  42) :status: 302\n" +
465             "      Table size: 222");
466
467        output.clear();
468
469        e.literalWithIndexing( 8, "307", true);
470        e.encode(output);
471        e.indexed(65);
472        e.encode(output);
473        e.indexed(64);
474        e.encode(output);
475        e.indexed(63);
476        e.encode(output);
477
478        output.flip();
479
480        test(e, output,
481
482             "4883 640e ffc1 c0bf",
483
484             "[  1] (s =  42) :status: 307\n" +
485             "[  2] (s =  63) location: https://www.example.com\n" +
486             "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
487             "[  4] (s =  52) cache-control: private\n" +
488             "      Table size: 222");
489
490        output.clear();
491
492        e.indexed( 8);
493        e.encode(output);
494        e.indexed(65);
495        e.encode(output);
496        e.literalWithIndexing(33, "Mon, 21 Oct 2013 20:13:22 GMT", true);
497        e.encode(output);
498        e.indexed(64);
499        e.encode(output);
500        e.literalWithIndexing(26, "gzip", true);
501        e.encode(output);
502        e.literalWithIndexing(55, "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", true);
503        e.encode(output);
504
505        output.flip();
506
507        test(e, output,
508
509             "88c1 6196 d07a be94 1054 d444 a820 0595\n" +
510             "040b 8166 e084 a62d 1bff c05a 839b d9ab\n" +
511             "77ad 94e7 821d d7f2 e6c7 b335 dfdf cd5b\n" +
512             "3960 d5af 2708 7f36 72c1 ab27 0fb5 291f\n" +
513             "9587 3160 65c0 03ed 4ee5 b106 3d50 07",
514
515             "[  1] (s =  98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\n" +
516             "[  2] (s =  52) content-encoding: gzip\n" +
517             "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
518             "      Table size: 215");
519        // @formatter:on
520    }
521
522    @Test
523    public void initialSizeUpdateDefaultEncoder() {
524        Function<Integer, Encoder> e = Encoder::new;
525        testSizeUpdate(e, 1024, asList(), asList(0));
526        testSizeUpdate(e, 1024, asList(1024), asList(0));
527        testSizeUpdate(e, 1024, asList(1024, 1024), asList(0));
528        testSizeUpdate(e, 1024, asList(1024, 512), asList(0));
529        testSizeUpdate(e, 1024, asList(512, 1024), asList(0));
530        testSizeUpdate(e, 1024, asList(512, 2048), asList(0));
531    }
532
533    @Test
534    public void initialSizeUpdateCustomEncoder() {
535        Function<Integer, Encoder> e = EncoderTest::newCustomEncoder;
536        testSizeUpdate(e, 1024, asList(), asList(1024));
537        testSizeUpdate(e, 1024, asList(1024), asList(1024));
538        testSizeUpdate(e, 1024, asList(1024, 1024), asList(1024));
539        testSizeUpdate(e, 1024, asList(1024, 512), asList(512));
540        testSizeUpdate(e, 1024, asList(512, 1024), asList(1024));
541        testSizeUpdate(e, 1024, asList(512, 2048), asList(2048));
542    }
543
544    @Test
545    public void seriesOfSizeUpdatesDefaultEncoder() {
546        Function<Integer, Encoder> e = c -> {
547            Encoder encoder = new Encoder(c);
548            drainInitialUpdate(encoder);
549            return encoder;
550        };
551        testSizeUpdate(e, 0, asList(0), asList());
552        testSizeUpdate(e, 1024, asList(1024), asList());
553        testSizeUpdate(e, 1024, asList(2048), asList());
554        testSizeUpdate(e, 1024, asList(512), asList());
555        testSizeUpdate(e, 1024, asList(1024, 1024), asList());
556        testSizeUpdate(e, 1024, asList(1024, 2048), asList());
557        testSizeUpdate(e, 1024, asList(2048, 1024), asList());
558        testSizeUpdate(e, 1024, asList(1024, 512), asList());
559        testSizeUpdate(e, 1024, asList(512, 1024), asList());
560    }
561
562    //
563    // https://tools.ietf.org/html/rfc7541#section-4.2
564    //
565    @Test
566    public void seriesOfSizeUpdatesCustomEncoder() {
567        Function<Integer, Encoder> e = c -> {
568            Encoder encoder = newCustomEncoder(c);
569            drainInitialUpdate(encoder);
570            return encoder;
571        };
572        testSizeUpdate(e, 0, asList(0), asList());
573        testSizeUpdate(e, 1024, asList(1024), asList());
574        testSizeUpdate(e, 1024, asList(2048), asList(2048));
575        testSizeUpdate(e, 1024, asList(512), asList(512));
576        testSizeUpdate(e, 1024, asList(1024, 1024), asList());
577        testSizeUpdate(e, 1024, asList(1024, 2048), asList(2048));
578        testSizeUpdate(e, 1024, asList(2048, 1024), asList());
579        testSizeUpdate(e, 1024, asList(1024, 512), asList(512));
580        testSizeUpdate(e, 1024, asList(512, 1024), asList(512, 1024));
581    }
582
583    @Test
584    public void callSequenceViolations() {
585        {   // Hasn't set up a header
586            Encoder e = new Encoder(0);
587            assertVoidThrows(IllegalStateException.class, () -> e.encode(ByteBuffer.allocate(16)));
588        }
589        {   // Can't set up header while there's an unfinished encoding
590            Encoder e = new Encoder(0);
591            e.indexed(32);
592            assertVoidThrows(IllegalStateException.class, () -> e.indexed(32));
593        }
594        {   // Can't setMaxCapacity while there's an unfinished encoding
595            Encoder e = new Encoder(0);
596            e.indexed(32);
597            assertVoidThrows(IllegalStateException.class, () -> e.setMaxCapacity(512));
598        }
599        {   // Hasn't set up a header
600            Encoder e = new Encoder(0);
601            e.setMaxCapacity(256);
602            assertVoidThrows(IllegalStateException.class, () -> e.encode(ByteBuffer.allocate(16)));
603        }
604        {   // Hasn't set up a header after the previous encoding
605            Encoder e = new Encoder(0);
606            e.indexed(0);
607            boolean encoded = e.encode(ByteBuffer.allocate(16));
608            assertTrue(encoded); // assumption
609            assertVoidThrows(IllegalStateException.class, () -> e.encode(ByteBuffer.allocate(16)));
610        }
611    }
612
613    private static void test(Encoder encoder,
614                             String expectedTableState,
615                             String expectedHexdump) {
616
617        ByteBuffer b = ByteBuffer.allocate(128);
618        encoder.encode(b);
619        b.flip();
620        test(encoder, b, expectedTableState, expectedHexdump);
621    }
622
623    private static void test(Encoder encoder,
624                             ByteBuffer output,
625                             String expectedHexdump,
626                             String expectedTableState) {
627
628        String actualTableState = encoder.getHeaderTable().getStateString();
629        assertEquals(actualTableState, expectedTableState);
630
631        String actualHexdump = toHexdump(output);
632        assertEquals(actualHexdump, expectedHexdump.replaceAll("\\n", " "));
633    }
634
635    // initial size - the size encoder is constructed with
636    // updates      - a sequence of values for consecutive calls to encoder.setMaxCapacity
637    // expected     - a sequence of values expected to be decoded by a decoder
638    private void testSizeUpdate(Function<Integer, Encoder> encoder,
639                                int initialSize,
640                                List<Integer> updates,
641                                List<Integer> expected) {
642        Encoder e = encoder.apply(initialSize);
643        updates.forEach(e::setMaxCapacity);
644        ByteBuffer b = ByteBuffer.allocate(64);
645        e.header("a", "b");
646        e.encode(b);
647        b.flip();
648        Decoder d = new Decoder(updates.isEmpty() ? initialSize : Collections.max(updates));
649        List<Integer> actual = new ArrayList<>();
650        d.decode(b, true, new DecodingCallback() {
651            @Override
652            public void onDecoded(CharSequence name, CharSequence value) { }
653
654            @Override
655            public void onSizeUpdate(int capacity) {
656                actual.add(capacity);
657            }
658        });
659        assertEquals(actual, expected);
660    }
661
662    //
663    // Default encoder does not need any table, therefore a subclass that
664    // behaves differently is needed
665    //
666    private static Encoder newCustomEncoder(int maxCapacity) {
667        return new Encoder(maxCapacity) {
668            @Override
669            protected int calculateCapacity(int maxCapacity) {
670                return maxCapacity;
671            }
672        };
673    }
674
675    private static void drainInitialUpdate(Encoder e) {
676        ByteBuffer b = ByteBuffer.allocate(4);
677        e.header("a", "b");
678        boolean done;
679        do {
680            done = e.encode(b);
681            b.flip();
682        } while (!done);
683    }
684
685    private static void erase(ByteBuffer buffer) {
686        buffer.clear();
687        while (buffer.hasRemaining()) {
688            buffer.put((byte) 0);
689        }
690        buffer.clear();
691    }
692}
693