• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright 2016 Google Inc. All Rights Reserved.
2 
3    Distributed under MIT license.
4    See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
5 */
6 
7 package org.brotli.integration;
8 
9 import org.brotli.dec.BrotliInputStream;
10 import java.io.FileInputStream;
11 import java.io.FileNotFoundException;
12 import java.io.FilterInputStream;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.math.BigInteger;
16 import java.util.concurrent.atomic.AtomicInteger;
17 import java.util.zip.ZipEntry;
18 import java.util.zip.ZipInputStream;
19 
20 /**
21  * Decompress files and (optionally) checks their checksums.
22  *
23  * <p> File are read from ZIP archive passed as an array of bytes. Multiple checkers negotiate about
24  * task distribution via shared AtomicInteger counter.
25  * <p> All entries are expected to be valid brotli compressed streams and output CRC64 checksum
26  * is expected to match the checksum hex-encoded in the first part of entry name.
27  */
28 public class BundleChecker implements Runnable {
29   private final AtomicInteger nextJob;
30   private final InputStream input;
31   private final boolean sanityCheck;
32 
33   /**
34    * @param sanityCheck do not calculate checksum and ignore {@link IOException}.
35    */
BundleChecker(InputStream input, AtomicInteger nextJob, boolean sanityCheck)36   public BundleChecker(InputStream input, AtomicInteger nextJob, boolean sanityCheck) {
37     this.input = input;
38     this.nextJob = nextJob;
39     this.sanityCheck = sanityCheck;
40   }
41 
42   /** ECMA CRC64 polynomial. */
43   private static final long CRC_64_POLY =
44       new BigInteger("C96C5795D7870F42", 16).longValue();
45 
46   /**
47    * Rolls CRC64 calculation.
48    *
49    * <p> {@code CRC64(data) = -1 ^ updateCrc64((... updateCrc64(-1, firstBlock), ...), lastBlock);}
50    * <p> This simple and reliable checksum is chosen to make is easy to calculate the same value
51    * across the variety of languages (C++, Java, Go, ...).
52    */
updateCrc64(long crc, byte[] data, int offset, int length)53   private static long updateCrc64(long crc, byte[] data, int offset, int length) {
54     for (int i = offset; i < offset + length; ++i) {
55       long c = (crc ^ (long) (data[i] & 0xFF)) & 0xFF;
56       for (int k = 0; k < 8; k++) {
57         c = ((c & 1) == 1) ? CRC_64_POLY ^ (c >>> 1) : c >>> 1;
58       }
59       crc = c ^ (crc >>> 8);
60     }
61     return crc;
62   }
63 
decompressAndCalculateCrc(ZipInputStream input)64   private long decompressAndCalculateCrc(ZipInputStream input) throws IOException {
65     /* Do not allow entry readers to close the whole ZipInputStream. */
66     FilterInputStream entryStream = new FilterInputStream(input) {
67       @Override
68       public void close() {}
69     };
70 
71     long crc = -1;
72     byte[] buffer = new byte[65536];
73     BrotliInputStream decompressedStream = new BrotliInputStream(entryStream);
74     while (true) {
75       int len = decompressedStream.read(buffer);
76       if (len <= 0) {
77         break;
78       }
79       crc = updateCrc64(crc, buffer, 0, len);
80     }
81     decompressedStream.close();
82     return ~crc;
83   }
84 
85   @Override
run()86   public void run() {
87     String entryName = "";
88     ZipInputStream zis = new ZipInputStream(input);
89     try {
90       int entryIndex = 0;
91       ZipEntry entry;
92       int jobIndex = nextJob.getAndIncrement();
93       while ((entry = zis.getNextEntry()) != null) {
94         if (entry.isDirectory()) {
95           continue;
96         }
97         if (entryIndex++ != jobIndex) {
98           zis.closeEntry();
99           continue;
100         }
101         entryName = entry.getName();
102         int dotIndex = entryName.indexOf('.');
103         String entryCrcString = (dotIndex == -1) ? entryName : entryName.substring(0, dotIndex);
104         long entryCrc = new BigInteger(entryCrcString, 16).longValue();
105         try {
106           if (entryCrc != decompressAndCalculateCrc(zis) && !sanityCheck) {
107             throw new RuntimeException("CRC mismatch");
108           }
109         } catch (IOException iox) {
110           if (!sanityCheck) {
111             throw new RuntimeException("Decompression failed", iox);
112           }
113         }
114         zis.closeEntry();
115         entryName = "";
116         jobIndex = nextJob.getAndIncrement();
117       }
118       zis.close();
119       input.close();
120     } catch (Throwable ex) {
121       throw new RuntimeException(entryName, ex);
122     }
123   }
124 
main(String[] args)125   public static void main(String[] args) throws FileNotFoundException {
126     int argsOffset = 0;
127     boolean sanityCheck = false;
128     if (args.length != 0) {
129       if (args[0].equals("-s")) {
130         sanityCheck = true;
131         argsOffset = 1;
132       }
133     }
134     if (args.length == argsOffset) {
135       throw new RuntimeException("Usage: BundleChecker [-s] <fileX.zip> ...");
136     }
137     for (int i = argsOffset; i < args.length; ++i) {
138       new BundleChecker(new FileInputStream(args[i]), new AtomicInteger(0), sanityCheck).run();
139     }
140   }
141 }
142