1 /* 2 * Copyright (c) 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.util; 27 28 import java.io.*; 29 import java.util.*; 30 import java.util.stream.*; 31 32 /** 33 * Converts hexadecimal (base 16) string representations to and from 34 * binary data. It can also generate the classic Unix hexdump(1) format. 35 * <p> 36 * <b>Example usage:</b> 37 * <pre>{@code 38 * // Display the hexadecimal representation of the local Internet Protocol (IP) address 39 * System.out.println(Hex.toString(InetAddress.getLocalHost().getAddress())); 40 * 41 * // Initialize a 16-byte array from a hexadecimal string 42 * byte[] bytes = Hex.fromString("a1a2a3a4a5a6a7a8a9aaabacadaeaf"); 43 * 44 * // Dump a Java class file to the standard output stream 45 * Hex.dump(Files.readAllBytes(Paths.get("MyApp.class")), System.out); 46 * 47 * // Dump a file to the standard output stream, skipping blocks of content comprising all zeros 48 * try (InputStream in = new FileInputStream("mydata.bin")) { 49 * Hex.dumpAsStream(in) 50 * .filter(s -> 51 * !s.contains("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00")) 52 * .forEachOrdered(System.out::println); 53 * } 54 * 55 * // Write the hexadecimal representation of a file to the standard output stream in 32-byte chunks 56 * Hex.stream(Files.readAllBytes(Paths.get("mydata.bin")), 32) 57 * .forEachOrdered(System.out::println); 58 * }</pre> 59 * 60 * @since 10 61 */ 62 public final class Hex { 63 64 private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray(); 65 private static final int CHUNK_SIZE = 16; 66 67 private Hex() {} 68 69 /** 70 * Returns a hexadecimal string representation of the contents of a 71 * binary buffer. 72 * <p> 73 * The binary value is converted to a string comprising pairs of 74 * hexadecimal digits using only the following ASCII characters: 75 * <blockquote> 76 * {@code 0123456789abcdef} 77 * </blockquote> 78 * 79 * @param bytes a binary buffer 80 * @return a hexadecimal string representation of the binary buffer. 81 * The string length is twice the buffer length. 82 * @throws NullPointerException if {@code bytes} is {@code null} 83 */ 84 public static String toString(byte[] bytes) { 85 Objects.requireNonNull(bytes, "bytes"); 86 return toString(bytes, 0, bytes.length); 87 } 88 89 /** 90 * Returns a hexadecimal string representation of the contents of a 91 * range within a binary buffer. 92 * <p> 93 * The binary value is converted to a string comprising pairs of 94 * hexadecimal digits using only the following ASCII characters: 95 * <blockquote> 96 * {@code 0123456789abcdef} 97 * </blockquote> 98 * The range to be converted extends from index {@code fromIndex}, 99 * inclusive, to index {@code toIndex}, exclusive. 100 * (If {@code fromIndex==toIndex}, the range to be converted is empty.) 101 * 102 * @param bytes a binary buffer 103 * @param fromIndex the index of the first byte (inclusive) to be converted 104 * @param toIndex the index of the last byte (exclusive) to be converted 105 * @return a hexadecimal string representation of the binary buffer. 106 * The string length is twice the number of bytes converted. 107 * @throws NullPointerException if {@code bytes} is {@code null} 108 * @throws IllegalArgumentException if {@code fromIndex > toIndex} 109 * @throws ArrayIndexOutOfBoundsException 110 * if {@code fromIndex < 0} or {@code toIndex > bytes.length} 111 */ 112 public static String toString(byte[] bytes, int fromIndex, int toIndex) { 113 Objects.requireNonNull(bytes, "bytes"); 114 Arrays.rangeCheck(bytes.length, fromIndex, toIndex); 115 116 StringBuilder hexString = new StringBuilder((toIndex - fromIndex) * 2); 117 for (int i = fromIndex; i < toIndex; i++) { 118 hexString.append(HEX_DIGITS[(bytes[i] >> 4) & 0xF]); 119 hexString.append(HEX_DIGITS[(bytes[i] & 0xF)]); 120 } 121 return hexString.toString(); 122 } 123 124 /** 125 * Returns a stream of hexadecimal string representations of the contents 126 * of a binary buffer. 127 * <p> 128 * The binary values are converted to strings comprising pairs of 129 * hexadecimal digits using only the following ASCII characters: 130 * <blockquote> 131 * {@code 0123456789abcdef} 132 * </blockquote> 133 * 134 * @param bytes a binary buffer 135 * @param chunkSize the number of bytes per hexadecimal string 136 * (defaults to 16 if omitted) 137 * @return a stream of hexadecimal strings representating the binary buffer. 138 * Each string length is twice the {@code chunkSize} 139 * (but the final string may be shorter). 140 * @throws NullPointerException if {@code bytes} is {@code null} 141 */ 142 public static Stream<String> stream(byte[] bytes, int... chunkSize) { 143 Objects.requireNonNull(bytes, "bytes"); 144 return stream(bytes, 0, bytes.length, chunkSize); 145 } 146 147 /** 148 * Returns a stream of hexadecimal string representations of the contents 149 * of a range within a binary buffer. 150 * <p> 151 * The binary values are converted to strings comprising pairs of 152 * hexadecimal digits using only the following ASCII characters: 153 * <blockquote> 154 * {@code 0123456789abcdef} 155 * </blockquote> 156 * The range to be converted extends from index {@code fromIndex}, 157 * inclusive, to index {@code toIndex}, exclusive. 158 * (If {@code fromIndex==toIndex}, the range to be converted is empty.) 159 * 160 * TBD 161 * @param bytes a binary buffer 162 * @param fromIndex the index of the first byte (inclusive) to be converted 163 * @param toIndex the index of the last byte (exclusive) to be converted 164 * @param chunkSize the number of bytes per hexadecimal string 165 * (defaults to 16 if omitted) 166 * @return a stream of hexadecimal strings representating the binary buffer. 167 * Each string length is twice the {@code chunkSize} 168 * (but the final string may be shorter). 169 * @throws NullPointerException if {@code bytes} is {@code null} 170 * @throws IllegalArgumentException if {@code fromIndex > toIndex} 171 * @throws ArrayIndexOutOfBoundsException 172 * if {@code fromIndex < 0} or {@code toIndex > bytes.length} 173 */ 174 public static Stream<String> stream(byte[] bytes, int fromIndex, 175 int toIndex, int... chunkSize) { 176 Objects.requireNonNull(bytes, "bytes"); 177 Arrays.rangeCheck(bytes.length, fromIndex, toIndex); 178 int range = toIndex - fromIndex; 179 int len = chunkSize.length > 0 ? chunkSize[0] : CHUNK_SIZE; 180 if (len > range) { 181 len = range; 182 } 183 184 return IntStream.range(0, roundUp(range, len)) 185 .mapToObj(i -> toString(bytes, fromIndex, toIndex)); 186 } 187 188 // /** 189 // * Returns a stream of hexadecimal strings from the contents of an input 190 // * stream. 191 // * 192 // * TBD 193 // */ 194 // public static Stream<String> stream(InputStream in, int... chunkSize) { 195 // int chunkSize = chunkSize.length > 0 ? chunkSize[0] : CHUNK_SIZE; 196 // } 197 198 /** 199 * Returns a binary buffer coresponding to the sequence of hexadecimal 200 * digits. 201 * <p> 202 * The binary value is generated from pairs of hexadecimal digits that use 203 * only the following ASCII characters: 204 * <blockquote> 205 * {@code 0123456789abcdef} 206 * </blockquote> 207 * 208 * @param hexString an even numbered sequence of hexadecimal digits 209 * @return a binary buffer 210 * @throws IllegalArgumentException if {@code hexString} has an odd number 211 * of digits or contains an illegal hexadecimal character 212 * @throws NullPointerException if {@code hexString} is {@code null} 213 */ 214 public static byte[] fromString(CharSequence hexString) { 215 Objects.requireNonNull(hexString, "hexString"); 216 int len = hexString.length(); 217 if (len % 2 != 0) { 218 throw new IllegalArgumentException( 219 "contains an odd number of digits: " + hexString); 220 } 221 byte[] bytes = new byte[len / 2]; 222 223 for (int i = 0; i < len; i += 2) { 224 int high = hexToBinary(hexString.charAt(i)); 225 int low = hexToBinary(hexString.charAt(i + 1)); 226 if (high == -1 || low == -1) { 227 throw new IllegalArgumentException( 228 "contains an illegal hexadecimal character: " + hexString); 229 } 230 231 bytes[i / 2] = (byte) (high * 16 + low); 232 } 233 return bytes; 234 } 235 236 /** 237 * Returns a binary buffer coresponding to a range within the sequence of 238 * hexadecimal digits. 239 * <p> 240 * The binary value is generated from pairs of hexadecimal digits that use 241 * only the following ASCII characters: 242 * <blockquote> 243 * {@code 0123456789abcdef} 244 * </blockquote> 245 * 246 * @param hexString an even numbered sequence of hexadecimal digits 247 * @param fromIndex the index of the first digit (inclusive) to be converted 248 * @param toIndex the index of the last digit (exclusive) to be converted 249 * @return a binary buffer 250 * @throws IllegalArgumentException if {@code hexString} has an odd number 251 * of digits or contains an illegal hexadecimal character 252 * @throws NullPointerException if {@code hexString} is {@code null} 253 * @throws IllegalArgumentException if {@code fromIndex > toIndex} 254 * @throws ArrayIndexOutOfBoundsException 255 * if {@code fromIndex < 0} or {@code toIndex > hexString.length()} 256 */ 257 public static byte[] fromString(CharSequence hexString, int fromIndex, 258 int toIndex) { 259 Objects.requireNonNull(hexString, "hexString"); 260 return fromString(hexString, 0, hexString.length()); 261 } 262 263 /** 264 * Generates a hexadecimal dump of the contents of a binary buffer, 265 * as a stream of hexadecimal strings. 266 * <p> 267 * It outputs the same format as {@link #dump(byte[],OutputStream)}, 268 * without the line separator characters. 269 * When the input is not a multiple of 16-bytes then the final chunk 270 * is shorter than 16-bytes. 271 * 272 * @param bytes a binary buffer 273 * @return a stream of hexadecimal strings 274 * @throws NullPointerException if {@code bytes} is {@code null} 275 */ 276 public static Stream<String> dumpAsStream(byte[] bytes) { 277 Objects.requireNonNull(bytes, "bytes"); 278 return dumpAsStream(bytes, 0, bytes.length); 279 } 280 281 /** 282 * Generates a hexadecimal dump of the contents of a range within a binary 283 * buffer, as a stream of hexadecimal strings. 284 * <p> 285 * It outputs the same format as {@link #dump(byte[],OutputStream)}, 286 * without the line separator characters. 287 * When the input is not a multiple of 16-bytes then the final chunk 288 * is shorter than 16-bytes. 289 * The range to be converted extends from index {@code fromIndex}, 290 * inclusive, to index {@code toIndex}, exclusive. 291 * (If {@code fromIndex==toIndex}, the range to be converted is empty.) 292 * 293 * @param bytes a binary buffer 294 * @param fromIndex the index of the first byte (inclusive) to be converted 295 * @param toIndex the index of the last byte (exclusive) to be converted 296 * @return a stream of hexadecimal strings 297 * @throws NullPointerException if {@code bytes} is {@code null} 298 * @throws IllegalArgumentException if {@code fromIndex > toIndex} 299 * @throws ArrayIndexOutOfBoundsException 300 * if {@code fromIndex < 0} or {@code toIndex > bytes.length} 301 */ 302 public static Stream<String> dumpAsStream(byte[] bytes, int fromIndex, 303 int toIndex) { 304 Objects.requireNonNull(bytes, "bytes"); 305 Arrays.rangeCheck(bytes.length, fromIndex, toIndex); 306 307 return IntStream.range(0, roundUp(toIndex - fromIndex, CHUNK_SIZE)) 308 .mapToObj(i -> chunk(i, bytes, fromIndex, toIndex)); 309 } 310 311 /** 312 * Generates a hexadecimal dump of the contents of an input stream, 313 * as a stream of hexadecimal strings. 314 * <p> 315 * It outputs the same format as {@link #dump(byte[],OutputStream)}, 316 * without the line separator characters. 317 * When the input is not a multiple of 16-bytes then the final chunk 318 * is shorter than 16-bytes. 319 * <p> 320 * On return, the input stream will be at end of stream. 321 * This method does not close the input stream and may block indefinitely 322 * reading from it. The behavior for the case where it is 323 * <i>asynchronously closed</i>, or the thread interrupted, 324 * is highly input stream specific, and therefore not specified. 325 * <p> 326 * If an I/O error occurs reading from the input stream then it may not be 327 * at end of stream and may be in an inconsistent state. It is strongly 328 * recommended that the input stream be promptly closed if an I/O error 329 * occurs. 330 * 331 * @param in the input stream, non-null 332 * @return a stream of hexadecimal strings 333 * @throws NullPointerException if {@code in} is {@code null} 334 */ 335 public static Stream<String> dumpAsStream(InputStream in) { 336 Objects.requireNonNull(in, "in"); 337 338 Iterator<String> iterator = new Iterator<>() { 339 String nextChunk = null; 340 int counter = 0; 341 342 @Override 343 public boolean hasNext() { 344 if (nextChunk != null) { 345 return true; 346 } else { 347 try { 348 nextChunk = readChunk(in, counter); 349 return (nextChunk != null); 350 } catch (IOException e) { 351 throw new UncheckedIOException(e); 352 } 353 } 354 } 355 356 @Override 357 public String next() { 358 if (nextChunk != null || hasNext()) { 359 String chunk = nextChunk; 360 nextChunk = null; 361 counter++; 362 return chunk; 363 } else { 364 throw new NoSuchElementException(); 365 } 366 } 367 }; 368 369 return StreamSupport.stream( 370 Spliterators.spliteratorUnknownSize( 371 iterator, Spliterator.ORDERED | Spliterator.NONNULL), 372 false); 373 } 374 375 /** 376 * Generates a hexadecimal dump of the contents of a binary buffer and 377 * writes it to the output stream. 378 * <p> 379 * This is useful when analyzing binary data. 380 * The general output format is as follows: 381 * <pre> 382 * xxxxxxxx 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff |................| 383 * </pre> 384 * where '{@code xxxxxxxx}' is the offset into the buffer in 16-byte chunks, 385 * followed by ASCII coded hexadecimal bytes, followed by the ASCII 386 * representation of the bytes, or {@code '.'} if it is non-printable. 387 * A non-printable character is one outside the ASCII range 388 * {@code ' '} through {@code '~'} 389 * ({@code '\u005Cu0020'} through {@code '\u005Cu007E'}). 390 * Output lines are separated by the platform-specific line separator. 391 * When the input is not a multiple of 16-bytes then the final line is 392 * shorter than normal. 393 * <p> 394 * This method does not close the output stream and may block indefinitely 395 * writing to it. The behavior for the case where it is 396 * <i>asynchronously closed</i>, or the thread interrupted, 397 * is highly output stream specific, and therefore not specified. 398 * <p> 399 * If an I/O error occurs writing to the output stream, then it may be 400 * in an inconsistent state. It is strongly recommended that the output 401 * stream be promptly closed if an I/O error occurs. 402 * 403 * @param bytes the binary buffer 404 * @param out the output stream, non-null 405 * @throws IOException if an I/O error occurs when writing 406 * @throws NullPointerException if {@code bytes} or {@code out} is 407 * {@code null} 408 */ 409 public static void dump(byte[] bytes, OutputStream out) throws IOException { 410 Objects.requireNonNull(bytes, "bytes"); 411 dump(bytes, 0, bytes.length, out); 412 } 413 414 /** 415 * Generates a hexadecimal dump of the contents of a range within a 416 * binary buffer and writes it to the output stream. 417 * It outputs the same format as {@link #dump(byte[],OutputStream)}. 418 * <p> 419 * The range to be converted extends from index {@code fromIndex}, 420 * inclusive, to index {@code toIndex}, exclusive. 421 * (If {@code fromIndex==toIndex}, the range to be converted is empty.) 422 * <p> 423 * This method does not close the output stream and may block indefinitely 424 * writing to it. The behavior for the case where it is 425 * <i>asynchronously closed</i>, or the thread interrupted, 426 * is highly output stream specific, and therefore not specified. 427 * <p> 428 * If an I/O error occurs writing to the output stream, then it may be 429 * in an inconsistent state. It is strongly recommended that the output 430 * stream be promptly closed if an I/O error occurs. 431 * 432 * @param bytes the binary buffer 433 * @param fromIndex the index of the first byte (inclusive) to be converted 434 * @param toIndex the index of the last byte (exclusive) to be converted 435 * @param out the output stream, non-null 436 * @throws IOException if an I/O error occurs when writing 437 * @throws NullPointerException if {@code bytes} or {@code out} is 438 * {@code null} 439 * @throws IllegalArgumentException if {@code fromIndex > toIndex} 440 * @throws ArrayIndexOutOfBoundsException 441 * if {@code fromIndex < 0} or {@code toIndex > bytes.length} 442 */ 443 public static void dump(byte[] bytes, int fromIndex, int toIndex, 444 OutputStream out) throws IOException { 445 dumpAsStream(bytes, fromIndex, toIndex) 446 .forEachOrdered(getPrintStream(out)::println); 447 } 448 449 /** 450 * Generates a hexadecimal dump of the contents of an input stream and 451 * writes it to the output stream. 452 * It outputs the same format as {@link #dump(byte[],OutputStream)}. 453 * <p> 454 * Reads all bytes from the input stream. 455 * On return, the input stream will be at end of stream. This method does 456 * not close either stream and may block indefinitely reading from the 457 * input stream, or writing to the output stream. The behavior for the case 458 * where the input and/or output stream is <i>asynchronously closed</i>, 459 * or the thread interrupted, is highly input stream and output stream 460 * specific, and therefore not specified. 461 * <p> 462 * If an I/O error occurs reading from the input stream or writing to the 463 * output stream, then it may do so after some bytes have been read or 464 * written. Consequently the input stream may not be at end of stream and 465 * one, or both, streams may be in an inconsistent state. It is strongly 466 * recommended that both streams be promptly closed if an I/O error occurs. 467 * 468 * @param in the input stream, non-null 469 * @param out the output stream, non-null 470 * @throws IOException if an I/O error occurs when reading or writing 471 * @throws NullPointerException if {@code in} or {@code out} is {@code null} 472 */ 473 public static void dump(InputStream in, OutputStream out) 474 throws IOException { 475 dumpAsStream(in) 476 .forEachOrdered(getPrintStream(out)::println); 477 } 478 479 //VR: TBD: check for (total + chunk - 1) > Integer.MAX_VALUE ?? 480 private static int roundUp(int total, int chunk) { 481 return (total + chunk - 1) / chunk; 482 } 483 484 private static String readChunk(InputStream inStream, int counter) 485 throws IOException { 486 byte[] buffer = new byte[CHUNK_SIZE]; 487 488 int n = inStream.readNBytes(buffer, 0, buffer.length); 489 if (n == 0) { 490 return null; 491 } 492 return chunk(counter, buffer, 0, n); 493 } 494 495 private static String chunk(int counter, byte[] bytes) { 496 int fromIndex = counter * CHUNK_SIZE; 497 int toIndex = fromIndex + CHUNK_SIZE; 498 if (toIndex > bytes.length) { 499 toIndex = bytes.length; 500 } 501 return chunk(counter, bytes, fromIndex, toIndex); 502 } 503 504 private static String chunk(int counter, byte[] bytes, int fromIndex, 505 int toIndex) { 506 StringBuilder hex = new StringBuilder(CHUNK_SIZE * 3 + 1); 507 StringBuilder ascii = new StringBuilder(CHUNK_SIZE); 508 boolean hasDivider = false; 509 510 for (int j = fromIndex; j < toIndex; j++) { 511 // Hex digits 512 hex.append(HEX_DIGITS[(bytes[j] >> 4) & 0xF]); 513 hex.append(HEX_DIGITS[(bytes[j] & 0xF)]); 514 if (j == fromIndex + (CHUNK_SIZE / 2 - 1)) { 515 hex.append(" "); 516 hasDivider = true; 517 } else { 518 hex.append(" "); 519 } 520 521 // Printable ASCII 522 if (bytes[j] < ' ' || bytes[j] > '~') { 523 ascii.append('.'); 524 } else { 525 ascii.append((char) bytes[j]); 526 } 527 } 528 529 // Pad the final chunk, if shorter 530 int chunkLength = toIndex - fromIndex; 531 if (chunkLength < CHUNK_SIZE) { 532 int padding = CHUNK_SIZE - chunkLength; 533 for (int k = 0; k < padding; k++) { 534 hex.append(" "); 535 ascii.append(' '); 536 } 537 if (!hasDivider) { 538 hex.append(' '); 539 } 540 } 541 542 return String.format("%08x %s |%s|", 543 counter * CHUNK_SIZE, hex.toString(), ascii.toString()); 544 } 545 546 private static PrintStream getPrintStream(OutputStream out) 547 throws IOException { 548 Objects.requireNonNull(out, "out"); 549 PrintStream ps = null; 550 if (out instanceof PrintStream) { 551 ps = (PrintStream) out; 552 } else { 553 ps = new PrintStream(out, true); // auto flush 554 } 555 return ps; 556 } 557 558 private static int hexToBinary(char ch) { 559 if ('0' <= ch && ch <= '9') { 560 return ch - '0'; 561 } 562 if ('A' <= ch && ch <= 'F') { 563 return ch - 'A' + 10; 564 } 565 if ('a' <= ch && ch <= 'f') { 566 return ch - 'a' + 10; 567 } 568 return -1; 569 } 570 }