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