1 /* 2 * Copyright (c) 2010, 2011, 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 org.openjdk.jigsaw; 27 28 import java.io.*; 29 import java.security.*; 30 import java.util.*; 31 import java.util.jar.*; 32 import java.util.zip.*; 33 34 import static org.openjdk.jigsaw.FileConstants.ModuleFile.*; 35 36 public final class ModuleFile { 37 /** 38 * Return the subdir of a section in an extracted module file. 39 */ 40 public static String getSubdirOfSection(SectionType type) { 41 switch (type) { 42 case MODULE_INFO: 43 case SIGNATURE: 44 return "."; 45 case CLASSES: 46 case RESOURCES: 47 return "classes"; 48 case NATIVE_LIBS: 49 return "lib"; 50 case NATIVE_CMDS: 51 return "bin"; 52 case CONFIG: 53 return "etc"; 54 default: 55 throw new AssertionError(type); 56 } 57 } 58 59 public final static class Reader implements Closeable { 60 61 private DataInputStream stream; 62 private File destination; 63 private boolean deflate; 64 private HashType hashtype; 65 66 private static class CountingInputStream extends FilterInputStream { 67 int count; 68 public CountingInputStream(InputStream stream, int count) { 69 super(stream); 70 this.count = count; 71 } 72 73 public int available() throws IOException { 74 return count; 75 } 76 77 public boolean markSupported() { 78 return false; 79 } 80 81 public int read() throws IOException { 82 if (count == 0) 83 return -1; 84 int read = super.read(); 85 if (-1 != read) 86 count--; 87 return read; 88 } 89 90 public int read(byte[] b, int off, int len) throws IOException { 91 if (count == 0) 92 return -1; 93 len = Math.min(len, count); 94 int read = super.read(b, off, len); 95 if (-1 != read) 96 count-=read; 97 return read; 98 } 99 100 public void reset() throws IOException { 101 throw new IOException("Can't reset this stream"); 102 } 103 104 public long skip(long n) throws IOException { 105 if (count == 0) 106 return -1; 107 n = Math.min(n, count); 108 long skipped = super.skip(n); 109 if (n > 0) 110 count-=skipped; 111 return skipped; 112 } 113 } 114 115 public Reader(DataInputStream stream) { 116 hashtype = HashType.SHA256; 117 // Ensure that mark/reset is supported 118 if (stream.markSupported()) { 119 this.stream = stream; 120 } else { 121 this.stream = 122 new DataInputStream(new BufferedInputStream(stream)); 123 } 124 } 125 126 private void checkHashMatch(byte[] expected, byte[] computed) 127 throws IOException 128 { 129 if (!MessageDigest.isEqual(expected, computed)) 130 throw new IOException("Expected hash " 131 + hashHexString(expected) 132 + " instead of " 133 + hashHexString(computed)); 134 } 135 136 private ModuleFileHeader fileHeader = null; 137 private MessageDigest fileDigest = null; 138 private MessageDigest sectionDigest = null; 139 private DataInputStream fileIn = null; 140 private byte[] moduleInfoBytes = null; 141 private Integer moduleSignatureType = null; 142 private byte[] moduleSignatureBytes = null; 143 private final int MAX_SECTION_HEADER_LENGTH = 128; 144 private List<byte[]> calculatedHashes = new ArrayList<>(); 145 private boolean extract = true; 146 147 /* 148 * Reads the MODULE_INFO section and the Signature section, if present, 149 * but does not write any files. 150 */ 151 public byte[] readStart() throws IOException { 152 153 try { 154 fileDigest = getHashInstance(hashtype); 155 sectionDigest = getHashInstance(hashtype); 156 DigestInputStream dis = 157 new DigestInputStream(stream, fileDigest); 158 fileHeader = ModuleFileHeader.read(dis); 159 // calculate module header hash 160 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 161 fileHeader.write(new DataOutputStream(baos)); 162 sectionDigest.update(baos.toByteArray()); 163 calculatedHashes.add(sectionDigest.digest()); 164 165 fileIn = new DataInputStream(dis); 166 if (readSection(fileIn) != SectionType.MODULE_INFO) 167 throw new IOException("First module-file section" 168 + " is not MODULE_INFO"); 169 assert moduleInfoBytes != null; 170 171 // Read the Signature Section, if present 172 readSignatureSection(fileIn, dis); 173 174 return moduleInfoBytes.clone(); 175 } catch (IOException x) { 176 close(); 177 throw x; 178 } 179 } 180 181 public void readRest() throws IOException { 182 extract = false; 183 readRest(null, false); 184 } 185 186 public void readRest(File dst, boolean deflate) throws IOException { 187 this.destination = dst; 188 this.deflate = deflate; 189 try { 190 if (extract) 191 Files.store(moduleInfoBytes, computeRealPath("info")); 192 // Module-Info and Signature, if present, have been consumed 193 194 // Read rest of file until all sections have been read 195 stream.mark(1); 196 while (-1 != stream.read()) { 197 stream.reset(); 198 readSection(fileIn); 199 stream.mark(1); 200 } 201 202 close(); 203 byte[] fileHeaderHash = fileHeader.getHashNoClone(); 204 checkHashMatch(fileHeaderHash, fileDigest.digest()); 205 calculatedHashes.add(fileHeaderHash); 206 } finally { 207 close(); 208 } 209 } 210 211 public byte[] getHash() throws IOException { 212 if (null == fileHeader) 213 readStart(); 214 return fileHeader.getHash(); 215 } 216 217 public List<byte[]> getCalculatedHashes() { 218 return calculatedHashes; 219 } 220 221 public boolean hasSignature() throws IOException { 222 if (null == fileHeader) 223 readStart(); 224 return moduleSignatureBytes != null; 225 } 226 227 public Integer getSignatureType() throws IOException { 228 if (null == fileHeader) 229 readStart(); 230 return moduleSignatureType; 231 } 232 233 public byte[] getSignature() throws IOException { 234 if (null == fileHeader) 235 readStart(); 236 return moduleSignatureBytes != null 237 ? moduleSignatureBytes.clone() 238 : null; 239 } 240 241 byte[] getSignatureNoClone() { 242 return moduleSignatureBytes; 243 } 244 245 private JarOutputStream contentStream = null; 246 247 private JarOutputStream contentStream() throws IOException { 248 if (contentStream == null) { 249 if (extract) { 250 FileOutputStream fos 251 = new FileOutputStream(computeRealPath("classes")); 252 contentStream 253 = new JarOutputStream(new BufferedOutputStream(fos)); 254 } else { 255 contentStream = new JarOutputStream(new NullOutputStream()); 256 } 257 } 258 return contentStream; 259 } 260 261 public void close() throws IOException { 262 try { 263 if (contentStream != null) { 264 contentStream.close(); 265 contentStream = null; 266 } 267 } finally { 268 if (fileIn != null) { 269 fileIn.close(); 270 fileIn = null; 271 } 272 } 273 } 274 275 public void readModule() throws IOException { 276 extract = false; 277 readStart(); 278 readRest(); 279 } 280 281 public void readModule(File dst) throws IOException { 282 readStart(); 283 readRest(dst, false); 284 } 285 286 private void readSignatureSection(DataInputStream stream, 287 DigestInputStream dis) 288 throws IOException 289 { 290 291 // Turn off digest computation before reading Signature Section 292 dis.on(false); 293 294 // Mark the starting position 295 stream.mark(MAX_SECTION_HEADER_LENGTH); 296 if (stream.read() != -1) { 297 stream.reset(); 298 SectionHeader header = SectionHeader.read(stream); 299 if (header != null && 300 header.getType() == SectionType.SIGNATURE) { 301 readSectionContent(header, stream); 302 } else { 303 // Revert back to the starting position 304 stream.reset(); 305 } 306 } 307 308 // Turn on digest computation again 309 dis.on(true); 310 } 311 312 private SectionType readSection(DataInputStream stream) 313 throws IOException 314 { 315 SectionHeader header = SectionHeader.read(stream); 316 readSectionContent(header, stream); 317 return header.getType(); 318 } 319 320 private void readSectionContent(SectionHeader header, 321 DataInputStream stream) 322 throws IOException 323 { 324 SectionType type = header.getType(); 325 Compressor compressor = header.getCompressor(); 326 int csize = header.getCSize(); 327 short subsections = 328 type.hasFiles() ? header.getSubsections() : 1; 329 330 CountingInputStream cs = new CountingInputStream(stream, csize); 331 sectionDigest.reset(); 332 DigestInputStream dis = new DigestInputStream(cs, sectionDigest); 333 DataInputStream in = new DataInputStream(dis); 334 335 for (int subsection = 0; subsection < subsections; subsection++) 336 readFile(in, compressor, type, csize); 337 338 byte[] headerHash = header.getHashNoClone(); 339 checkHashMatch(headerHash, sectionDigest.digest()); 340 if (header.getType() != SectionType.SIGNATURE) { 341 calculatedHashes.add(headerHash); 342 } 343 } 344 345 public void readFile(DataInputStream in, 346 Compressor compressor, 347 SectionType type, 348 int csize) 349 throws IOException 350 { 351 switch (compressor) { 352 case NONE: 353 if (type == SectionType.MODULE_INFO) { 354 moduleInfoBytes = readModuleInfo(in, csize); 355 356 } else if (type == SectionType.SIGNATURE) { 357 // Examine the Signature header 358 moduleSignatureType = (int)in.readShort(); 359 int length = in.readInt(); 360 moduleSignatureBytes = readModuleSignature(in, csize - 6); 361 if (length != moduleSignatureBytes.length) { 362 throw new IOException("Invalid Signature length"); 363 } 364 } else { 365 readUncompressedFile(in, type, csize); 366 } 367 break; 368 case GZIP: 369 readGZIPCompressedFile(in, type); 370 break; 371 case PACK200_GZIP: 372 readClasses( 373 new DataInputStream(new CountingInputStream(in, csize))); 374 break; 375 default: 376 throw new IOException("Unsupported Compressor for files: " + 377 compressor); 378 } 379 } 380 381 public void readClasses(DataInputStream in) throws IOException { 382 unpack200gzip(in); 383 } 384 385 private File currentPath = null; 386 387 private OutputStream openOutputStream(SectionType type, 388 String path) 389 throws IOException 390 { 391 if (!extract) 392 return new NullOutputStream(); 393 currentPath = null; 394 assert type != SectionType.CLASSES; 395 if (type == SectionType.RESOURCES) 396 return Files.newOutputStream(contentStream(), path); 397 currentPath = computeRealPath(type, path); 398 File parent = currentPath.getParentFile(); 399 if (!parent.exists()) 400 Files.mkdirs(parent, currentPath.getName()); 401 return new BufferedOutputStream(new FileOutputStream(currentPath)); 402 } 403 404 private static class NullOutputStream extends OutputStream { 405 @Override 406 public void write(int b) throws IOException {} 407 @Override 408 public void write(byte[] b) throws IOException {} 409 @Override 410 public void write(byte[] b, int off, int len) throws IOException {} 411 } 412 413 public void readGZIPCompressedFile(DataInputStream in, 414 SectionType type) 415 throws IOException 416 { 417 SubSectionFileHeader header = SubSectionFileHeader.read(in); 418 int csize = header.getCSize(); 419 420 // Splice off the compressed file from input stream 421 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 422 copyStream(new CountingInputStream(in, csize), baos, csize); 423 424 byte[] compressedfile = baos.toByteArray(); 425 ByteArrayInputStream bain 426 = new ByteArrayInputStream(compressedfile); 427 try (GZIPInputStream gin = new GZIPInputStream(bain); 428 OutputStream out = openOutputStream(type, header.getPath())) { 429 copyStream(gin, out); 430 } 431 432 if (extract) 433 markNativeCodeExecutable(type, currentPath); 434 } 435 436 public void readUncompressedFile(DataInputStream in, 437 SectionType type, 438 int csize) 439 throws IOException 440 { 441 assert type != SectionType.MODULE_INFO; 442 SubSectionFileHeader header = SubSectionFileHeader.read(in); 443 csize = header.getCSize(); 444 try (OutputStream out = openOutputStream(type, header.getPath())) { 445 CountingInputStream cin = new CountingInputStream(in, csize); 446 byte[] buf = new byte[8192]; 447 int n; 448 while ((n = cin.read(buf)) >= 0) 449 out.write(buf, 0, n); 450 } 451 markNativeCodeExecutable(type, currentPath); 452 } 453 454 public byte[] readModuleInfo(DataInputStream in, int csize) 455 throws IOException 456 { 457 CountingInputStream cin = new CountingInputStream(in, csize); 458 ByteArrayOutputStream out = new ByteArrayOutputStream(); 459 byte[] buf = new byte[8192]; 460 int n; 461 while ((n = cin.read(buf)) >= 0) 462 out.write(buf, 0, n); 463 return out.toByteArray(); 464 } 465 466 public byte[] readModuleSignature(DataInputStream in, int csize) 467 throws IOException 468 { 469 return readModuleInfo(in, csize); // signature has the same format 470 } 471 472 private File computeRealPath(String storedpath) throws IOException { 473 474 String convertedpath = storedpath.replace('/', File.separatorChar); 475 File path = new File(convertedpath); 476 477 // Absolute path names are not permitted. 478 ensureNonAbsolute(path); 479 path = resolveAndNormalize(destination, convertedpath); 480 // Create the parent directories if necessary 481 File parent = path.getParentFile(); 482 if (!parent.exists()) 483 Files.mkdirs(parent, path.getName()); 484 485 return path; 486 } 487 488 private File computeRealPath(SectionType type, 489 String storedpath) 490 throws IOException 491 { 492 String dir = getSubdirOfSection(type); 493 return computeRealPath(dir + File.separatorChar + storedpath); 494 } 495 496 private static void markNativeCodeExecutable(SectionType type, 497 File file) 498 { 499 if (type == SectionType.NATIVE_CMDS 500 || (type == SectionType.NATIVE_LIBS 501 && System.getProperty("os.name").startsWith("Windows"))) 502 { 503 file.setExecutable(true); 504 } 505 } 506 507 private void unpack200gzip(DataInputStream in) throws IOException { 508 GZIPInputStream gis = new GZIPInputStream(in) { 509 public void close() throws IOException {} 510 }; 511 Pack200.Unpacker unpacker = Pack200.newUnpacker(); 512 if (deflate) { 513 Map<String,String> p = unpacker.properties(); 514 p.put(Pack200.Unpacker.DEFLATE_HINT, Pack200.Unpacker.TRUE); 515 } 516 unpacker.unpack(gis, contentStream()); 517 } 518 519 } 520 521 private static void checkCompressor(SectionType type, 522 Compressor compressor) { 523 524 if ((SectionType.MODULE_INFO == type && 525 Compressor.NONE != compressor) 526 || (SectionType.CLASSES == type && 527 Compressor.PACK200_GZIP != compressor)) 528 throw new IllegalArgumentException(type 529 + " may not use compressor " 530 + compressor); 531 } 532 533 private static void checkSubsectionCount(SectionType type, 534 short subsections) { 535 if (!type.hasFiles() && subsections != 0) 536 throw new IllegalArgumentException(type 537 + " subsection count not 0: " 538 + subsections); 539 else if (type.hasFiles() && subsections == 0) 540 throw new IllegalArgumentException(type + " subsection count is 0"); 541 } 542 543 private static void copyStream(InputStream in, DataOutput out) 544 throws IOException 545 { 546 547 byte[] buffer = new byte[1024 * 8]; 548 for (int b_read = in.read(buffer); 549 -1 != b_read; 550 b_read = in.read(buffer)) 551 out.write(buffer, 0, b_read); 552 } 553 554 private static void copyStream(InputStream in, OutputStream out) 555 throws IOException 556 { 557 copyStream(in, (DataOutput) new DataOutputStream(out)); 558 } 559 560 private static void copyStream(InputStream in, DataOutput out, 561 int count) 562 throws IOException 563 { 564 byte[] buffer = new byte[1024 * 8]; 565 566 while(count > 0) { 567 int b_read = in.read(buffer, 0, Math.min(count, buffer.length)); 568 if (-1 == b_read) 569 return; 570 out.write(buffer, 0, b_read); 571 count-=b_read; 572 } 573 } 574 575 private static void copyStream(InputStream in, OutputStream out, 576 int count) 577 throws IOException 578 { 579 copyStream(in, (DataOutput) new DataOutputStream(out), count); 580 } 581 582 private static void ensureNonAbsolute(File path) throws IOException { 583 if (path.isAbsolute()) 584 throw new IOException("Abolute path instead of relative: " + path); 585 } 586 587 private static void ensureNonNegativity(long size, String parameter) { 588 if (size < 0) 589 throw new IllegalArgumentException(parameter + "<0: " + size); 590 } 591 592 private static void ensureNonNull(Object reference, String parameter) { 593 if (null == reference) 594 throw new IllegalArgumentException(parameter + " == null"); 595 } 596 597 private static void ensureMatch(int found, int expected, String field) 598 throws IOException 599 { 600 if (found != expected) 601 throw new IOException(field + " expected : " 602 + Integer.toHexString(expected) + " found: " 603 + Integer.toHexString(found)); 604 } 605 606 private static void ensureShortNativePath(File path, String name) 607 throws IOException 608 { 609 // TODO: check for native code file in a stricter way 610 if (path.canExecute() 611 && name.indexOf('/') != -1) 612 throw new IOException("Native code path too long: " + path); 613 } 614 615 private static void ensureValidFileSize(long size, File path) 616 throws IOException 617 { 618 if (size < 0 || size > Integer.MAX_VALUE) 619 throw new IOException("File " + path + " too large: " + size); 620 } 621 622 static MessageDigest getHashInstance(HashType hashtype) 623 throws IOException 624 { 625 try { 626 switch(hashtype) { 627 case SHA256: 628 return MessageDigest.getInstance("SHA-256"); 629 default: 630 throw new IOException("Unknown hash type: " + hashtype); 631 } 632 } 633 catch (NoSuchAlgorithmException ex) { 634 throw (IOException) (new IOException(hashtype + " not found")) 635 .initCause(ex); 636 } 637 } 638 639 private static short getMUTF8Length(String name) { 640 short size = 2; 641 642 for (int i = name.length()-1; i >= 0; i--) { 643 char ch = name.charAt(i); 644 645 if ('\u0001' <= ch && ch <= '\u007F') 646 size += 1; 647 else if ('\u0000' == ch 648 || '\u0080' <= ch && ch <= '\u07FF') 649 size += 2; 650 else 651 size += 3; 652 } 653 654 return size; 655 } 656 657 private static String hashHexString(byte[] hash) { 658 StringBuilder hex = new StringBuilder("0x"); 659 for (int i = 0; i < hash.length; i++) { 660 int val = (hash[i] & 0xFF); 661 if (val <= 16) 662 hex.append("0"); 663 hex.append(Integer.toHexString(val)); 664 } 665 return hex.toString(); 666 } 667 668 private static File resolveAndNormalize(File directory, String path) 669 throws IOException 670 { 671 File realpath = new File(directory, path); 672 if (directory != null && 673 ! realpath.toPath().startsWith(directory.toPath())) 674 throw new IOException("Bogus relative path: " + path); 675 676 return realpath; 677 } 678 679 private static short readHashLength(DataInputStream in) throws IOException { 680 final short hashLength = in.readShort(); 681 ensureNonNegativity(hashLength, "hashLength"); 682 683 return hashLength; 684 } 685 686 private static byte[] readHashBytes(DataInputStream in, short hashLength) 687 throws IOException 688 { 689 690 final byte[] hash = new byte[hashLength]; 691 in.readFully(hash); 692 693 return hash; 694 } 695 696 private static byte[] readHash(DataInputStream in) throws IOException { 697 return readHashBytes(in, readHashLength(in)); 698 } 699 700 private static byte[] readFileHash(DigestInputStream dis) 701 throws IOException 702 { 703 704 DataInputStream in = new DataInputStream(dis); 705 706 final short hashLength = readHashLength(in); 707 708 // Turn digest computation off before reading the file hash 709 dis.on(false); 710 byte[] hash = readHashBytes(in, hashLength); 711 // Turn digest computation on again afterwards. 712 dis.on(true); 713 714 return hash; 715 } 716 717 public final static class ModuleFileHeader { 718 public static final int LENGTH_WITHOUT_HASH = 30; 719 public static final int LENGTH = 720 LENGTH_WITHOUT_HASH + HashType.SHA256.length(); 721 722 // Fields are specified as unsigned. Treat signed values as bugs. 723 private final int magic; // MAGIC 724 private final FileConstants.Type type; // Type.MODULE_FILE 725 private final short major; // ModuleFile.MAJOR_VERSION 726 private final short minor; // ModuleFile.MINOR_VERSION 727 private final long csize; // Size of rest of file, compressed 728 private final long usize; // Space required for uncompressed contents 729 // (upper private final ound; need not be exact) 730 private final HashType hashType; // One of ModuleFile.HashType 731 // (applies final o all hashes in this file) 732 private final byte[] hash; // Hash of entire file (except this hash 733 // and the Signature section, if present) 734 735 public byte[] getHash() { 736 return hash.clone(); 737 } 738 739 private byte[] getHashNoClone() { 740 return hash; 741 } 742 743 public ModuleFileHeader(long csize, long usize, 744 HashType hashType, byte[] hash) { 745 ensureNonNegativity(csize, "csize"); 746 ensureNonNegativity(usize, "usize"); 747 748 magic = FileConstants.MAGIC; 749 type = FileConstants.Type.MODULE_FILE; 750 major = MAJOR_VERSION; 751 minor = MINOR_VERSION; 752 753 this.csize = csize; 754 this.usize = usize; 755 this.hashType = hashType; 756 this.hash = hash.clone(); 757 } 758 759 public void write(final DataOutput out) throws IOException { 760 out.writeInt(magic); 761 out.writeShort(type.value()); 762 out.writeShort(major); 763 out.writeShort(minor); 764 out.writeLong(csize); 765 out.writeLong(usize); 766 out.writeShort(hashType.value()); 767 writeHash(out, hash); 768 } 769 770 private static HashType lookupHashType(short value) { 771 for (HashType i : HashType.class.getEnumConstants()) { 772 if (i.value() == value) return i; 773 } 774 775 throw new IllegalArgumentException("No HashType exists with value " 776 + value); 777 } 778 779 public static ModuleFileHeader read(final DigestInputStream dis) 780 throws IOException 781 { 782 DataInputStream in = new DataInputStream(dis); 783 784 final int magic = in.readInt(); 785 ensureMatch(magic, FileConstants.MAGIC, 786 "FileConstants.MAGIC"); 787 788 final short type = in.readShort(); 789 ensureMatch(type, FileConstants.Type.MODULE_FILE.value(), 790 "Type.MODULE_FILE"); 791 792 final short major = in.readShort(); 793 ensureMatch(major, MAJOR_VERSION, 794 "ModuleFile.MAJOR_VERSION"); 795 796 final short minor = in.readShort(); 797 ensureMatch(minor, MINOR_VERSION, 798 "ModuleFile.MINOR_VERSION"); 799 800 final long csize = in.readLong(); 801 final long usize = in.readLong(); 802 final short hashTypeValue = in.readShort(); 803 HashType hashType = lookupHashType(hashTypeValue); 804 final byte[] hash = readFileHash(dis); 805 806 return new ModuleFileHeader(csize, usize, hashType, hash); 807 } 808 809 public String toString() { 810 return "MODULE{csize=" + csize + 811 ", hash=" + hashHexString(hash) + "}"; 812 } 813 } 814 815 public final static class SectionHeader { 816 public static final int LENGTH_WITHOUT_HASH = 12; 817 public static final int LENGTH = 818 LENGTH_WITHOUT_HASH + HashType.SHA256.length(); 819 820 // Fields are specified as unsigned. Treat signed values as bugs. 821 private final SectionType type; 822 private final Compressor compressor; 823 private final int csize; // Size of section content, compressed 824 private final short subsections; // Number of following subsections 825 private final byte[] hash; // Hash of section content 826 827 public SectionHeader(SectionType type, 828 Compressor compressor, 829 int csize, short subsections, byte[] hash) { 830 ensureNonNull(type, "type"); 831 ensureNonNull(compressor, "compressor"); 832 ensureNonNegativity(csize, "csize"); 833 ensureNonNegativity(subsections, "subsections"); 834 ensureNonNull(hash, "hash"); 835 checkSubsectionCount(type, subsections); 836 checkCompressor(type, compressor); 837 838 this.type = type; 839 this.compressor = compressor; 840 this.csize = csize; 841 this.subsections = subsections; 842 this.hash = hash.clone(); 843 } 844 845 public void write(DataOutput out) throws IOException { 846 out.writeShort(type.value()); 847 out.writeShort(compressor.value()); 848 out.writeInt(csize); 849 out.writeShort(subsections); 850 writeHash(out, hash); 851 } 852 853 private static SectionType lookupSectionType(short value) { 854 for (SectionType i : SectionType.class.getEnumConstants()) { 855 if (i.value() == value) return i; 856 } 857 858 throw new 859 IllegalArgumentException("No SectionType exists with value " 860 + value); 861 } 862 863 private static Compressor lookupCompressor(short value) { 864 for (Compressor i : Compressor.class.getEnumConstants()) { 865 if (i.value() == value) return i; 866 } 867 868 throw new 869 IllegalArgumentException("No Compressor exists with value " 870 + value); 871 } 872 873 public static SectionHeader read(DataInputStream in) throws IOException { 874 short tvalue = in.readShort(); 875 final SectionType type = lookupSectionType(tvalue); 876 short cvalue = in.readShort(); 877 final Compressor compressor = lookupCompressor(cvalue); 878 final int csize = in.readInt(); 879 final short sections = in.readShort(); 880 final byte[] hash = readHash(in); 881 882 return new SectionHeader(type, compressor, csize, 883 sections, hash); 884 } 885 886 public SectionType getType() { 887 return type; 888 } 889 890 public Compressor getCompressor() { 891 return compressor; 892 } 893 894 public int getCSize() { 895 return csize; 896 } 897 898 public short getSubsections() { 899 return subsections; 900 } 901 902 public byte[] getHash() { 903 return hash.clone(); 904 } 905 906 private byte[] getHashNoClone() { 907 return hash; 908 } 909 910 public String toString() { 911 return "SectionHeader{type= " + type 912 + ", compressor=" + compressor 913 + ", csize=" + csize 914 + ", subsections=" + subsections 915 + ", hash=" + hashHexString(hash) + "}"; 916 } 917 } 918 919 public final static class SubSectionFileHeader { 920 private final int csize; // Size of file, compressed 921 private final String path; // Path name, in Java-modified UTF-8 922 923 public int getCSize() { 924 return csize; 925 } 926 927 public String getPath() { 928 return path; 929 } 930 931 public SubSectionFileHeader(int csize, String path) { 932 ensureNonNegativity(csize, "csize"); 933 ensureNonNull(path, "path"); 934 935 this.csize = csize; 936 this.path = path; 937 } 938 939 public void write(DataOutput out) throws IOException { 940 out.writeShort(SubSectionType.FILE.value()); 941 out.writeInt(csize); 942 out.writeUTF(path); 943 } 944 945 public static SubSectionFileHeader read(DataInputStream in) 946 throws IOException 947 { 948 final short type = in.readShort(); 949 ensureMatch(type, SubSectionType.FILE.value(), 950 "ModuleFile.SubSectionType.FILE"); 951 final int csize = in.readInt(); 952 final String path = in.readUTF(); 953 954 return new SubSectionFileHeader(csize, path); 955 } 956 } 957 958 private static void writeHash(DataOutput out, byte[] hash) 959 throws IOException 960 { 961 out.writeShort(hash.length); 962 out.write(hash); 963 } 964 } --- EOF ---