blob: a3ae9863fa3126991042797630f5aa90baf256eb [file] [log] [blame]
/*
* 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;
}
}