/* * IndexDecoder * * Author: Lasse Collin <lasse.collin@tukaani.org> * * This file has been put into the public domain. * You can do whatever you want with this file. */ package org.tukaani.xz.index; import java.io.IOException; import java.io.EOFException; import java.util.zip.CheckedInputStream; import org.tukaani.xz.common.DecoderUtil; import org.tukaani.xz.common.StreamFlags; import org.tukaani.xz.SeekableInputStream; import org.tukaani.xz.CorruptedInputException; import org.tukaani.xz.MemoryLimitException; import org.tukaani.xz.UnsupportedOptionsException; public class IndexDecoder extends IndexBase { private final StreamFlags streamFlags; private final long streamPadding; private final int memoryUsage; // Unpadded Size and Uncompressed Size fields private final long[] unpadded; private final long[] uncompressed; // Uncompressed size of the largest Block. It is used by // SeekableXZInputStream to find out the largest Block of the .xz file. private long largestBlockSize = 0; // Offsets relative to the beginning of the .xz file. These are all zero // for the first Stream in the file. private int recordOffset = 0; private long compressedOffset = 0; private long uncompressedOffset = 0; public IndexDecoder(SeekableInputStream in, StreamFlags streamFooterFlags, long streamPadding, int memoryLimit) throws IOException { super(new CorruptedInputException("XZ Index is corrupt")); this.streamFlags = streamFooterFlags; this.streamPadding = streamPadding; // If endPos is exceeded before the CRC32 field has been decoded, // the Index is corrupt. long endPos = in.position() + streamFooterFlags.backwardSize - 4; java.util.zip.CRC32 crc32 = new java.util.zip.CRC32(); CheckedInputStream inChecked = new CheckedInputStream(in, crc32); // Index Indicator if (inChecked.read() != 0x00) throw new CorruptedInputException("XZ Index is corrupt"); try { // Number of Records long count = DecoderUtil.decodeVLI(inChecked); // Catch Record counts that obviously too high to be valid. // This test isn't exact because it ignores Index Indicator, // Number of Records, and CRC32 fields, but this is good enough // to catch the most obvious problems. if (count >= streamFooterFlags.backwardSize / 2) throw new CorruptedInputException("XZ Index is corrupt"); // If the Record count doesn't fit into an int, we cannot // allocate the arrays to hold the Records. if (count > Integer.MAX_VALUE) throw new UnsupportedOptionsException("XZ Index has over " + Integer.MAX_VALUE + " Records"); // Calculate approximate memory requirements and check the // memory usage limit. memoryUsage = 1 + (int)((16L * count + 1023) / 1024); if (memoryLimit >= 0 && memoryUsage > memoryLimit) throw new MemoryLimitException(memoryUsage, memoryLimit); // Allocate the arrays for the Records. unpadded = new long[(int)count]; uncompressed = new long[(int)count]; int record = 0; // Decode the Records. for (int i = (int)count; i > 0; --i) { // Get the next Record. long unpaddedSize = DecoderUtil.decodeVLI(inChecked); long uncompressedSize = DecoderUtil.decodeVLI(inChecked); // Check that the input position stays sane. Since this is // checked only once per loop iteration instead of for // every input byte read, it's still possible that // EOFException gets thrown with corrupt input. if (in.position() > endPos) throw new CorruptedInputException("XZ Index is corrupt"); // Add the new Record. unpadded[record] = blocksSum + unpaddedSize; uncompressed[record] = uncompressedSum + uncompressedSize; ++record; super.add(unpaddedSize, uncompressedSize); assert record == recordCount; // Remember the uncompressed size of the largest Block. if (largestBlockSize < uncompressedSize) largestBlockSize = uncompressedSize; } } catch (EOFException e) { // EOFException is caught just in case a corrupt input causes // DecoderUtil.decodeVLI to read too much at once. throw new CorruptedInputException("XZ Index is corrupt"); } // Validate that the size of the Index field matches // Backward Size. int indexPaddingSize = getIndexPaddingSize(); if (in.position() + indexPaddingSize != endPos) throw new CorruptedInputException("XZ Index is corrupt"); // Index Padding while (indexPaddingSize-- > 0) if (inChecked.read() != 0x00) throw new CorruptedInputException("XZ Index is corrupt"); // CRC32 long value = crc32.getValue(); for (int i = 0; i < 4; ++i) if (((value >>> (i * 8)) & 0xFF) != in.read()) throw new CorruptedInputException("XZ Index is corrupt"); } public void setOffsets(IndexDecoder prev) { // NOTE: SeekableXZInputStream checks that the total number of Blocks // in concatenated Streams fits into an int. recordOffset = prev.recordOffset + (int)prev.recordCount; compressedOffset = prev.compressedOffset + prev.getStreamSize() + prev.streamPadding; assert (compressedOffset & 3) == 0; uncompressedOffset = prev.uncompressedOffset + prev.uncompressedSum; } public int getMemoryUsage() { return memoryUsage; } public StreamFlags getStreamFlags() { return streamFlags; } public int getRecordCount() { // It was already checked in the constructor that it fits into an int. // Otherwise we couldn't have allocated the arrays. return (int)recordCount; } public long getUncompressedSize() { return uncompressedSum; } public long getLargestBlockSize() { return largestBlockSize; } public boolean hasUncompressedOffset(long pos) { return pos >= uncompressedOffset && pos < uncompressedOffset + uncompressedSum; } public boolean hasRecord(int blockNumber) { return blockNumber >= recordOffset && blockNumber < recordOffset + recordCount; } public void locateBlock(BlockInfo info, long target) { assert target >= uncompressedOffset; target -= uncompressedOffset; assert target < uncompressedSum; int left = 0; int right = unpadded.length - 1; while (left < right) { int i = left + (right - left) / 2; if (uncompressed[i] <= target) left = i + 1; else right = i; } setBlockInfo(info, recordOffset + left); } public void setBlockInfo(BlockInfo info, int blockNumber) { // The caller has checked that the given Block number is inside // this Index. assert blockNumber >= recordOffset; assert blockNumber - recordOffset < recordCount; info.index = this; info.blockNumber = blockNumber; int pos = blockNumber - recordOffset; if (pos == 0) { info.compressedOffset = 0; info.uncompressedOffset = 0; } else { info.compressedOffset = (unpadded[pos - 1] + 3) & ~3; info.uncompressedOffset = uncompressed[pos - 1]; } info.unpaddedSize = unpadded[pos] - info.compressedOffset; info.uncompressedSize = uncompressed[pos] - info.uncompressedOffset; info.compressedOffset += compressedOffset + DecoderUtil.STREAM_HEADER_SIZE; info.uncompressedOffset += uncompressedOffset; } }