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.ByteBuffer;
28import java.nio.CharBuffer;
29import java.nio.charset.StandardCharsets;
30import java.util.ArrayList;
31import java.util.List;
32import java.util.Random;
33
34import static org.testng.Assert.assertEquals;
35import static org.testng.Assert.fail;
36import static jdk.incubator.http.internal.hpack.BuffersTestingKit.*;
37import static jdk.incubator.http.internal.hpack.TestHelper.newRandom;
38
39//
40// Some of the tests below overlap in what they test. This allows to diagnose
41// bugs quicker and with less pain by simply ruling out common working bits.
42//
43public final class BinaryPrimitivesTest {
44
45    private final Random rnd = newRandom();
46
47    @Test
48    public void integerRead1() {
49        verifyRead(bytes(0b00011111, 0b10011010, 0b00001010), 1337, 5);
50    }
51
52    @Test
53    public void integerRead2() {
54        verifyRead(bytes(0b00001010), 10, 5);
55    }
56
57    @Test
58    public void integerRead3() {
59        verifyRead(bytes(0b00101010), 42, 8);
60    }
61
62    @Test
63    public void integerWrite1() {
64        verifyWrite(bytes(0b00011111, 0b10011010, 0b00001010), 1337, 5);
65    }
66
67    @Test
68    public void integerWrite2() {
69        verifyWrite(bytes(0b00001010), 10, 5);
70    }
71
72    @Test
73    public void integerWrite3() {
74        verifyWrite(bytes(0b00101010), 42, 8);
75    }
76
77    //
78    // Since readInteger(x) is the inverse of writeInteger(x), thus:
79    //
80    // for all x: readInteger(writeInteger(x)) == x
81    //
82    @Test
83    public void integerIdentity() {
84        final int MAX_VALUE = 1 << 22;
85        int totalCases = 0;
86        int maxFilling = 0;
87        IntegerReader r = new IntegerReader();
88        IntegerWriter w = new IntegerWriter();
89        ByteBuffer buf = ByteBuffer.allocate(8);
90        for (int N = 1; N < 9; N++) {
91            for (int expected = 0; expected <= MAX_VALUE; expected++) {
92                w.reset().configure(expected, N, 1).write(buf);
93                buf.flip();
94                totalCases++;
95                maxFilling = Math.max(maxFilling, buf.remaining());
96                r.reset().configure(N).read(buf);
97                assertEquals(r.get(), expected);
98                buf.clear();
99            }
100        }
101        System.out.printf("totalCases: %,d, maxFilling: %,d, maxValue: %,d%n",
102                totalCases, maxFilling, MAX_VALUE);
103    }
104
105    @Test
106    public void integerReadChunked() {
107        final int NUM_TESTS = 1024;
108        IntegerReader r = new IntegerReader();
109        ByteBuffer bb = ByteBuffer.allocate(8);
110        IntegerWriter w = new IntegerWriter();
111        for (int i = 0; i < NUM_TESTS; i++) {
112            final int N = 1 + rnd.nextInt(8);
113            final int expected = rnd.nextInt(Integer.MAX_VALUE) + 1;
114            w.reset().configure(expected, N, rnd.nextInt()).write(bb);
115            bb.flip();
116
117            forEachSplit(bb,
118                    (buffers) -> {
119                        Iterable<? extends ByteBuffer> buf = relocateBuffers(injectEmptyBuffers(buffers));
120                        r.configure(N);
121                        for (ByteBuffer b : buf) {
122                            r.read(b);
123                        }
124                        assertEquals(r.get(), expected);
125                        r.reset();
126                    });
127            bb.clear();
128        }
129    }
130
131    // FIXME: use maxValue in the test
132
133    @Test
134    // FIXME: tune values for better coverage
135    public void integerWriteChunked() {
136        ByteBuffer bb = ByteBuffer.allocate(6);
137        IntegerWriter w = new IntegerWriter();
138        IntegerReader r = new IntegerReader();
139        for (int i = 0; i < 1024; i++) { // number of tests
140            final int N = 1 + rnd.nextInt(8);
141            final int payload = rnd.nextInt(255);
142            final int expected = rnd.nextInt(Integer.MAX_VALUE) + 1;
143
144            forEachSplit(bb,
145                    (buffers) -> {
146                        List<ByteBuffer> buf = new ArrayList<>();
147                        relocateBuffers(injectEmptyBuffers(buffers)).forEach(buf::add);
148                        boolean written = false;
149                        w.configure(expected, N, payload); // TODO: test for payload it can be read after written
150                        for (ByteBuffer b : buf) {
151                            int pos = b.position();
152                            written = w.write(b);
153                            b.position(pos);
154                        }
155                        if (!written) {
156                            fail("please increase bb size");
157                        }
158                        r.configure(N).read(concat(buf));
159                        // TODO: check payload here
160                        assertEquals(r.get(), expected);
161                        w.reset();
162                        r.reset();
163                        bb.clear();
164                    });
165        }
166    }
167
168
169    //
170    // Since readString(x) is the inverse of writeString(x), thus:
171    //
172    // for all x: readString(writeString(x)) == x
173    //
174    @Test
175    public void stringIdentity() {
176        final int MAX_STRING_LENGTH = 4096;
177        ByteBuffer bytes = ByteBuffer.allocate(MAX_STRING_LENGTH + 6); // it takes 6 bytes to encode string length of Integer.MAX_VALUE
178        CharBuffer chars = CharBuffer.allocate(MAX_STRING_LENGTH);
179        StringReader reader = new StringReader();
180        StringWriter writer = new StringWriter();
181        for (int len = 0; len <= MAX_STRING_LENGTH; len++) {
182            for (int i = 0; i < 64; i++) {
183                // not so much "test in isolation", I know... we're testing .reset() as well
184                bytes.clear();
185                chars.clear();
186
187                byte[] b = new byte[len];
188                rnd.nextBytes(b);
189
190                String expected = new String(b, StandardCharsets.ISO_8859_1); // reference string
191
192                boolean written = writer
193                        .configure(CharBuffer.wrap(expected), 0, expected.length(), false)
194                        .write(bytes);
195
196                if (!written) {
197                    fail("please increase 'bytes' size");
198                }
199                bytes.flip();
200                reader.read(bytes, chars);
201                chars.flip();
202                assertEquals(chars.toString(), expected);
203                reader.reset();
204                writer.reset();
205            }
206        }
207    }
208
209//    @Test
210//    public void huffmanStringWriteChunked() {
211//        fail();
212//    }
213//
214//    @Test
215//    public void huffmanStringReadChunked() {
216//        fail();
217//    }
218
219    @Test
220    public void stringWriteChunked() {
221        final int MAX_STRING_LENGTH = 8;
222        final ByteBuffer bytes = ByteBuffer.allocate(MAX_STRING_LENGTH + 6);
223        final CharBuffer chars = CharBuffer.allocate(MAX_STRING_LENGTH);
224        final StringReader reader = new StringReader();
225        final StringWriter writer = new StringWriter();
226        for (int len = 0; len <= MAX_STRING_LENGTH; len++) {
227
228            byte[] b = new byte[len];
229            rnd.nextBytes(b);
230
231            String expected = new String(b, StandardCharsets.ISO_8859_1); // reference string
232
233            forEachSplit(bytes, (buffers) -> {
234                writer.configure(expected, 0, expected.length(), false);
235                boolean written = false;
236                for (ByteBuffer buf : buffers) {
237                    int p0 = buf.position();
238                    written = writer.write(buf);
239                    buf.position(p0);
240                }
241                if (!written) {
242                    fail("please increase 'bytes' size");
243                }
244                reader.read(concat(buffers), chars);
245                chars.flip();
246                assertEquals(chars.toString(), expected);
247                reader.reset();
248                writer.reset();
249                chars.clear();
250                bytes.clear();
251            });
252        }
253    }
254
255    @Test
256    public void stringReadChunked() {
257        final int MAX_STRING_LENGTH = 16;
258        final ByteBuffer bytes = ByteBuffer.allocate(MAX_STRING_LENGTH + 6);
259        final CharBuffer chars = CharBuffer.allocate(MAX_STRING_LENGTH);
260        final StringReader reader = new StringReader();
261        final StringWriter writer = new StringWriter();
262        for (int len = 0; len <= MAX_STRING_LENGTH; len++) {
263
264            byte[] b = new byte[len];
265            rnd.nextBytes(b);
266
267            String expected = new String(b, StandardCharsets.ISO_8859_1); // reference string
268
269            boolean written = writer
270                    .configure(CharBuffer.wrap(expected), 0, expected.length(), false)
271                    .write(bytes);
272            writer.reset();
273
274            if (!written) {
275                fail("please increase 'bytes' size");
276            }
277            bytes.flip();
278
279            forEachSplit(bytes, (buffers) -> {
280                for (ByteBuffer buf : buffers) {
281                    int p0 = buf.position();
282                    reader.read(buf, chars);
283                    buf.position(p0);
284                }
285                chars.flip();
286                assertEquals(chars.toString(), expected);
287                reader.reset();
288                chars.clear();
289            });
290
291            bytes.clear();
292        }
293    }
294
295//    @Test
296//    public void test_Huffman_String_Identity() {
297//        StringWriter writer = new StringWriter();
298//        StringReader reader = new StringReader();
299//        // 256 * 8 gives 2048 bits in case of plain 8 bit coding
300//        // 256 * 30 gives you 7680 bits or 960 bytes in case of almost
301//        //          improbable event of 256 30 bits symbols in a row
302//        ByteBuffer binary = ByteBuffer.allocate(960);
303//        CharBuffer text = CharBuffer.allocate(960 / 5); // 5 = minimum code length
304//        for (int len = 0; len < 128; len++) {
305//            for (int i = 0; i < 256; i++) {
306//                // not so much "test in isolation", I know...
307//                binary.clear();
308//
309//                byte[] bytes = new byte[len];
310//                rnd.nextBytes(bytes);
311//
312//                String s = new String(bytes, StandardCharsets.ISO_8859_1);
313//
314//                writer.write(CharBuffer.wrap(s), binary, true);
315//                binary.flip();
316//                reader.read(binary, text);
317//                text.flip();
318//                assertEquals(text.toString(), s);
319//            }
320//        }
321//    }
322
323    // TODO: atomic failures: e.g. readonly/overflow
324
325    private static byte[] bytes(int... data) {
326        byte[] bytes = new byte[data.length];
327        for (int i = 0; i < data.length; i++) {
328            bytes[i] = (byte) data[i];
329        }
330        return bytes;
331    }
332
333    private static void verifyRead(byte[] data, int expected, int N) {
334        ByteBuffer buf = ByteBuffer.wrap(data, 0, data.length);
335        IntegerReader reader = new IntegerReader();
336        reader.configure(N).read(buf);
337        assertEquals(expected, reader.get());
338    }
339
340    private void verifyWrite(byte[] expected, int data, int N) {
341        IntegerWriter w = new IntegerWriter();
342        ByteBuffer buf = ByteBuffer.allocate(2 * expected.length);
343        w.configure(data, N, 1).write(buf);
344        buf.flip();
345        assertEquals(ByteBuffer.wrap(expected), buf);
346    }
347}
348