| /* |
| * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| package jdk.internal.net.http.hpack; |
| |
| import org.testng.annotations.Test; |
| |
| import java.io.IOException; |
| import java.nio.Buffer; |
| import java.nio.ByteBuffer; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.function.Consumer; |
| import java.util.function.Function; |
| |
| import static jdk.internal.net.http.hpack.BuffersTestingKit.concat; |
| import static jdk.internal.net.http.hpack.BuffersTestingKit.forEachSplit; |
| import static jdk.internal.net.http.hpack.SpecHelper.toHexdump; |
| import static jdk.internal.net.http.hpack.TestHelper.assertVoidThrows; |
| import static java.util.Arrays.asList; |
| import static org.testng.Assert.assertEquals; |
| import static org.testng.Assert.assertTrue; |
| |
| // TODO: map textual representation of commands from the spec to actual |
| // calls to encoder (actually, this is a good idea for decoder as well) |
| public final class EncoderTest { |
| |
| // |
| // http://tools.ietf.org/html/rfc7541#appendix-C.2.1 |
| // |
| @Test |
| public void example1() { |
| |
| Encoder e = newCustomEncoder(256); |
| drainInitialUpdate(e); |
| |
| e.literalWithIndexing("custom-key", false, "custom-header", false); |
| // @formatter:off |
| test(e, |
| |
| "400a 6375 7374 6f6d 2d6b 6579 0d63 7573\n" + |
| "746f 6d2d 6865 6164 6572", |
| |
| "[ 1] (s = 55) custom-key: custom-header\n" + |
| " Table size: 55"); |
| // @formatter:on |
| } |
| |
| // |
| // http://tools.ietf.org/html/rfc7541#appendix-C.2.2 |
| // |
| @Test |
| public void example2() { |
| |
| Encoder e = newCustomEncoder(256); |
| drainInitialUpdate(e); |
| |
| e.literal(4, "/sample/path", false); |
| // @formatter:off |
| test(e, |
| |
| "040c 2f73 616d 706c 652f 7061 7468", |
| |
| "empty."); |
| // @formatter:on |
| } |
| |
| // |
| // http://tools.ietf.org/html/rfc7541#appendix-C.2.3 |
| // |
| @Test |
| public void example3() { |
| |
| Encoder e = newCustomEncoder(256); |
| drainInitialUpdate(e); |
| |
| e.literalNeverIndexed("password", false, "secret", false); |
| // @formatter:off |
| test(e, |
| |
| "1008 7061 7373 776f 7264 0673 6563 7265\n" + |
| "74", |
| |
| "empty."); |
| // @formatter:on |
| } |
| |
| // |
| // http://tools.ietf.org/html/rfc7541#appendix-C.2.4 |
| // |
| @Test |
| public void example4() { |
| |
| Encoder e = newCustomEncoder(256); |
| drainInitialUpdate(e); |
| |
| e.indexed(2); |
| // @formatter:off |
| test(e, |
| |
| "82", |
| |
| "empty."); |
| // @formatter:on |
| } |
| |
| // |
| // http://tools.ietf.org/html/rfc7541#appendix-C.3 |
| // |
| @Test |
| public void example5() { |
| Encoder e = newCustomEncoder(256); |
| drainInitialUpdate(e); |
| |
| ByteBuffer output = ByteBuffer.allocate(64); |
| e.indexed(2); |
| e.encode(output); |
| e.indexed(6); |
| e.encode(output); |
| e.indexed(4); |
| e.encode(output); |
| e.literalWithIndexing(1, "www.example.com", false); |
| e.encode(output); |
| |
| output.flip(); |
| |
| // @formatter:off |
| test(e, output, |
| |
| "8286 8441 0f77 7777 2e65 7861 6d70 6c65\n" + |
| "2e63 6f6d", |
| |
| "[ 1] (s = 57) :authority: www.example.com\n" + |
| " Table size: 57"); |
| |
| output.clear(); |
| |
| e.indexed( 2); |
| e.encode(output); |
| e.indexed( 6); |
| e.encode(output); |
| e.indexed( 4); |
| e.encode(output); |
| e.indexed(62); |
| e.encode(output); |
| e.literalWithIndexing(24, "no-cache", false); |
| e.encode(output); |
| |
| output.flip(); |
| |
| test(e, output, |
| |
| "8286 84be 5808 6e6f 2d63 6163 6865", |
| |
| "[ 1] (s = 53) cache-control: no-cache\n" + |
| "[ 2] (s = 57) :authority: www.example.com\n" + |
| " Table size: 110"); |
| |
| output.clear(); |
| |
| e.indexed( 2); |
| e.encode(output); |
| e.indexed( 7); |
| e.encode(output); |
| e.indexed( 5); |
| e.encode(output); |
| e.indexed(63); |
| e.encode(output); |
| e.literalWithIndexing("custom-key", false, "custom-value", false); |
| e.encode(output); |
| |
| output.flip(); |
| |
| test(e, output, |
| |
| "8287 85bf 400a 6375 7374 6f6d 2d6b 6579\n" + |
| "0c63 7573 746f 6d2d 7661 6c75 65", |
| |
| "[ 1] (s = 54) custom-key: custom-value\n" + |
| "[ 2] (s = 53) cache-control: no-cache\n" + |
| "[ 3] (s = 57) :authority: www.example.com\n" + |
| " Table size: 164"); |
| // @formatter:on |
| } |
| |
| @Test |
| public void example5AllSplits() { |
| |
| List<Consumer<Encoder>> actions = new LinkedList<>(); |
| actions.add(e -> e.indexed(2)); |
| actions.add(e -> e.indexed(6)); |
| actions.add(e -> e.indexed(4)); |
| actions.add(e -> e.literalWithIndexing(1, "www.example.com", false)); |
| |
| encodeAllSplits( |
| actions, |
| |
| "8286 8441 0f77 7777 2e65 7861 6d70 6c65\n" + |
| "2e63 6f6d", |
| |
| "[ 1] (s = 57) :authority: www.example.com\n" + |
| " Table size: 57"); |
| } |
| |
| private static void encodeAllSplits(Iterable<Consumer<Encoder>> consumers, |
| String expectedHexdump, |
| String expectedTableState) { |
| ByteBuffer buffer = SpecHelper.toBytes(expectedHexdump); |
| erase(buffer); // Zeroed buffer of size needed to hold the encoding |
| forEachSplit(buffer, iterable -> { |
| List<ByteBuffer> copy = new LinkedList<>(); |
| iterable.forEach(b -> copy.add(ByteBuffer.allocate(b.remaining()))); |
| Iterator<ByteBuffer> output = copy.iterator(); |
| if (!output.hasNext()) { |
| throw new IllegalStateException("No buffers to encode to"); |
| } |
| Encoder e = newCustomEncoder(256); // FIXME: pull up (as a parameter) |
| drainInitialUpdate(e); |
| boolean encoded; |
| ByteBuffer b = output.next(); |
| for (Consumer<Encoder> c : consumers) { |
| c.accept(e); |
| do { |
| encoded = e.encode(b); |
| if (!encoded) { |
| if (output.hasNext()) { |
| b = output.next(); |
| } else { |
| throw new IllegalStateException("No room for encoding"); |
| } |
| } |
| } |
| while (!encoded); |
| } |
| copy.forEach(Buffer::flip); |
| ByteBuffer data = concat(copy); |
| test(e, data, expectedHexdump, expectedTableState); |
| }); |
| } |
| |
| // |
| // http://tools.ietf.org/html/rfc7541#appendix-C.4 |
| // |
| @Test |
| public void example6() { |
| Encoder e = newCustomEncoder(256); |
| drainInitialUpdate(e); |
| |
| ByteBuffer output = ByteBuffer.allocate(64); |
| e.indexed(2); |
| e.encode(output); |
| e.indexed(6); |
| e.encode(output); |
| e.indexed(4); |
| e.encode(output); |
| e.literalWithIndexing(1, "www.example.com", true); |
| e.encode(output); |
| |
| output.flip(); |
| |
| // @formatter:off |
| test(e, output, |
| |
| "8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4\n" + |
| "ff", |
| |
| "[ 1] (s = 57) :authority: www.example.com\n" + |
| " Table size: 57"); |
| |
| output.clear(); |
| |
| e.indexed( 2); |
| e.encode(output); |
| e.indexed( 6); |
| e.encode(output); |
| e.indexed( 4); |
| e.encode(output); |
| e.indexed(62); |
| e.encode(output); |
| e.literalWithIndexing(24, "no-cache", true); |
| e.encode(output); |
| |
| output.flip(); |
| |
| test(e, output, |
| |
| "8286 84be 5886 a8eb 1064 9cbf", |
| |
| "[ 1] (s = 53) cache-control: no-cache\n" + |
| "[ 2] (s = 57) :authority: www.example.com\n" + |
| " Table size: 110"); |
| |
| output.clear(); |
| |
| e.indexed( 2); |
| e.encode(output); |
| e.indexed( 7); |
| e.encode(output); |
| e.indexed( 5); |
| e.encode(output); |
| e.indexed(63); |
| e.encode(output); |
| e.literalWithIndexing("custom-key", true, "custom-value", true); |
| e.encode(output); |
| |
| output.flip(); |
| |
| test(e, output, |
| |
| "8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925\n" + |
| "a849 e95b b8e8 b4bf", |
| |
| "[ 1] (s = 54) custom-key: custom-value\n" + |
| "[ 2] (s = 53) cache-control: no-cache\n" + |
| "[ 3] (s = 57) :authority: www.example.com\n" + |
| " Table size: 164"); |
| // @formatter:on |
| } |
| |
| // |
| // http://tools.ietf.org/html/rfc7541#appendix-C.5 |
| // |
| @Test |
| public void example7() { |
| Encoder e = newCustomEncoder(256); |
| drainInitialUpdate(e); |
| |
| ByteBuffer output = ByteBuffer.allocate(128); |
| // @formatter:off |
| e.literalWithIndexing( 8, "302", false); |
| e.encode(output); |
| e.literalWithIndexing(24, "private", false); |
| e.encode(output); |
| e.literalWithIndexing(33, "Mon, 21 Oct 2013 20:13:21 GMT", false); |
| e.encode(output); |
| e.literalWithIndexing(46, "https://www.example.com", false); |
| e.encode(output); |
| |
| output.flip(); |
| |
| test(e, output, |
| |
| "4803 3330 3258 0770 7269 7661 7465 611d\n" + |
| "4d6f 6e2c 2032 3120 4f63 7420 3230 3133\n" + |
| "2032 303a 3133 3a32 3120 474d 546e 1768\n" + |
| "7474 7073 3a2f 2f77 7777 2e65 7861 6d70\n" + |
| "6c65 2e63 6f6d", |
| |
| "[ 1] (s = 63) location: https://www.example.com\n" + |
| "[ 2] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" + |
| "[ 3] (s = 52) cache-control: private\n" + |
| "[ 4] (s = 42) :status: 302\n" + |
| " Table size: 222"); |
| |
| output.clear(); |
| |
| e.literalWithIndexing( 8, "307", false); |
| e.encode(output); |
| e.indexed(65); |
| e.encode(output); |
| e.indexed(64); |
| e.encode(output); |
| e.indexed(63); |
| e.encode(output); |
| |
| output.flip(); |
| |
| test(e, output, |
| |
| "4803 3330 37c1 c0bf", |
| |
| "[ 1] (s = 42) :status: 307\n" + |
| "[ 2] (s = 63) location: https://www.example.com\n" + |
| "[ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" + |
| "[ 4] (s = 52) cache-control: private\n" + |
| " Table size: 222"); |
| |
| output.clear(); |
| |
| e.indexed( 8); |
| e.encode(output); |
| e.indexed(65); |
| e.encode(output); |
| e.literalWithIndexing(33, "Mon, 21 Oct 2013 20:13:22 GMT", false); |
| e.encode(output); |
| e.indexed(64); |
| e.encode(output); |
| e.literalWithIndexing(26, "gzip", false); |
| e.encode(output); |
| e.literalWithIndexing(55, "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", false); |
| e.encode(output); |
| |
| output.flip(); |
| |
| test(e, output, |
| |
| "88c1 611d 4d6f 6e2c 2032 3120 4f63 7420\n" + |
| "3230 3133 2032 303a 3133 3a32 3220 474d\n" + |
| "54c0 5a04 677a 6970 7738 666f 6f3d 4153\n" + |
| "444a 4b48 514b 425a 584f 5157 454f 5049\n" + |
| "5541 5851 5745 4f49 553b 206d 6178 2d61\n" + |
| "6765 3d33 3630 303b 2076 6572 7369 6f6e\n" + |
| "3d31", |
| |
| "[ 1] (s = 98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\n" + |
| "[ 2] (s = 52) content-encoding: gzip\n" + |
| "[ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:22 GMT\n" + |
| " Table size: 215"); |
| // @formatter:on |
| } |
| |
| // |
| // http://tools.ietf.org/html/rfc7541#appendix-C.6 |
| // |
| @Test |
| public void example8() { |
| Encoder e = newCustomEncoder(256); |
| drainInitialUpdate(e); |
| |
| ByteBuffer output = ByteBuffer.allocate(128); |
| // @formatter:off |
| e.literalWithIndexing( 8, "302", true); |
| e.encode(output); |
| e.literalWithIndexing(24, "private", true); |
| e.encode(output); |
| e.literalWithIndexing(33, "Mon, 21 Oct 2013 20:13:21 GMT", true); |
| e.encode(output); |
| e.literalWithIndexing(46, "https://www.example.com", true); |
| e.encode(output); |
| |
| output.flip(); |
| |
| test(e, output, |
| |
| "4882 6402 5885 aec3 771a 4b61 96d0 7abe\n" + |
| "9410 54d4 44a8 2005 9504 0b81 66e0 82a6\n" + |
| "2d1b ff6e 919d 29ad 1718 63c7 8f0b 97c8\n" + |
| "e9ae 82ae 43d3", |
| |
| "[ 1] (s = 63) location: https://www.example.com\n" + |
| "[ 2] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" + |
| "[ 3] (s = 52) cache-control: private\n" + |
| "[ 4] (s = 42) :status: 302\n" + |
| " Table size: 222"); |
| |
| output.clear(); |
| |
| e.literalWithIndexing( 8, "307", true); |
| e.encode(output); |
| e.indexed(65); |
| e.encode(output); |
| e.indexed(64); |
| e.encode(output); |
| e.indexed(63); |
| e.encode(output); |
| |
| output.flip(); |
| |
| test(e, output, |
| |
| "4883 640e ffc1 c0bf", |
| |
| "[ 1] (s = 42) :status: 307\n" + |
| "[ 2] (s = 63) location: https://www.example.com\n" + |
| "[ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" + |
| "[ 4] (s = 52) cache-control: private\n" + |
| " Table size: 222"); |
| |
| output.clear(); |
| |
| e.indexed( 8); |
| e.encode(output); |
| e.indexed(65); |
| e.encode(output); |
| e.literalWithIndexing(33, "Mon, 21 Oct 2013 20:13:22 GMT", true); |
| e.encode(output); |
| e.indexed(64); |
| e.encode(output); |
| e.literalWithIndexing(26, "gzip", true); |
| e.encode(output); |
| e.literalWithIndexing(55, "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", true); |
| e.encode(output); |
| |
| output.flip(); |
| |
| test(e, output, |
| |
| "88c1 6196 d07a be94 1054 d444 a820 0595\n" + |
| "040b 8166 e084 a62d 1bff c05a 839b d9ab\n" + |
| "77ad 94e7 821d d7f2 e6c7 b335 dfdf cd5b\n" + |
| "3960 d5af 2708 7f36 72c1 ab27 0fb5 291f\n" + |
| "9587 3160 65c0 03ed 4ee5 b106 3d50 07", |
| |
| "[ 1] (s = 98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\n" + |
| "[ 2] (s = 52) content-encoding: gzip\n" + |
| "[ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:22 GMT\n" + |
| " Table size: 215"); |
| // @formatter:on |
| } |
| |
| @Test |
| public void initialSizeUpdateDefaultEncoder() throws IOException { |
| Function<Integer, Encoder> e = Encoder::new; |
| testSizeUpdate(e, 1024, asList(), asList(0)); |
| testSizeUpdate(e, 1024, asList(1024), asList(0)); |
| testSizeUpdate(e, 1024, asList(1024, 1024), asList(0)); |
| testSizeUpdate(e, 1024, asList(1024, 512), asList(0)); |
| testSizeUpdate(e, 1024, asList(512, 1024), asList(0)); |
| testSizeUpdate(e, 1024, asList(512, 2048), asList(0)); |
| } |
| |
| @Test |
| public void initialSizeUpdateCustomEncoder() throws IOException { |
| Function<Integer, Encoder> e = EncoderTest::newCustomEncoder; |
| testSizeUpdate(e, 1024, asList(), asList(1024)); |
| testSizeUpdate(e, 1024, asList(1024), asList(1024)); |
| testSizeUpdate(e, 1024, asList(1024, 1024), asList(1024)); |
| testSizeUpdate(e, 1024, asList(1024, 512), asList(512)); |
| testSizeUpdate(e, 1024, asList(512, 1024), asList(1024)); |
| testSizeUpdate(e, 1024, asList(512, 2048), asList(2048)); |
| } |
| |
| @Test |
| public void seriesOfSizeUpdatesDefaultEncoder() throws IOException { |
| Function<Integer, Encoder> e = c -> { |
| Encoder encoder = new Encoder(c); |
| drainInitialUpdate(encoder); |
| return encoder; |
| }; |
| testSizeUpdate(e, 0, asList(0), asList()); |
| testSizeUpdate(e, 1024, asList(1024), asList()); |
| testSizeUpdate(e, 1024, asList(2048), asList()); |
| testSizeUpdate(e, 1024, asList(512), asList()); |
| testSizeUpdate(e, 1024, asList(1024, 1024), asList()); |
| testSizeUpdate(e, 1024, asList(1024, 2048), asList()); |
| testSizeUpdate(e, 1024, asList(2048, 1024), asList()); |
| testSizeUpdate(e, 1024, asList(1024, 512), asList()); |
| testSizeUpdate(e, 1024, asList(512, 1024), asList()); |
| } |
| |
| // |
| // https://tools.ietf.org/html/rfc7541#section-4.2 |
| // |
| @Test |
| public void seriesOfSizeUpdatesCustomEncoder() throws IOException { |
| Function<Integer, Encoder> e = c -> { |
| Encoder encoder = newCustomEncoder(c); |
| drainInitialUpdate(encoder); |
| return encoder; |
| }; |
| testSizeUpdate(e, 0, asList(0), asList()); |
| testSizeUpdate(e, 1024, asList(1024), asList()); |
| testSizeUpdate(e, 1024, asList(2048), asList(2048)); |
| testSizeUpdate(e, 1024, asList(512), asList(512)); |
| testSizeUpdate(e, 1024, asList(1024, 1024), asList()); |
| testSizeUpdate(e, 1024, asList(1024, 2048), asList(2048)); |
| testSizeUpdate(e, 1024, asList(2048, 1024), asList()); |
| testSizeUpdate(e, 1024, asList(1024, 512), asList(512)); |
| testSizeUpdate(e, 1024, asList(512, 1024), asList(512, 1024)); |
| } |
| |
| @Test |
| public void callSequenceViolations() { |
| { // Hasn't set up a header |
| Encoder e = new Encoder(0); |
| assertVoidThrows(IllegalStateException.class, () -> e.encode(ByteBuffer.allocate(16))); |
| } |
| { // Can't set up header while there's an unfinished encoding |
| Encoder e = new Encoder(0); |
| e.indexed(32); |
| assertVoidThrows(IllegalStateException.class, () -> e.indexed(32)); |
| } |
| { // Can't setMaxCapacity while there's an unfinished encoding |
| Encoder e = new Encoder(0); |
| e.indexed(32); |
| assertVoidThrows(IllegalStateException.class, () -> e.setMaxCapacity(512)); |
| } |
| { // Hasn't set up a header |
| Encoder e = new Encoder(0); |
| e.setMaxCapacity(256); |
| assertVoidThrows(IllegalStateException.class, () -> e.encode(ByteBuffer.allocate(16))); |
| } |
| { // Hasn't set up a header after the previous encoding |
| Encoder e = new Encoder(0); |
| e.indexed(0); |
| boolean encoded = e.encode(ByteBuffer.allocate(16)); |
| assertTrue(encoded); // assumption |
| assertVoidThrows(IllegalStateException.class, () -> e.encode(ByteBuffer.allocate(16))); |
| } |
| } |
| |
| private static void test(Encoder encoder, |
| String expectedTableState, |
| String expectedHexdump) { |
| |
| ByteBuffer b = ByteBuffer.allocate(128); |
| encoder.encode(b); |
| b.flip(); |
| test(encoder, b, expectedTableState, expectedHexdump); |
| } |
| |
| private static void test(Encoder encoder, |
| ByteBuffer output, |
| String expectedHexdump, |
| String expectedTableState) { |
| |
| String actualTableState = encoder.getHeaderTable().getStateString(); |
| assertEquals(actualTableState, expectedTableState); |
| |
| String actualHexdump = toHexdump(output); |
| assertEquals(actualHexdump, expectedHexdump.replaceAll("\\n", " ")); |
| } |
| |
| // initial size - the size encoder is constructed with |
| // updates - a sequence of values for consecutive calls to encoder.setMaxCapacity |
| // expected - a sequence of values expected to be decoded by a decoder |
| private void testSizeUpdate(Function<Integer, Encoder> encoder, |
| int initialSize, |
| List<Integer> updates, |
| List<Integer> expected) throws IOException { |
| Encoder e = encoder.apply(initialSize); |
| updates.forEach(e::setMaxCapacity); |
| ByteBuffer b = ByteBuffer.allocate(64); |
| e.header("a", "b"); |
| e.encode(b); |
| b.flip(); |
| Decoder d = new Decoder(updates.isEmpty() ? initialSize : Collections.max(updates)); |
| List<Integer> actual = new ArrayList<>(); |
| d.decode(b, true, new DecodingCallback() { |
| @Override |
| public void onDecoded(CharSequence name, CharSequence value) { } |
| |
| @Override |
| public void onSizeUpdate(int capacity) { |
| actual.add(capacity); |
| } |
| }); |
| assertEquals(actual, expected); |
| } |
| |
| // |
| // Default encoder does not need any table, therefore a subclass that |
| // behaves differently is needed |
| // |
| private static Encoder newCustomEncoder(int maxCapacity) { |
| return new Encoder(maxCapacity) { |
| @Override |
| protected int calculateCapacity(int maxCapacity) { |
| return maxCapacity; |
| } |
| }; |
| } |
| |
| private static void drainInitialUpdate(Encoder e) { |
| ByteBuffer b = ByteBuffer.allocate(4); |
| e.header("a", "b"); |
| boolean done; |
| do { |
| done = e.encode(b); |
| b.flip(); |
| } while (!done); |
| } |
| |
| private static void erase(ByteBuffer buffer) { |
| buffer.clear(); |
| while (buffer.hasRemaining()) { |
| buffer.put((byte) 0); |
| } |
| buffer.clear(); |
| } |
| } |