| /* |
| * 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; |
| } |
| } |