1 package net.jpountz.lz4;
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 import static net.jpountz.lz4.LZ4BlockOutputStream.COMPRESSION_LEVEL_BASE;
18 import static net.jpountz.lz4.LZ4BlockOutputStream.COMPRESSION_METHOD_LZ4;
19 import static net.jpountz.lz4.LZ4BlockOutputStream.COMPRESSION_METHOD_RAW;
20 import static net.jpountz.lz4.LZ4BlockOutputStream.DEFAULT_SEED;
21 import static net.jpountz.lz4.LZ4BlockOutputStream.HEADER_LENGTH;
22 import static net.jpountz.lz4.LZ4BlockOutputStream.MAGIC;
23 import static net.jpountz.lz4.LZ4BlockOutputStream.MAGIC_LENGTH;
25 import java.io.EOFException;
26 import java.io.FilterInputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.util.zip.Checksum;
31 import net.jpountz.util.SafeUtils;
32 import net.jpountz.xxhash.StreamingXXHash32;
33 import net.jpountz.xxhash.XXHash32;
34 import net.jpountz.xxhash.XXHashFactory;
37 * {@link InputStream} implementation to decode data written with
38 * {@link LZ4BlockOutputStream}. This class is not thread-safe and does not
39 * support {@link #mark(int)}/{@link #reset()}.
40 * @see LZ4BlockOutputStream
42 public final class LZ4BlockInputStream extends FilterInputStream {
44 private final LZ4FastDecompressor decompressor;
45 private final Checksum checksum;
46 private byte[] buffer;
47 private byte[] compressedBuffer;
48 private int originalLen;
50 private boolean finished;
53 * Create a new {@link InputStream}.
55 * @param in the {@link InputStream} to poll
56 * @param decompressor the {@link LZ4FastDecompressor decompressor} instance to
58 * @param checksum the {@link Checksum} instance to use, must be
59 * equivalent to the instance which has been used to
62 public LZ4BlockInputStream(InputStream in, LZ4FastDecompressor decompressor, Checksum checksum) {
64 this.decompressor = decompressor;
65 this.checksum = checksum;
66 this.buffer = new byte[0];
67 this.compressedBuffer = new byte[HEADER_LENGTH];
73 * Create a new instance using {@link XXHash32} for checksuming.
74 * @see #LZ4BlockInputStream(InputStream, LZ4FastDecompressor, Checksum)
75 * @see StreamingXXHash32#asChecksum()
77 public LZ4BlockInputStream(InputStream in, LZ4FastDecompressor decompressor) {
78 this(in, decompressor, XXHashFactory.fastestInstance().newStreamingHash32(DEFAULT_SEED).asChecksum());
82 * Create a new instance which uses the fastest {@link LZ4FastDecompressor} available.
83 * @see LZ4Factory#fastestInstance()
84 * @see #LZ4BlockInputStream(InputStream, LZ4FastDecompressor)
86 public LZ4BlockInputStream(InputStream in) {
87 this(in, LZ4Factory.fastestInstance().fastDecompressor());
91 public int available() throws IOException {
92 return originalLen - o;
96 public int read() throws IOException {
100 if (o == originalLen) {
106 return buffer[o++] & 0xFF;
110 public int read(byte[] b, int off, int len) throws IOException {
111 SafeUtils.checkRange(b, off, len);
115 if (o == originalLen) {
121 len = Math.min(len, originalLen - o);
122 System.arraycopy(buffer, o, b, off, len);
128 public int read(byte[] b) throws IOException {
129 return read(b, 0, b.length);
133 public long skip(long n) throws IOException {
137 if (o == originalLen) {
143 final int skipped = (int) Math.min(n, originalLen - o);
148 private void refill() throws IOException {
149 readFully(compressedBuffer, HEADER_LENGTH);
150 for (int i = 0; i < MAGIC_LENGTH; ++i) {
151 if (compressedBuffer[i] != MAGIC[i]) {
152 throw new IOException("Stream is corrupted");
155 final int token = compressedBuffer[MAGIC_LENGTH] & 0xFF;
156 final int compressionMethod = token & 0xF0;
157 final int compressionLevel = COMPRESSION_LEVEL_BASE + (token & 0x0F);
158 if (compressionMethod != COMPRESSION_METHOD_RAW && compressionMethod != COMPRESSION_METHOD_LZ4) {
159 throw new IOException("Stream is corrupted");
161 final int compressedLen = SafeUtils.readIntLE(compressedBuffer, MAGIC_LENGTH + 1);
162 originalLen = SafeUtils.readIntLE(compressedBuffer, MAGIC_LENGTH + 5);
163 final int check = SafeUtils.readIntLE(compressedBuffer, MAGIC_LENGTH + 9);
164 assert HEADER_LENGTH == MAGIC_LENGTH + 13;
165 if (originalLen > 1 << compressionLevel
168 || (originalLen == 0 && compressedLen != 0)
169 || (originalLen != 0 && compressedLen == 0)
170 || (compressionMethod == COMPRESSION_METHOD_RAW && originalLen != compressedLen)) {
171 throw new IOException("Stream is corrupted");
173 if (originalLen == 0 && compressedLen == 0) {
175 throw new IOException("Stream is corrupted");
180 if (buffer.length < originalLen) {
181 buffer = new byte[Math.max(originalLen, buffer.length * 3 / 2)];
183 switch (compressionMethod) {
184 case COMPRESSION_METHOD_RAW:
185 readFully(buffer, originalLen);
187 case COMPRESSION_METHOD_LZ4:
188 if (compressedBuffer.length < originalLen) {
189 compressedBuffer = new byte[Math.max(compressedLen, compressedBuffer.length * 3 / 2)];
191 readFully(compressedBuffer, compressedLen);
193 final int compressedLen2 = decompressor.decompress(compressedBuffer, 0, buffer, 0, originalLen);
194 if (compressedLen != compressedLen2) {
195 throw new IOException("Stream is corrupted");
197 } catch (LZ4Exception e) {
198 throw new IOException("Stream is corrupted", e);
202 throw new AssertionError();
205 checksum.update(buffer, 0, originalLen);
206 if ((int) checksum.getValue() != check) {
207 throw new IOException("Stream is corrupted");
212 private void readFully(byte[] b, int len) throws IOException {
215 final int r = in.read(b, read, len - read);
217 throw new EOFException("Stream ended prematurely");
225 public boolean markSupported() {
229 @SuppressWarnings("sync-override")
231 public void mark(int readlimit) {
235 @SuppressWarnings("sync-override")
237 public void reset() throws IOException {
238 throw new IOException("mark/reset not supported");
242 public String toString() {
243 return getClass().getSimpleName() + "(in=" + in
244 + ", decompressor=" + decompressor + ", checksum=" + checksum + ")";