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 ---