/* Copyright 2016 Google Inc. All Rights Reserved. Distributed under MIT license. See file LICENSE for detail or copy at https://opensource.org/licenses/MIT */ package org.brotli.integration; import org.brotli.dec.BrotliInputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.util.concurrent.atomic.AtomicInteger; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; /** * Decompress files and (optionally) checks their checksums. * *

File are read from ZIP archive passed as an array of bytes. Multiple checkers negotiate about * task distribution via shared AtomicInteger counter. *

All entries are expected to be valid brotli compressed streams and output CRC64 checksum * is expected to match the checksum hex-encoded in the first part of entry name. */ public class BundleChecker implements Runnable { private final AtomicInteger nextJob; private final InputStream input; private final boolean sanityCheck; /** * @param sanityCheck do not calculate checksum and ignore {@link IOException}. */ public BundleChecker(InputStream input, AtomicInteger nextJob, boolean sanityCheck) { this.input = input; this.nextJob = nextJob; this.sanityCheck = sanityCheck; } private long decompressAndCalculateCrc(ZipInputStream input) throws IOException { /* Do not allow entry readers to close the whole ZipInputStream. */ FilterInputStream entryStream = new FilterInputStream(input) { @Override public void close() {} }; BrotliInputStream decompressedStream = new BrotliInputStream(entryStream); long crc; try { crc = BundleHelper.fingerprintStream(decompressedStream); } finally { decompressedStream.close(); } return crc; } @Override public void run() { String entryName = ""; ZipInputStream zis = new ZipInputStream(input); try { int entryIndex = 0; ZipEntry entry; int jobIndex = nextJob.getAndIncrement(); while ((entry = zis.getNextEntry()) != null) { if (entry.isDirectory()) { continue; } if (entryIndex++ != jobIndex) { zis.closeEntry(); continue; } entryName = entry.getName(); long entryCrc = BundleHelper.getExpectedFingerprint(entryName); try { if (entryCrc != decompressAndCalculateCrc(zis) && !sanityCheck) { throw new RuntimeException("CRC mismatch"); } } catch (IOException iox) { if (!sanityCheck) { throw new RuntimeException("Decompression failed", iox); } } zis.closeEntry(); entryName = ""; jobIndex = nextJob.getAndIncrement(); } zis.close(); input.close(); } catch (Throwable ex) { throw new RuntimeException(entryName, ex); } } public static void main(String[] args) throws FileNotFoundException { int argsOffset = 0; boolean sanityCheck = false; if (args.length != 0) { if (args[0].equals("-s")) { sanityCheck = true; argsOffset = 1; } } if (args.length == argsOffset) { throw new RuntimeException("Usage: BundleChecker [-s] ..."); } for (int i = argsOffset; i < args.length; ++i) { new BundleChecker(new FileInputStream(args[i]), new AtomicInteger(0), sanityCheck).run(); } } }