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 }