1 /*
   2  * Copyright (c) 2010, 2012, 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.nio.charset.Charset;
  30 import java.security.*;
  31 import java.util.*;
  32 import java.util.Map.Entry;
  33 import java.util.jar.*;
  34 import org.openjdk.jigsaw.ModuleFileParser.Event;
  35 import static org.openjdk.jigsaw.FileConstants.ModuleFile.*;
  36 import static org.openjdk.jigsaw.FileConstants.ModuleFile.SectionType.*;
  37 import static org.openjdk.jigsaw.ModuleFileParser.Event.*;
  38 
  39 
  40 /**
  41  * <p> A known <a
  42  * href="http://cr.openjdk.java.net/~mr/jigsaw/notes/module-file-format/">
  43  * module file</a> </p>
  44  */
  45 
  46 public final class ModuleFile {
  47 
  48     /**
  49      * Return the subdir of a section in an extracted module file.
  50      */
  51     public static String getSubdirOfSection(SectionType type) {
  52         switch (type) {
  53         case MODULE_INFO:
  54         case SIGNATURE:
  55             return ".";
  56         case CLASSES:
  57         case RESOURCES:
  58             return "classes";
  59         case NATIVE_LIBS:
  60             return "lib";
  61         case NATIVE_CMDS:
  62             return "bin";
  63         case CONFIG:
  64             return "etc";
  65         default:
  66             throw new AssertionError(type);
  67         }
  68     }
  69 
  70     /**
  71      * Returns a ModuleFileParser instance.
  72      *
  73      * @param   stream
  74      *          module file stream
  75      *
  76      * @return  a module file parser
  77      *
  78      * @throws  ModuleFileParserException
  79      *          If there is an error processing the underlying module file
  80      */
  81     public static ModuleFileParser newParser(InputStream stream) {
  82         return new ModuleFileParserImpl(stream);
  83     }
  84 
  85     /**
  86      * Returns a ValidatingModuleFileParser instance.
  87      *
  88      * @param   stream
  89      *          module file stream
  90      *
  91      * @return  a validating module file parser
  92      *
  93      * @throws  ModuleFileParserException
  94      *          If there is an error processing the underlying module file
  95      */
  96     public static ValidatingModuleFileParser newValidatingParser(InputStream stream) {
  97         return new ValidatingModuleFileParserImpl(stream);
  98     }
  99 
 100     /**
 101      * <p> A module-file reader </p>
 102      */
 103     public final static class Reader implements Closeable {
 104         private final ValidatingModuleFileParser parser;
 105         private final ModuleFileHeader fileHeader;
 106         private final byte[] moduleInfoBytes;
 107         private final SignatureType moduleSignatureType;
 108         private final byte[] moduleSignatureBytes ;
 109 
 110         private File destination;
 111         private boolean deflate;
 112         private File natlibs;
 113         private File natcmds;
 114         private File configs;
 115 
 116         public Reader(InputStream stream) throws IOException {
 117             parser = ModuleFile.newValidatingParser(stream);
 118             fileHeader = parser.fileHeader();
 119             // Read the MODULE_INFO and the Signature section (if present),
 120             // but does not write any files.
 121             parser.next();
 122             ByteArrayOutputStream baos = new ByteArrayOutputStream();
 123             copyStream(parser.getRawStream(), baos);
 124             moduleInfoBytes = baos.toByteArray();
 125             assert moduleInfoBytes != null;
 126 
 127             if (parser.next() != END_SECTION)
 128                 throw new ModuleFileParserException(
 129                         "Expected END_SECTION of module-info");
 130 
 131             if (parser.hasNext()) {
 132                 Event event = parser.next();
 133                 if (event != END_FILE) {  // more sections
 134                     SectionHeader header = parser.getSectionHeader();
 135                     if (header.type == SIGNATURE) {
 136                         SignatureSection sh = SignatureSection.read(
 137                                 new DataInputStream(parser.getRawStream()));
 138                         moduleSignatureType = SignatureType.valueOf(sh.getSignatureType());
 139                         moduleSignatureBytes = sh.getSignature();
 140                         if (parser.next() != END_SECTION)
 141                             throw new ModuleFileParserException(
 142                                     "Expected END_SECTION of signature");
 143                         if (parser.hasNext())
 144                             parser.next();  // position parser at next event
 145                         return;
 146                     }
 147                 }
 148             }
 149             // no signature section, or possibly other sections at all.
 150             moduleSignatureBytes = null;
 151             moduleSignatureType = null;
 152         }
 153 
 154         public void extractTo(File dst) throws IOException {
 155             extractTo(dst, false);
 156         }
 157 
 158         public void extractTo(File dst, boolean deflate) throws IOException {
 159             extractTo(dst, deflate, null, null, null);
 160         }
 161 
 162         public void extractTo(File dst, boolean deflate, File natlibs,
 163                               File natcmds, File configs)
 164             throws IOException
 165         {
 166             this.deflate = deflate;
 167             this.destination = dst != null ? dst.getCanonicalFile() : null;
 168             this.natlibs = natlibs != null ? natlibs : new File(destination, "lib");
 169             this.natcmds = natcmds != null ? natcmds : new File(destination, "bin");
 170             this.configs = configs != null ? configs : new File(destination, "etc");
 171 
 172             try {
 173                 Files.store(moduleInfoBytes, computeRealPath("info"));
 174 
 175                 Event event = parser.event();
 176                 if (event == END_FILE)
 177                     return;
 178 
 179                 if (event != START_SECTION)
 180                     throw new ModuleFileParserException(
 181                                         "Expected START_SECTION, got : " + event);
 182                 // Module-Info and Signature, if present, have been consumed
 183                 do {
 184                     SectionHeader header = parser.getSectionHeader();
 185                     SectionType type = header.getType();
 186                     if (type.hasFiles()) {
 187                         while(parser.skipToNextStartSubSection()) {
 188                             readSubSection(type);
 189                         }
 190                     } else if (type == CLASSES) {
 191                         Iterator<Map.Entry<String,InputStream>> classes =
 192                                 parser.getClasses();
 193                         while (classes.hasNext()) {
 194                             Map.Entry<String,InputStream> entry = classes.next();
 195                             try (OutputStream out = openOutputStream(type, entry.getKey())) {
 196                                 copyStream(entry.getValue(), out);
 197                             }
 198                         }
 199                         // END_SECTION
 200                         parser.next();
 201                     } else {
 202                         throw new IllegalArgumentException("Unknown type: " + type);
 203                     }
 204                 } while (parser.skipToNextStartSection());
 205 
 206                 if (parser.event() != END_FILE)
 207                     throw new IOException("Expected END_FILE");
 208             } finally {
 209                 close();
 210             }
 211         }
 212 
 213         public byte[] getModuleInfoBytes() {
 214             return moduleInfoBytes.clone();
 215         }
 216 
 217         public byte[] getHash() {
 218             return fileHeader.getHash();
 219         }
 220 
 221         public ModuleArchitecture getArchitecture() {
 222             return fileHeader.getArchitecture();
 223         }
 224 
 225         public List<byte[]> getCalculatedHashes() {
 226             List<byte[]> hashes = new ArrayList<>();
 227             hashes.add(parser.getHeaderHash());
 228             for (Entry<SectionType,byte[]> entry : parser.getHashes().entrySet()) {
 229                 if (entry.getKey() != SIGNATURE)
 230                     hashes.add(entry.getValue());
 231             }
 232             hashes.add(getHash());
 233 
 234             return hashes;
 235         }
 236 
 237         public boolean hasSignature() {
 238             return moduleSignatureBytes != null;
 239         }
 240 
 241         public SignatureType getSignatureType() {
 242             return moduleSignatureType;
 243         }
 244 
 245         public byte[] getSignature() {
 246             return moduleSignatureBytes == null ? null :
 247                    moduleSignatureBytes.clone();
 248         }
 249 
 250         /*package*/ byte[] getSignatureNoClone() {
 251             return moduleSignatureBytes;
 252         }
 253 
 254         public void close() throws IOException {
 255             try {
 256                 if (contentStream != null) {
 257                     contentStream.close();
 258                     contentStream = null;
 259                 }
 260             } finally {
 261                 if (filesWriter != null) {
 262                     filesWriter.close();
 263                     filesWriter = null;
 264                 }
 265             }
 266         }
 267 
 268         // subsections/files (resources, libs, cmds, configs)
 269         public void readSubSection(SectionType type) throws IOException {
 270             assert type == RESOURCES || type == NATIVE_LIBS ||
 271                    type == NATIVE_CMDS || type == CONFIG;
 272 
 273             SubSectionFileHeader subHeader = parser.getSubSectionFileHeader();
 274             String path = subHeader.getPath();
 275             try (OutputStream sink = openOutputStream(type, path)) {
 276                 copyStream(parser.getContentStream(), sink);
 277             }
 278 
 279             // post processing for executable and files outside the module dir
 280             postExtract(type, currentPath);
 281         }
 282 
 283         private JarOutputStream contentStream = null;
 284 
 285         private JarOutputStream contentStream() throws IOException {
 286             if (contentStream != null)
 287                 return contentStream;
 288 
 289             return contentStream = new JarOutputStream(
 290                     new BufferedOutputStream(
 291                         new FileOutputStream(computeRealPath("classes"))));
 292         }
 293 
 294         private File currentPath = null;
 295 
 296         private OutputStream openOutputStream(SectionType type, String path)
 297             throws IOException
 298         {
 299             currentPath = null;
 300             if (type == CLASSES || type == RESOURCES)
 301                 return Files.newOutputStream(contentStream(), deflate, path);
 302             currentPath = computeRealPath(type, path);
 303             File parent = currentPath.getParentFile();
 304             if (!parent.exists())
 305                 Files.mkdirs(parent, currentPath.getName());
 306             return new BufferedOutputStream(new FileOutputStream(currentPath));
 307         }
 308 
 309         // Track files installed outside the module library. For later removal.
 310         // files are relative to the modules directory.
 311         private PrintWriter filesWriter;
 312 
 313         private void trackFiles(File file)
 314             throws IOException
 315         {
 316             if (file == null || file.toPath().startsWith(destination.toPath()))
 317                 return;
 318 
 319             // Lazy construction, not all modules will need this.
 320             if (filesWriter == null)
 321                 filesWriter = new PrintWriter(computeRealPath("files"), "UTF-8");
 322 
 323             filesWriter.println(Files.convertSeparator(relativize(destination, file)));
 324             filesWriter.flush();
 325         }
 326 
 327         List<IOException> remove() {
 328             return ModuleFile.Reader.remove(destination);
 329         }
 330 
 331         // Removes a module, given its module install directory
 332         static List<IOException> remove(File moduleDir) {
 333             List<IOException> excs = new ArrayList<>();
 334             // Firstly remove any files installed outside of the module dir
 335             File files = new File(moduleDir, "files");
 336             if (files.exists()) {
 337                 try {
 338                     List<String> filenames =
 339                         java.nio.file.Files.readAllLines(files.toPath(),
 340                                                          Charset.forName("UTF-8"));
 341                     for (String fn : filenames) {
 342                         try {
 343                             Files.delete(new File(moduleDir,
 344                                                   Files.platformSeparator(fn)));
 345                         } catch (IOException x) {
 346                             excs.add(x);
 347                         }
 348                     }
 349                 } catch (IOException x) {
 350                     excs.add(x);
 351                 }
 352             }
 353 
 354             excs.addAll(Files.deleteTreeUnchecked(moduleDir.toPath()));
 355             return excs;
 356         }
 357 
 358         // Returns the absolute path of the given section type.
 359         private File getDirOfSection(SectionType type) {
 360             if (type == NATIVE_LIBS)
 361                 return natlibs;
 362             else if (type == NATIVE_CMDS)
 363                 return natcmds;
 364             else if (type == CONFIG)
 365                 return configs;
 366 
 367             // resolve sub dir section paths against the modules directory
 368             return new File(destination, ModuleFile.getSubdirOfSection(type));
 369         }
 370 
 371         private File computeRealPath(String path) throws IOException {
 372             return resolveAndNormalize(destination, path);
 373         }
 374 
 375         private File computeRealPath(SectionType type, String storedpath)
 376             throws IOException
 377         {
 378             File sectionPath = getDirOfSection(type);
 379             File realpath = new File(sectionPath,
 380                  Files.ensureNonAbsolute(Files.platformSeparator(storedpath)));
 381 
 382             validatePath(sectionPath, realpath);
 383 
 384             // Create the parent directories if necessary
 385             File parent = realpath.getParentFile();
 386             if (!parent.exists())
 387                 Files.mkdirs(parent, realpath.getName());
 388 
 389             return realpath;
 390         }
 391 
 392         private static void markNativeCodeExecutable(SectionType type,
 393                                                      File file)
 394         {
 395             if (type == NATIVE_CMDS || (type == NATIVE_LIBS &&
 396                     System.getProperty("os.name").startsWith("Windows")))
 397                 file.setExecutable(true);
 398         }
 399 
 400         private void postExtract(SectionType type, File path)
 401             throws IOException
 402         {
 403             markNativeCodeExecutable(type, path);
 404             trackFiles(path);
 405         }
 406     }
 407 
 408     private static void checkCompressor(SectionType type,
 409                                         Compressor compressor) {
 410 
 411         if ((MODULE_INFO == type && Compressor.NONE != compressor) ||
 412             (CLASSES == type && Compressor.PACK200_GZIP != compressor))
 413             throw new IllegalArgumentException(type
 414                                                + " may not use compressor "
 415                                                + compressor);
 416     }
 417 
 418     private static void checkSubsectionCount(SectionType type,
 419                                              short subsections) {
 420         if (!type.hasFiles() && subsections != 0)
 421             throw new IllegalArgumentException(type
 422                                                + " subsection count not 0: "
 423                                                + subsections);
 424         else if (type.hasFiles() && subsections == 0)
 425             throw new IllegalArgumentException(type + " subsection count is 0");
 426     }
 427 
 428     private static void copyStream(InputStream in, OutputStream out)
 429         throws IOException
 430     {
 431         byte[] buf = new byte[8192];
 432         int read;
 433         while ((read = in.read(buf)) > 0)
 434             out.write(buf, 0, read);
 435     }
 436 
 437     private static void ensureNonNegativity(long size, String parameter) {
 438         if (size < 0)
 439             throw new IllegalArgumentException(parameter + "<0: " + size);
 440     }
 441 
 442     private static void ensureNonNull(Object reference, String parameter) {
 443         if (null == reference)
 444             throw new IllegalArgumentException(parameter + " == null");
 445     }
 446 
 447     private static void ensureMatch(int found, int expected, String field)
 448         throws IOException
 449     {
 450         if (found != expected)
 451             throw new IOException(field + " expected : "
 452                 + Integer.toHexString(expected) + " found: "
 453                 + Integer.toHexString(found));
 454     }
 455 
 456     private static void ensureShortNativePath(File path, String name)
 457         throws IOException
 458     {
 459             // TODO: check for native code file in a stricter way
 460             if (path.canExecute()
 461                 && name.indexOf('/') != -1)
 462                 throw new IOException("Native code path too long: " + path);
 463     }
 464 
 465     private static void ensureValidFileSize(long size, File path)
 466         throws IOException
 467     {
 468         if (size < 0 || size > Integer.MAX_VALUE)
 469             throw new IOException("File " + path + " too large: " + size);
 470     }
 471 
 472     static MessageDigest getHashInstance(HashType hashtype)
 473         throws IOException
 474     {
 475         try {
 476             switch(hashtype) {
 477             case SHA256:
 478                 return MessageDigest.getInstance("SHA-256");
 479             default:
 480                 throw new IOException("Unknown hash type: " + hashtype);
 481             }
 482         }
 483         catch (NoSuchAlgorithmException ex) {
 484             throw new IOException(hashtype + " not found", ex);
 485         }
 486     }
 487 
 488     private static short getMUTF8Length(String name) {
 489         short size = 2;
 490 
 491         for (int i = name.length()-1; i >= 0; i--) {
 492             char ch = name.charAt(i);
 493 
 494             if ('\u0001' <= ch && ch <= '\u007F')
 495                 size += 1;
 496             else if ('\u0000' == ch
 497                      || '\u0080' <= ch && ch <= '\u07FF')
 498                 size += 2;
 499             else
 500                 size += 3;
 501         }
 502 
 503         return size;
 504     }
 505 
 506     private static String hashHexString(byte[] hash) {
 507         StringBuilder hex = new StringBuilder("0x");
 508         for (int i = 0; i < hash.length; i++) {
 509             int val = (hash[i] & 0xFF);
 510             if (val <= 16)
 511                 hex.append("0");
 512             hex.append(Integer.toHexString(val));
 513         }
 514         return hex.toString();
 515     }
 516 
 517     private static File resolveAndNormalize(File directory, String path)
 518         throws IOException
 519     {
 520         File realpath = new File(directory, path);
 521         if (directory != null &&
 522             ! realpath.toPath().startsWith(directory.toPath()))
 523             throw new IOException("Bogus relative path: " + path);
 524 
 525         return realpath;
 526     }
 527 
 528 
 529     private static String relativize(File directory, File path) throws IOException {
 530         return (directory.toPath().relativize(path.toPath().toRealPath())).toString();
 531     }
 532 
 533     private static void validatePath(File parent, File child)
 534         throws IOException
 535     {
 536         if (!child.toPath().startsWith(parent.toPath()) )
 537             throw new IOException("Bogus relative path: " + child);
 538         if (child.exists()) {
 539             // conflict, for now just fail
 540             throw new IOException("File " + child + " already exists");
 541         }
 542     }
 543 
 544     private static short readHashLength(DataInputStream in) throws IOException {
 545         final short hashLength = in.readShort();
 546         ensureNonNegativity(hashLength, "hashLength");
 547 
 548         return hashLength;
 549     }
 550 
 551     private static byte[] readHashBytes(DataInputStream in, short hashLength)
 552         throws IOException
 553     {
 554         final byte[] hash = new byte[hashLength];
 555         in.readFully(hash);
 556 
 557         return hash;
 558     }
 559 
 560     private static byte[] readHash(DataInputStream in) throws IOException {
 561         return readHashBytes(in, readHashLength(in));
 562     }
 563 
 564     private static byte[] readFileHash(InputStream in)
 565         throws IOException
 566     {
 567         boolean digestStream = in instanceof DigestInputStream ? true : false;
 568         DataInputStream din = new DataInputStream(in);
 569 
 570         final short hashLength = readHashLength(din);
 571 
 572         // Turn digest computation off before reading the file hash
 573         if (digestStream)
 574             ((DigestInputStream)in).on(false);
 575         byte[] hash = readHashBytes(din, hashLength);
 576         // Turn digest computation on again afterwards.
 577         if (digestStream)
 578             ((DigestInputStream)in).on(true);
 579 
 580         return hash;
 581     }
 582 
 583     /**
 584      * <p> A module-file header </p>
 585      */
 586     public final static class ModuleFileHeader {
 587         private static final int LENGTH_WITHOUT_VARIABLE_FIELDS = 34;


 588 
 589         // Fields are specified as unsigned. Treat signed values as bugs.
 590         private final int magic;                // MAGIC
 591         private final FileConstants.Type type;  // Type.MODULE_FILE
 592         private final short major;              // ModuleFile.MAJOR_VERSION
 593         private final short minor;              // ModuleFile.MINOR_VERSION
 594         private final long csize;               // Size of rest of file, compressed
 595         private final long usize;               // Space required for uncompressed contents
 596                                                 //   (upper private final ound; need not be exact)
 597         private final HashType hashType;        // One of ModuleFile.HashType
 598                                                 //   (applies final o all hashes in this file)
 599         private final byte[] hash;              // Hash of entire file (except this hash
 600                                                 // and the Signature section, if present)
 601         private final ModuleArchitecture modArch;// os/arch, Java-modified UTF-8 pair
 602 
 603         private final int length;
 604 
 605         public HashType getHashType() {
 606             return hashType;
 607         }
 608 
 609         public byte[] getHash() {
 610             return hash.clone();
 611         }
 612 
 613         private byte[] getHashNoClone() {
 614             return hash;
 615         }
 616 
 617         public long getCSize() {
 618             return csize;
 619         }
 620 
 621         public long getUSize() {
 622             return usize;
 623         }
 624 
 625         public ModuleArchitecture getArchitecture() {
 626             return modArch;
 627         }
 628 
 629         public int getLength() {
 630             return length;
 631         }
 632 
 633         private ModuleFileHeader(long csize, long usize, HashType hashType,
 634                                  byte[] hash, ModuleArchitecture modArch) {
 635             ensureNonNegativity(csize, "csize");
 636             ensureNonNegativity(usize, "usize");
 637 
 638             magic = FileConstants.MAGIC;
 639             type = FileConstants.Type.MODULE_FILE;
 640             major = MAJOR_VERSION;
 641             minor = MINOR_VERSION;
 642 
 643             this.csize = csize;
 644             this.usize = usize;
 645             this.hashType = hashType;
 646             this.hash = hash.clone();
 647             this.modArch = modArch;
 648             try {
 649                 length = LENGTH_WITHOUT_VARIABLE_FIELDS
 650                          + hash.length
 651                          + modArch.os().getBytes("UTF-8").length
 652                          + modArch.arch().getBytes("UTF-8").length;
 653             } catch (UnsupportedEncodingException x) {
 654                 throw new InternalError(x);
 655             }
 656         }
 657 
 658         public static Builder newBuilder() {
 659             return new Builder();
 660         }
 661 
 662         public static class Builder {
 663             private long csize;
 664             private long usize;
 665             private ModuleArchitecture architecture = ModuleArchitecture.ANY;
 666             private HashType hashType;
 667             private byte[] hash;
 668 
 669             private Builder() {}
 670 
 671             public Builder setHashType(HashType hashType) {
 672                 this.hashType = hashType;
 673                 return this;
 674             }
 675 
 676             public Builder setHash(byte[] hash) {
 677                 this.hash = hash.clone();
 678                 return this;
 679             }
 680 
 681             public Builder setCSize(long csize) {
 682                 this.csize = csize;
 683                 return this;
 684             }
 685 
 686             public Builder setUSize(long usize) {
 687                 this.usize = usize;
 688                 return this;
 689             }
 690 
 691             public Builder setArchitecture(ModuleArchitecture architecture) {
 692                 this.architecture = architecture;
 693                 return this;
 694             }
 695 
 696             public ModuleFileHeader build() {
 697                 ensureNonNegativity(csize, "csize");
 698                 ensureNonNegativity(usize, "usize");
 699                 return new ModuleFileHeader(csize, usize, hashType, hash,
 700                                             architecture);
 701             }
 702         }
 703 
 704         public void write(final DataOutput out) throws IOException {
 705             out.writeInt(magic);
 706             out.writeShort(type.value());
 707             out.writeShort(major);
 708             out.writeShort(minor);
 709             out.writeLong(csize);
 710             out.writeLong(usize);
 711             out.writeShort(hashType.value());
 712             writeHash(out, hash);
 713             out.writeUTF(modArch.os());
 714             out.writeUTF(modArch.arch());
 715         }
 716 
 717         public static ModuleFileHeader read(final InputStream in)
 718             throws IOException
 719         {
 720             DataInputStream din = new DataInputStream(in);
 721 
 722             final int magic = din.readInt();
 723             ensureMatch(magic, FileConstants.MAGIC,
 724                         "FileConstants.MAGIC");
 725 
 726             final short type = din.readShort();
 727             ensureMatch(type, FileConstants.Type.MODULE_FILE.value(),
 728                        "Type.MODULE_FILE");
 729 
 730             final short major = din.readShort();
 731             ensureMatch(major, MAJOR_VERSION,
 732                         "ModuleFile.MAJOR_VERSION");
 733 
 734             final short minor = din.readShort();
 735             ensureMatch(minor, MINOR_VERSION,
 736                         "ModuleFile.MINOR_VERSION");
 737 
 738             final long csize = din.readLong();
 739             final long usize = din.readLong();
 740 
 741             final short hashTypeValue = din.readShort();
 742             HashType hashType = null;
 743             try {
 744                 hashType = HashType.valueOf(hashTypeValue);
 745             } catch (IllegalArgumentException x) {
 746                 throw new IOException("Invalid hash type: " + hashTypeValue);
 747             }
 748             final byte[] hash = readFileHash(in);
 749 
 750             final String os = din.readUTF();
 751             final String arch = din.readUTF();
 752             final ModuleArchitecture architecture = ModuleArchitecture.create(os, arch);
 753 
 754             return new ModuleFileHeader(csize, usize, hashType, hash, architecture);
 755         }
 756 
 757         @Override
 758         public String toString() {
 759             return "MODULE{csize=" + csize +
 760                    ", hash=" + hashHexString(hash) + "}";
 761         }
 762     }
 763 
 764     /**
 765      * <p> A module-file section header </p>
 766      */
 767     public final static class SectionHeader {
 768         public static final int LENGTH_WITHOUT_HASH = 12;
 769         public static final int LENGTH =
 770             LENGTH_WITHOUT_HASH + HashType.SHA256.length();
 771 
 772         // Fields are specified as unsigned. Treat signed values as bugs.
 773         private final SectionType type;
 774         private final Compressor compressor;
 775         private final int csize;               // Size of section content, compressed
 776         private final short subsections;       // Number of following subsections
 777         private final byte[] hash;             // Hash of section content
 778 
 779         public SectionHeader(SectionType type,
 780                              Compressor compressor,
 781                              int csize, short subsections, byte[] hash) {
 782             ensureNonNull(type, "type");
 783             ensureNonNull(compressor, "compressor");
 784             ensureNonNegativity(csize, "csize");
 785             ensureNonNegativity(subsections, "subsections");
 786             ensureNonNull(hash, "hash");
 787             checkSubsectionCount(type, subsections);
 788             checkCompressor(type, compressor);
 789 
 790             this.type = type;
 791             this.compressor = compressor;
 792             this.csize = csize;
 793             this.subsections = subsections;
 794             this.hash = hash.clone();
 795         }
 796 
 797         public void write(DataOutput out) throws IOException {
 798             out.writeShort(type.value());
 799             out.writeShort(compressor.value());
 800             out.writeInt(csize);
 801             out.writeShort(subsections);
 802             writeHash(out, hash);
 803         }
 804 
 805         private static SectionType lookupSectionType(short value) {
 806             for (SectionType i : SectionType.class.getEnumConstants()) {
 807                 if (i.value() == value) return i;
 808             }
 809 
 810             throw new
 811                 IllegalArgumentException("No SectionType exists with value "
 812                                          + value);
 813         }
 814 
 815         private static Compressor lookupCompressor(short value) {
 816             for (Compressor i : Compressor.class.getEnumConstants()) {
 817                 if (i.value() == value) return i;
 818             }
 819 
 820             throw new
 821                 IllegalArgumentException("No Compressor exists with value "
 822                                          + value);
 823         }
 824 
 825         public static SectionHeader read(DataInputStream in) throws IOException {
 826             short tvalue = in.readShort();
 827             final SectionType type = lookupSectionType(tvalue);
 828             short cvalue = in.readShort();
 829             final Compressor compressor = lookupCompressor(cvalue);
 830             final int csize = in.readInt();
 831             final short sections = in.readShort();
 832             final byte[] hash = readHash(in);
 833 
 834             return new SectionHeader(type, compressor, csize,
 835                     sections, hash);
 836         }
 837 
 838         public SectionType getType() {
 839             return type;
 840         }
 841 
 842         public Compressor getCompressor() {
 843             return compressor;
 844         }
 845 
 846         public int getCSize() {
 847             return csize;
 848         }
 849 
 850         public short getSubsections() {
 851             return subsections;
 852         }
 853 
 854         public byte[] getHash() {
 855             return hash.clone();
 856         }
 857 
 858         private byte[] getHashNoClone() {
 859             return hash;
 860         }
 861 
 862         @Override
 863         public String toString() {
 864             return "SectionHeader{type= " + type
 865                     + ", compressor=" + compressor
 866                     + ", csize=" + csize
 867                     + ", subsections=" + subsections
 868                     + ", hash=" + hashHexString(hash) + "}";
 869         }
 870     }
 871 
 872     /**
 873      * <p> A module-file sub-section header </p>
 874      */
 875     public final static class SubSectionFileHeader {
 876         private final int csize;              // Size of file, compressed
 877         private final String path;            // Path name, in Java-modified UTF-8
 878 
 879         public int getCSize() {
 880             return csize;
 881         }
 882 
 883         public String getPath() {
 884             return path;
 885         }
 886 
 887         public SubSectionFileHeader(int csize, String path) {
 888             ensureNonNegativity(csize, "csize");
 889             ensureNonNull(path, "path");
 890 
 891             this.csize = csize;
 892             this.path = path;
 893         }
 894 
 895         public void write(DataOutput out) throws IOException {
 896             out.writeShort(SubSectionType.FILE.value());
 897             out.writeInt(csize);
 898             out.writeUTF(path);
 899         }
 900 
 901         public static SubSectionFileHeader read(DataInputStream in)
 902                 throws IOException
 903         {
 904             final short type = in.readShort();
 905             ensureMatch(type, SubSectionType.FILE.value(),
 906                         "ModuleFile.SubSectionType.FILE");
 907             final int csize = in.readInt();
 908             final String path = in.readUTF();
 909 
 910             return new SubSectionFileHeader(csize, path);
 911         }
 912     }
 913 
 914     /**
 915      * <p> A module-file signature section </p>
 916      */
 917     public final static class SignatureSection {
 918         public static final int HEADER_LENGTH = 6; // type + signature length
 919 
 920         private final int signatureType;   // One of FileConstants.ModuleFile.HashType
 921         private final int signatureLength; // Length of signature
 922         private final byte[] signature;    // Signature bytes
 923 
 924         public int getSignatureType() {
 925             return signatureType;
 926         }
 927 
 928         public int getSignatureLength() {
 929             return signatureLength;
 930         }
 931 
 932         public byte[] getSignature() {
 933             return signature;
 934         }
 935 
 936         public SignatureSection(int signatureType, int signatureLength,
 937                                 byte[] signature) {
 938             ensureNonNegativity(signatureLength, "signatureLength");
 939 
 940             this.signatureType = signatureType;
 941             this.signatureLength = signatureLength;
 942             this.signature = signature.clone();
 943         }
 944 
 945         public void write(DataOutput out) throws IOException {
 946             out.writeShort(signatureType);
 947             out.writeInt(signatureLength);
 948             out.write(signature);
 949         }
 950 
 951         public static SignatureSection read(DataInputStream in)
 952             throws IOException
 953         {
 954             short signatureType = in.readShort();
 955             try {
 956                 SignatureType.valueOf(signatureType);
 957             } catch (IllegalArgumentException x) {
 958                 throw new IOException("Invalid signature type: " + signatureType);
 959             }
 960             final int signatureLength = in.readInt();
 961             ensureNonNegativity(signatureLength, "signatureLength");
 962             final byte[] signature = new byte[signatureLength];
 963             in.readFully(signature);
 964             return new SignatureSection(signatureType, signatureLength,
 965                                         signature);
 966         }
 967     }
 968 
 969     private static void writeHash(DataOutput out, byte[] hash)
 970             throws IOException
 971     {
 972         out.writeShort(hash.length);
 973         out.write(hash);
 974     }
 975 
 976 }
--- EOF ---