]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.lz4/src/net/jpountz/lz4/LZ4BlockInputStream.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.lz4 / src / net / jpountz / lz4 / LZ4BlockInputStream.java
1 package net.jpountz.lz4;
2
3 /*
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
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
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;
24
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;
30
31 import net.jpountz.util.SafeUtils;
32 import net.jpountz.xxhash.StreamingXXHash32;
33 import net.jpountz.xxhash.XXHash32;
34 import net.jpountz.xxhash.XXHashFactory;
35
36 /**
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
41  */
42 public final class LZ4BlockInputStream extends FilterInputStream {
43
44   private final LZ4FastDecompressor decompressor;
45   private final Checksum checksum;
46   private byte[] buffer;
47   private byte[] compressedBuffer;
48   private int originalLen;
49   private int o;
50   private boolean finished;
51
52   /**
53    * Create a new {@link InputStream}.
54    *
55    * @param in            the {@link InputStream} to poll
56    * @param decompressor  the {@link LZ4FastDecompressor decompressor} instance to
57    *                      use
58    * @param checksum      the {@link Checksum} instance to use, must be
59    *                      equivalent to the instance which has been used to
60    *                      write the stream
61    */
62   public LZ4BlockInputStream(InputStream in, LZ4FastDecompressor decompressor, Checksum checksum) {
63     super(in);
64     this.decompressor = decompressor;
65     this.checksum = checksum;
66     this.buffer = new byte[0];
67     this.compressedBuffer = new byte[HEADER_LENGTH];
68     o = originalLen = 0;
69     finished = false;
70   }
71
72   /**
73    * Create a new instance using {@link XXHash32} for checksuming.
74    * @see #LZ4BlockInputStream(InputStream, LZ4FastDecompressor, Checksum)
75    * @see StreamingXXHash32#asChecksum()
76    */
77   public LZ4BlockInputStream(InputStream in, LZ4FastDecompressor decompressor) {
78     this(in, decompressor, XXHashFactory.fastestInstance().newStreamingHash32(DEFAULT_SEED).asChecksum());
79   }
80
81   /**
82    * Create a new instance which uses the fastest {@link LZ4FastDecompressor} available.
83    * @see LZ4Factory#fastestInstance()
84    * @see #LZ4BlockInputStream(InputStream, LZ4FastDecompressor)
85    */
86   public LZ4BlockInputStream(InputStream in) {
87     this(in, LZ4Factory.fastestInstance().fastDecompressor());
88   }
89
90   @Override
91   public int available() throws IOException {
92     return originalLen - o;
93   }
94
95   @Override
96   public int read() throws IOException {
97     if (finished) {
98       return -1;
99     }
100     if (o == originalLen) {
101       refill();
102     }
103     if (finished) {
104       return -1;
105     }
106     return buffer[o++] & 0xFF;
107   }
108
109   @Override
110   public int read(byte[] b, int off, int len) throws IOException {
111     SafeUtils.checkRange(b, off, len);
112     if (finished) {
113       return -1;
114     }
115     if (o == originalLen) {
116       refill();
117     }
118     if (finished) {
119       return -1;
120     }
121     len = Math.min(len, originalLen - o);
122     System.arraycopy(buffer, o, b, off, len);
123     o += len;
124     return len;
125   }
126
127   @Override
128   public int read(byte[] b) throws IOException {
129     return read(b, 0, b.length);
130   }
131
132   @Override
133   public long skip(long n) throws IOException {
134     if (finished) {
135       return -1;
136     }
137     if (o == originalLen) {
138       refill();
139     }
140     if (finished) {
141       return -1;
142     }
143     final int skipped = (int) Math.min(n, originalLen - o);
144     o += skipped;
145     return skipped;
146   }
147
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");
153       }
154     }
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");
160     }
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
166         || originalLen < 0
167         || compressedLen < 0
168         || (originalLen == 0 && compressedLen != 0)
169         || (originalLen != 0 && compressedLen == 0)
170         || (compressionMethod == COMPRESSION_METHOD_RAW && originalLen != compressedLen)) {
171       throw new IOException("Stream is corrupted");
172     }
173     if (originalLen == 0 && compressedLen == 0) {
174       if (check != 0) {
175         throw new IOException("Stream is corrupted");
176       }
177       finished = true;
178       return;
179     }
180     if (buffer.length < originalLen) {
181       buffer = new byte[Math.max(originalLen, buffer.length * 3 / 2)];
182     }
183     switch (compressionMethod) {
184     case COMPRESSION_METHOD_RAW:
185       readFully(buffer, originalLen);
186       break;
187     case COMPRESSION_METHOD_LZ4:
188       if (compressedBuffer.length < originalLen) {
189         compressedBuffer = new byte[Math.max(compressedLen, compressedBuffer.length * 3 / 2)];
190       }
191       readFully(compressedBuffer, compressedLen);
192       try {
193         final int compressedLen2 = decompressor.decompress(compressedBuffer, 0, buffer, 0, originalLen);
194         if (compressedLen != compressedLen2) {
195           throw new IOException("Stream is corrupted");
196         }
197       } catch (LZ4Exception e) {
198         throw new IOException("Stream is corrupted", e);
199       }
200       break;
201     default:
202       throw new AssertionError();
203     }
204     checksum.reset();
205     checksum.update(buffer, 0, originalLen);
206     if ((int) checksum.getValue() != check) {
207       throw new IOException("Stream is corrupted");
208     }
209     o = 0;
210   }
211
212   private void readFully(byte[] b, int len) throws IOException {
213     int read = 0;
214     while (read < len) {
215       final int r = in.read(b, read, len - read);
216       if (r < 0) {
217         throw new EOFException("Stream ended prematurely");
218       }
219       read += r;
220     }
221     assert len == read;
222   }
223
224   @Override
225   public boolean markSupported() {
226     return false;
227   }
228
229   @SuppressWarnings("sync-override")
230   @Override
231   public void mark(int readlimit) {
232     // unsupported
233   }
234
235   @SuppressWarnings("sync-override")
236   @Override
237   public void reset() throws IOException {
238     throw new IOException("mark/reset not supported");
239   }
240
241   @Override
242   public String toString() {
243     return getClass().getSimpleName() + "(in=" + in
244         + ", decompressor=" + decompressor + ", checksum=" + checksum + ")";
245   }
246
247 }