1 /*
   2  * Copyright (c) 2018, 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 
  24 import java.io.*;
  25 import java.nio.*;
  26 import java.util.*;
  27 import java.util.stream.*;
  28 import static java.nio.charset.StandardCharsets.*;
  29 import static java.util.Hex.*;
  30 
  31 /**
  32  * @test
  33  * @bug 8170769
  34  * @summary test hexadecimal conversions to/from binary data.
  35  */
  36 
  37 public class HexdumpTest {
  38     private static final String LINE_SEPARATOR = String.format("%n");
  39 
  40     /**
  41      * Formatter that generates a custom hexdump format (8-byte chunks).
  42      */
  43     public static final Hex.Formatter CUSTOM_8_HEXDUMP_FORMATTER = new Hex.Formatter() {
  44         public String format(long offset, byte[] chunk, int fromIndex, int toIndex) {
  45             return String.format("%04d %-16s %s",
  46                 offset,
  47                 Hex.toString(chunk, fromIndex, toIndex),
  48                 Hex.toPrintableString(chunk, fromIndex, toIndex));
  49         }
  50     };
  51 
  52     /**
  53      * Formatter that generates a custom hexdump format (32-byte chunks).
  54      */
  55     public static final Hex.Formatter CUSTOM_32_HEXDUMP_FORMATTER = new Hex.Formatter() {
  56         public String format(long offset, byte[] chunk, int fromIndex, int toIndex) {
  57             return String.format("%04d %-64s %s",
  58                 offset,
  59                 Hex.toString(chunk, fromIndex, toIndex),
  60                 Hex.toPrintableString(chunk, fromIndex, toIndex));
  61         }
  62     };
  63 
  64     /**
  65      * Formatter that generates a custom hexdump format (supports Latin-1).
  66      */
  67     public static final Hex.Formatter CUSTOM_LATIN1_HEXDUMP_FORMATTER = new Hex.Formatter() {
  68 
  69         public String format(long offset, byte[] chunk, int fromIndex, int toIndex) {
  70             return String.format("%04d %s %s",
  71                 offset,
  72                 Hex.toFormattedHexString(chunk, fromIndex, toIndex),
  73                 new String(chunk, fromIndex, toIndex - fromIndex, ISO_8859_1).replaceAll("[\\x00-\\x1F\\x7F]", "."));
  74         }
  75     };
  76 
  77     public static final void main(String[] args) throws Exception {
  78 
  79         // Test data for byte array
  80         List<byte[]> byteArrayData = new ArrayList<>() {{
  81             add(new byte[]{ 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18 });
  82             add(new byte[]{ 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 });
  83             add(new byte[]{ 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14 });
  84             add(new byte[]{ (byte)0, (byte)0, (byte)134, (byte)0, (byte)61 });
  85             add(new byte[]{ (byte)0x00, (byte)0x01, (byte)0x02 });
  86             add(new byte[]{ (byte)0x00, (byte)0x01 });
  87             add(new byte[]{ (byte)0x00 });
  88             add(new byte[0]);
  89             add(new byte[]{ 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,
  90                 49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,
  91                 70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,
  92                 91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,
  93                 109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,
  94                 125,126,127 });
  95         }};
  96 
  97         // Test data for byte array (Latin1)
  98         List<byte[]> latin1ByteArrayData = new ArrayList<>() {{
  99             add(new byte[]{ (byte)192, (byte)193, (byte)194, (byte)195, (byte)196, (byte)197, (byte)198, (byte)199,
 100                             (byte)200, (byte)201, (byte)202, (byte)203, (byte)204, (byte)205, (byte)206, (byte)207 });
 101             add(new byte[]{ (byte)192, 1, (byte)193, 2, (byte)194, 3, (byte)195, 4, (byte)196, 5, (byte)197, 6 });
 102             add(new byte[]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
 103                             (byte)192, (byte)193, (byte)194, (byte)195, (byte)196, (byte)197, (byte)198, (byte)199,
 104                             (byte)200, (byte)201, (byte)202, (byte)203, (byte)204, (byte)205, (byte)206, (byte)207 });
 105         }};
 106 
 107         // Test data for String
 108         List<String> stringData = new ArrayList<>() {{
 109             add("000102030405060708090a0b0c0d0e0f101112");
 110             add("000102030405060708090a0b0c0d0e0f");
 111             add("000102030405060708090a0b0c0d0e");
 112             add("000086003d");
 113             add("000102");
 114             add("0001");
 115             add("00");
 116             add("");
 117             add("202122232425262728292a2b2c2d2e2f" +
 118                 "303132333435363738393a3b3c3d3e3f" +
 119                 "404142434445464748494a4b4c4d4e4f" +
 120                 "505152535455565758595a5b5c5d5e5f" +
 121                 "606162636465666768696a6b6c6d6e6f" +
 122                 "707172737475767778797a7b7c7d7e7f");
 123         }};
 124 
 125         // Test data for Stream of String
 126         List<List<String>> streamData = new ArrayList<>() {{
 127             add(List.of(
 128                 "00000000  00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f  |................|",
 129                 "00000010  10 11 12                                          |...|"));
 130             add(List.of(
 131                 "00000000  00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f  |................|"
 132                 ));
 133             add(List.of(
 134                 "00000000  00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e     |...............|"
 135                 ));
 136             add(List.of(
 137                 "00000000  00 00 86 00 3d                                    |....=|"));
 138             add(List.of(
 139                 "00000000  00 01 02                                          |...|"));
 140             add(List.of(
 141                 "00000000  00 01                                             |..|"));
 142             add(List.of(
 143                 "00000000  00                                                |.|"));
 144             add(Collections.emptyList());
 145             add(List.of(
 146                 "00000000  20 21 22 23 24 25 26 27  28 29 2a 2b 2c 2d 2e 2f  | !\"#$%&'()*+,-./|",
 147                 "00000010  30 31 32 33 34 35 36 37  38 39 3a 3b 3c 3d 3e 3f  |0123456789:;<=>?|",
 148                 "00000020  40 41 42 43 44 45 46 47  48 49 4a 4b 4c 4d 4e 4f  |@ABCDEFGHIJKLMNO|",
 149                 "00000030  50 51 52 53 54 55 56 57  58 59 5a 5b 5c 5d 5e 5f  |PQRSTUVWXYZ[\\]^_|",
 150                 "00000040  60 61 62 63 64 65 66 67  68 69 6a 6b 6c 6d 6e 6f  |`abcdefghijklmno|",
 151                 "00000050  70 71 72 73 74 75 76 77  78 79 7a 7b 7c 7d 7e 7f  |pqrstuvwxyz{|}~.|"
 152                 ));
 153         }};
 154 
 155         // Test data for Stream of String (subarray)
 156         List<List<String>> subarrayStreamData = new ArrayList<>() {{
 157             add(List.of(
 158                 "00000000  01 02 03 04 05 06 07 08  09 0a 0b 0c 0d 0e 0f 10  |................|",
 159                 "00000010  11                                                |.|"));
 160             add(List.of(
 161                 "00000000  01 02 03 04 05 06 07 08  09 0a 0b 0c 0d 0e        |..............|"
 162                 ));
 163             add(List.of(
 164                 "00000000  01 02 03 04 05 06 07 08  09 0a 0b 0c 0d           |.............|"
 165                 ));
 166             add(List.of(
 167                 "00000000  00 86 00                                          |...|"));
 168             add(List.of(
 169                 "00000000  01                                                |.|"));
 170             add(Collections.emptyList()); // skipped, too short
 171             add(Collections.emptyList()); // skipped, too short
 172             add(Collections.emptyList()); // skipped, too short
 173             add(List.of(
 174                 "00000000  21 22 23 24 25 26 27 28  29 2a 2b 2c 2d 2e 2f 30  |!\"#$%&'()*+,-./0|",
 175                 "00000010  31 32 33 34 35 36 37 38  39 3a 3b 3c 3d 3e 3f 40  |123456789:;<=>?@|",
 176                 "00000020  41 42 43 44 45 46 47 48  49 4a 4b 4c 4d 4e 4f 50  |ABCDEFGHIJKLMNOP|",
 177                 "00000030  51 52 53 54 55 56 57 58  59 5a 5b 5c 5d 5e 5f 60  |QRSTUVWXYZ[\\]^_`|",
 178                 "00000040  61 62 63 64 65 66 67 68  69 6a 6b 6c 6d 6e 6f 70  |abcdefghijklmnop|",
 179                 "00000050  71 72 73 74 75 76 77 78  79 7a 7b 7c 7d 7e        |qrstuvwxyz{|}~|"
 180                 ));
 181         }};
 182 
 183         // Test data for Stream of custom String
 184         List<List<String>> customStreamData = new ArrayList<>() {{
 185             add(List.of(
 186                 "0000 000102030405060708090a0b0c0d0e0f101112                           ..................."));
 187             add(List.of(
 188                 "0000 000102030405060708090a0b0c0d0e0f                                 ................"));
 189             add(List.of(
 190                 "0000 000102030405060708090a0b0c0d0e                                   ..............."));
 191             add(List.of(
 192                 "0000 000086003d                                                       ....="));
 193             add(List.of(
 194                 "0000 000102                                                           ..."));
 195             add(List.of(
 196                 "0000 0001                                                             .."));
 197             add(List.of(
 198                 "0000 00                                                               ."));
 199             add(Collections.emptyList());
 200             add(List.of(
 201                 "0000 202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f  !\"#$%&'()*+,-./0123456789:;<=>?",
 202                 "0032 404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_",
 203                 "0064 606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f `abcdefghijklmnopqrstuvwxyz{|}~."
 204                 ));
 205         }};
 206 
 207         // Test data for Stream of custom Latin-1 String
 208         List<List<String>> customLatin1StreamData = new ArrayList<>() {{
 209             add(List.of(
 210                 "0000 c0 c1 c2 c3 c4 c5 c6 c7  c8 c9 ca cb cc cd ce cf ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ"
 211                 ));
 212             add(List.of(
 213                 "0000 c0 01 c1 02 c2 03 c3 04  c4 05 c5 06             À.Á.Â.Ã.Ä.Å."
 214                 ));
 215             add(List.of(
 216                 "0000 00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f ................",
 217                 "0016 c0 c1 c2 c3 c4 c5 c6 c7  c8 c9 ca cb cc cd ce cf ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ"
 218                 ));
 219         }};
 220 
 221         // Test data for Stream of custom String (byteBuffer)
 222         List<List<String>> byteBufferStreamData = new ArrayList<>() {{
 223             add(List.of(
 224                 "0000 0001020304050607 ........",
 225                 "0008 08090a0b0c0d0e0f ........",
 226                 "0016 101112           ..."
 227                 ));
 228             add(List.of(
 229                 "0000 0001020304050607 ........",
 230                 "0008 08090a0b0c0d0e0f ........"
 231                 ));
 232             add(List.of(
 233                 "0000 0001020304050607 ........",
 234                 "0008 08090a0b0c0d0e   ......."
 235                 ));
 236             add(List.of(
 237                 "0000 000086003d       ....="
 238                 ));
 239             add(List.of(
 240                 "0000 000102           ..."
 241                 ));
 242             add(List.of(
 243                 "0000 0001             .."
 244                 ));
 245             add(List.of(
 246                 "0000 00               ."
 247                 ));
 248             add(Collections.emptyList());
 249             add(List.of(
 250                 "0000 2021222324252627  !\"#$%&'",
 251                 "0008 28292a2b2c2d2e2f ()*+,-./",
 252                 "0016 3031323334353637 01234567",
 253                 "0024 38393a3b3c3d3e3f 89:;<=>?",
 254                 "0032 4041424344454647 @ABCDEFG",
 255                 "0040 48494a4b4c4d4e4f HIJKLMNO",
 256                 "0048 5051525354555657 PQRSTUVW",
 257                 "0056 58595a5b5c5d5e5f XYZ[\\]^_",
 258                 "0064 6061626364656667 `abcdefg",
 259                 "0072 68696a6b6c6d6e6f hijklmno",
 260                 "0080 7071727374757677 pqrstuvw",
 261                 "0088 78797a7b7c7d7e7f xyz{|}~."
 262                 ));
 263         }};
 264 
 265         // Testing byte array conversions to hex string
 266         System.out.println("----------");
 267         for (int i = 0; i < byteArrayData.size(); i++) {
 268             byte[] input = byteArrayData.get(i);
 269             String expected = stringData.get(i);
 270             String output = Hex.toString(input);
 271             if (expected.equals(output)) {
 272                 System.out.println((i + 1) + ") Generated hex string: \"" + output + "\"");
 273             } else {
 274                 throw new Exception("ERROR: expected: \"" + expected +
 275                     "\" but received: \"" + output + "\"");
 276             }
 277         }
 278 
 279         // Testing subarray conversions to hex string
 280         System.out.println("----------");
 281         for (int i = 0; i < byteArrayData.size(); i++) {
 282             byte[] input = byteArrayData.get(i);
 283             if (input.length < 2) {
 284                 System.out.println((i + 1) + ") Input too short - skipping...");
 285                 continue;
 286             }
 287             String expected = stringData.get(i).toLowerCase();
 288             expected = expected.substring(2, expected.length() - 2);
 289             String output = Hex.toString(input, 1, input.length - 1);
 290             if (expected.equals(output)) {
 291                 System.out.println((i + 1) +
 292                     ") Generated subarray hex string: \"" + output + "\"");
 293             } else {
 294                 throw new Exception("ERROR: expected: \"" + expected +
 295                     "\" but received: \"" + output + "\"");
 296             }
 297         }
 298 
 299         // Testing conversions from hex string
 300         System.out.println("----------");
 301         for (int i = 0; i < stringData.size(); i++) {
 302             String input = stringData.get(i);
 303             byte[] expected = byteArrayData.get(i);
 304             byte[] output = Hex.fromString(input);
 305             if (Arrays.equals(expected, output)) {
 306                 System.out.println((i + 1) + ") Parsed hex string: \"" + input + "\"");
 307             } else {
 308                 throw new Exception("ERROR: expected: " +
 309                     Arrays.toString(expected) + " but received: " +
 310                     Arrays.toString(output));
 311             }
 312         }
 313 
 314         // Testing conversions to stream of hexdump string
 315         System.out.println("----------");
 316         for (int i = 0; i < byteArrayData.size(); i++) {
 317             byte[] input = byteArrayData.get(i);
 318             Stream<String> expected =
 319                 Stream.of(streamData.get(i).toArray(new String[0]));
 320             Stream<String> output = Hex.dumpAsStream(input);
 321             Object[] expectedArray = expected.toArray();
 322             System.out.println((i + 1) + ") Generating stream of hexdump string: (from byte array)");
 323             if (Arrays.equals(expectedArray, output.toArray())) {
 324                 Hex.dumpAsStream(input).forEach(System.out::println);
 325             } else {
 326                 throw new Exception(
 327                     "ERROR: expected this stream of hexdump string: " +
 328                     Arrays.toString(expectedArray) + " but received: " +
 329                     Arrays.toString(Hex.dumpAsStream(input).toArray()));
 330             }
 331         }
 332 
 333         // Testing subarray conversions to stream of hexdump string
 334         System.out.println("----------");
 335         for (int i = 0; i < byteArrayData.size(); i++) {
 336             byte[] input = byteArrayData.get(i);
 337             if (input.length < 2) {
 338                 System.out.println((i + 1) + ") Input too short - skipping...");
 339                 continue;
 340             }
 341             Stream<String> expected =
 342                 Stream.of(subarrayStreamData.get(i).toArray(new String[0]));
 343             Stream<String> output =
 344                 Hex.dumpAsStream(input, 1, input.length - 1, 16, null);
 345             Object[] expectedArray = expected.toArray();
 346             System.out.println((i + 1) + ") Generating stream of hexdump string: (from byte subarray)");
 347             if (Arrays.equals(expectedArray, output.toArray())) {
 348                 Hex.dumpAsStream(input, 1, input.length - 1, 16, null)
 349                     .forEach(System.out::println);
 350             } else {
 351                 throw new Exception(
 352                     "ERROR: expected this stream of hexdump string: " +
 353                     Arrays.toString(expectedArray) + " but received: " +
 354                     Arrays.toString(
 355                         Hex.dumpAsStream(input, 1, input.length - 1, 16, null)
 356                             .toArray()));
 357             }
 358         }
 359 
 360         // Testing subarray conversions to stream of hexdump string
 361         System.out.println("----------");
 362         for (int i = 0; i < byteArrayData.size(); i++) {
 363             byte[] input = byteArrayData.get(i);
 364             if (input.length < 2) {
 365                 System.out.println((i + 1) + ") Input too short - skipping...");
 366                 continue;
 367             }
 368             Stream<String> expected =
 369                 Stream.of(subarrayStreamData.get(i).toArray(new String[0]));
 370             Stream<String> output =
 371                 Hex.dumpAsStream(Arrays.copyOfRange(input, 1, input.length - 1));
 372             Object[] expectedArray = expected.toArray();
 373             System.out.println((i + 1) + ") Generating stream of hexdump string: (from byte subarray)");
 374             if (Arrays.equals(expectedArray, output.toArray())) {
 375                 Hex.dumpAsStream(Arrays.copyOfRange(input, 1, input.length - 1))
 376                     .forEach(System.out::println);
 377             } else {
 378                 throw new Exception(
 379                     "ERROR: expected this stream of hexdump string: " +
 380                     Arrays.toString(expectedArray) + " but received: " +
 381                     Arrays.toString(
 382                         Hex.dumpAsStream(Arrays.copyOfRange(input, 1, input.length - 1))
 383                             .toArray()));
 384             }
 385         }
 386 
 387         // Testing conversions to stream of custom hexdump string using 32-byte chunks
 388         System.out.println("----------");
 389         for (int i = 0; i < byteArrayData.size(); i++) {
 390             byte[] input = byteArrayData.get(i);
 391             Stream<String> expected =
 392                 Stream.of(customStreamData.get(i).toArray(new String[0]));
 393             Stream<String> output = Hex.dumpAsStream(input, 0, input.length, 32, CUSTOM_32_HEXDUMP_FORMATTER);
 394             Object[] expectedArray = expected.toArray();
 395             System.out.println((i + 1) + ") Generating stream of custom hexdump string: (from byte array)");
 396             if (Arrays.equals(expectedArray, output.toArray())) {
 397                 Hex.dumpAsStream(input, 0, input.length, 32, CUSTOM_32_HEXDUMP_FORMATTER)
 398                     .forEach(System.out::println);
 399             } else {
 400                 throw new Exception(
 401                     "ERROR: expected this stream of hexdump string: " +
 402                     Arrays.toString(expectedArray) + " but received: " +
 403                     Arrays.toString(Hex.dumpAsStream(input, 0, input.length, 32, CUSTOM_32_HEXDUMP_FORMATTER)
 404                         .toArray()));
 405             }
 406         }
 407 
 408         // Testing conversions to stream of custom hexdump string using Latin-1
 409         System.out.println("----------");
 410         for (int i = 0; i < latin1ByteArrayData.size(); i++) {
 411             byte[] input = latin1ByteArrayData.get(i);
 412             Stream<String> expected =
 413                 Stream.of(customLatin1StreamData.get(i).toArray(new String[0]));
 414             Stream<String> output = Hex.dumpAsStream(input, 0, input.length, 16, CUSTOM_LATIN1_HEXDUMP_FORMATTER);
 415             Object[] expectedArray = expected.toArray();
 416             System.out.println((i + 1) + ") Generating stream of custom Latin-1 hexdump string: (from byte array)");
 417             if (Arrays.equals(expectedArray, output.toArray())) {
 418                 Hex.dumpAsStream(input, 0, input.length, 16, CUSTOM_LATIN1_HEXDUMP_FORMATTER)
 419                     .forEach(System.out::println);
 420             } else {
 421                 throw new Exception(
 422                     "ERROR: expected this stream of hexdump string: " +
 423                     Arrays.toString(expectedArray) + " but received: " +
 424                     Arrays.toString(Hex.dumpAsStream(input, 0, input.length, 16, CUSTOM_LATIN1_HEXDUMP_FORMATTER)
 425                         .toArray()));
 426             }
 427         }
 428 
 429         // Testing ByteBuffer conversions to stream of custom hexdump string using 8-byte chunks
 430         System.out.println("----------");
 431         for (int i = 0; i < byteArrayData.size(); i++) {
 432             byte[] input = byteArrayData.get(i);
 433             Stream<String> expected =
 434                 Stream.of(byteBufferStreamData.get(i).toArray(new String[0]));
 435             Stream<String> output =
 436                 Hex.dumpAsStream(ByteBuffer.wrap(input), 0, input.length, 8, CUSTOM_8_HEXDUMP_FORMATTER);
 437             Object[] expectedArray = expected.toArray();
 438             System.out.println((i + 1) + ") Generating stream of custom hexdump string: (from ByteBuffer)");
 439             if (Arrays.equals(expectedArray, output.toArray())) {
 440                 Hex.dumpAsStream(ByteBuffer.wrap(input), 0, input.length, 8, CUSTOM_8_HEXDUMP_FORMATTER)
 441                     .forEach(System.out::println);
 442             } else {
 443                 throw new Exception(
 444                     "ERROR: expected this stream of custom hexdump string: " +
 445                     Arrays.toString(expectedArray) + " but received: " +
 446                     Arrays.toString(
 447                         Hex.dumpAsStream(ByteBuffer.wrap(input), 0, input.length, 8, CUSTOM_8_HEXDUMP_FORMATTER).toArray()));
 448             }
 449         }
 450     }
 451 }