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  */
  23 package jdk.incubator.http.internal.hpack;
  24 
  25 import org.testng.annotations.Test;
  26 
  27 import java.nio.ByteBuffer;
  28 import java.nio.CharBuffer;
  29 import java.nio.charset.StandardCharsets;
  30 import java.util.ArrayList;
  31 import java.util.List;
  32 import java.util.Random;
  33 
  34 import static org.testng.Assert.assertEquals;
  35 import static org.testng.Assert.fail;
  36 import static jdk.incubator.http.internal.hpack.BuffersTestingKit.*;
  37 import 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 //
  43 public 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 }