View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.compress.compressors.lz4;
20  
21  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
22  import static org.junit.jupiter.api.Assertions.assertEquals;
23  import static org.junit.jupiter.api.Assertions.assertFalse;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.junit.jupiter.api.Assertions.assertTrue;
26  import static org.junit.jupiter.api.Assertions.fail;
27  
28  import java.io.BufferedInputStream;
29  import java.io.ByteArrayInputStream;
30  import java.io.File;
31  import java.io.IOException;
32  import java.io.InputStream;
33  import java.nio.file.Files;
34  import java.util.Arrays;
35  
36  import org.apache.commons.compress.AbstractTest;
37  import org.apache.commons.compress.compressors.CompressorStreamFactory;
38  import org.apache.commons.io.IOUtils;
39  import org.junit.jupiter.api.Test;
40  
41  public final class FramedLZ4CompressorInputStreamTest extends AbstractTest {
42  
43      interface StreamWrapper {
44          InputStream wrap(InputStream in) throws Exception;
45      }
46  
47      private static byte[] duplicate(final byte[] from) {
48          final byte[] to = Arrays.copyOf(from, 2 * from.length);
49          System.arraycopy(from, 0, to, from.length, from.length);
50          return to;
51      }
52  
53      private void expectIOException(final String fileName) {
54          assertThrows(IOException.class, () -> {
55              try (InputStream is = Files.newInputStream(getFile(fileName).toPath());
56                      FramedLZ4CompressorInputStream in = new FramedLZ4CompressorInputStream(is)) {
57                  IOUtils.toByteArray(in);
58              }
59          });
60      }
61  
62      private void readDoubledBlaLz4(final StreamWrapper wrapper, final boolean expectDuplicateOutput) throws Exception {
63          final byte[] singleInput;
64          try (InputStream i = newInputStream("bla.tar.lz4")) {
65              singleInput = IOUtils.toByteArray(i);
66          }
67          final byte[] input = duplicate(singleInput);
68          try (InputStream a = wrapper.wrap(new ByteArrayInputStream(input));
69                  InputStream e = newInputStream("bla.tar")) {
70              final byte[] expected = IOUtils.toByteArray(e);
71              final byte[] actual = IOUtils.toByteArray(a);
72              assertArrayEquals(expectDuplicateOutput ? duplicate(expected) : expected, actual);
73          }
74      }
75  
76      @Test
77      void testBackreferenceAtStartCausesIOException() {
78          expectIOException("COMPRESS-490/ArrayIndexOutOfBoundsException1.lz4");
79      }
80  
81      @Test
82      void testBackreferenceOfSize0CausesIOException() {
83          expectIOException("COMPRESS-490/ArithmeticException.lz4");
84      }
85  
86      @Test
87      void testBackreferenceWithOffsetTooBigCausesIOException() {
88          expectIOException("COMPRESS-490/ArrayIndexOutOfBoundsException2.lz4");
89      }
90  
91      @Test
92      void testMatches() throws IOException {
93          assertFalse(FramedLZ4CompressorInputStream.matches(new byte[10], 4));
94          final byte[] expected = readAllBytes("bla.tar.lz4");
95          assertFalse(FramedLZ4CompressorInputStream.matches(expected, 3));
96          assertTrue(FramedLZ4CompressorInputStream.matches(expected, 4));
97          assertTrue(FramedLZ4CompressorInputStream.matches(expected, 5));
98      }
99  
100     @Test
101     void testMultiByteReadConsistentlyReturnsMinusOneAtEof() throws IOException {
102         final File input = getFile("bla.tar.lz4");
103         final byte[] buf = new byte[2];
104         try (InputStream is = Files.newInputStream(input.toPath());
105                 FramedLZ4CompressorInputStream in = new FramedLZ4CompressorInputStream(is)) {
106             IOUtils.toByteArray(in);
107             assertEquals(-1, in.read(buf));
108             assertEquals(-1, in.read(buf));
109         }
110     }
111 
112     @Test
113     void testReadBlaDumpLz4() throws IOException {
114         try (InputStream a = new FramedLZ4CompressorInputStream(newInputStream("bla.dump.lz4"));
115                 InputStream e = newInputStream("bla.dump")) {
116             final byte[] expected = IOUtils.toByteArray(e);
117             final byte[] actual = IOUtils.toByteArray(a);
118             assertArrayEquals(expected, actual);
119         }
120     }
121 
122     @Test
123     void testReadBlaLz4() throws IOException {
124         try (InputStream a = new FramedLZ4CompressorInputStream(newInputStream("bla.tar.lz4"));
125                 InputStream e = newInputStream("bla.tar")) {
126             final byte[] expected = IOUtils.toByteArray(e);
127             final byte[] actual = IOUtils.toByteArray(a);
128             assertArrayEquals(expected, actual);
129         }
130     }
131 
132     @Test
133     void testReadBlaLz4ViaFactory() throws Exception {
134         try (InputStream a = new CompressorStreamFactory().createCompressorInputStream(CompressorStreamFactory.getLZ4Framed(), newInputStream("bla.tar.lz4"));
135                 InputStream e = newInputStream("bla.tar")) {
136             final byte[] expected = IOUtils.toByteArray(e);
137             final byte[] actual = IOUtils.toByteArray(a);
138             assertArrayEquals(expected, actual);
139         }
140     }
141 
142     @Test
143     void testReadBlaLz4ViaFactoryAutoDetection() throws Exception {
144         try (InputStream a = new CompressorStreamFactory().createCompressorInputStream(new BufferedInputStream(newInputStream("bla.tar.lz4")));
145                 InputStream e = newInputStream("bla.tar")) {
146             final byte[] expected = IOUtils.toByteArray(e);
147             final byte[] actual = IOUtils.toByteArray(a);
148             assertArrayEquals(expected, actual);
149         }
150     }
151 
152     @Test
153     void testReadBlaLz4ViaFactoryWithDecompressConcatenated() throws Exception {
154         try (InputStream a = new CompressorStreamFactory().createCompressorInputStream(CompressorStreamFactory.getLZ4Framed(), newInputStream("bla.tar.lz4"),
155                 true);
156                 InputStream e = newInputStream("bla.tar")) {
157             final byte[] expected = IOUtils.toByteArray(e);
158             final byte[] actual = IOUtils.toByteArray(a);
159             assertArrayEquals(expected, actual);
160         }
161     }
162 
163     @Test
164     void testReadBlaLz4WithDecompressConcatenated() throws IOException {
165         try (InputStream a = new FramedLZ4CompressorInputStream(newInputStream("bla.tar.lz4"), true);
166                 InputStream e = newInputStream("bla.tar")) {
167             final byte[] expected = IOUtils.toByteArray(e);
168             final byte[] actual = IOUtils.toByteArray(a);
169             assertArrayEquals(expected, actual);
170         }
171     }
172 
173     @Test
174     void testReadDoubledBlaLz4ViaFactoryWithDecompressConcatenatedFalse() throws Exception {
175         readDoubledBlaLz4(in -> new CompressorStreamFactory().createCompressorInputStream(CompressorStreamFactory.getLZ4Framed(), in, false), false);
176     }
177 
178     @Test
179     void testReadDoubledBlaLz4ViaFactoryWithDecompressConcatenatedTrue() throws Exception {
180         readDoubledBlaLz4(in -> new CompressorStreamFactory().createCompressorInputStream(CompressorStreamFactory.getLZ4Framed(), in, true), true);
181     }
182 
183     @Test
184     void testReadDoubledBlaLz4ViaFactoryWithoutExplicitDecompressConcatenated() throws Exception {
185         readDoubledBlaLz4(in -> new CompressorStreamFactory().createCompressorInputStream(CompressorStreamFactory.getLZ4Framed(), in), false);
186     }
187 
188     @Test
189     void testReadDoubledBlaLz4WithDecompressConcatenatedFalse() throws Exception {
190         readDoubledBlaLz4(in -> new FramedLZ4CompressorInputStream(in, false), false);
191     }
192 
193     @Test
194     void testReadDoubledBlaLz4WithDecompressConcatenatedTrue() throws Exception {
195         readDoubledBlaLz4(in -> new FramedLZ4CompressorInputStream(in, true), true);
196     }
197 
198     @Test
199     void testReadDoubledBlaLz4WithoutExplicitDecompressConcatenated() throws Exception {
200         readDoubledBlaLz4(FramedLZ4CompressorInputStream::new, false);
201     }
202 
203     @Test
204     void testReadsUncompressedBlocks() throws IOException {
205         final byte[] input = { 4, 0x22, 0x4d, 0x18, // signature
206                 0x60, // flag - Version 01, block independent, no block checksum, no content size, no content checksum
207                 0x70, // block size 4MB
208                 115, // checksum
209                 13, 0, 0, (byte) 0x80, // 13 bytes length and uncompressed bit set
210                 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // content
211                 0, 0, 0, 0, // empty block marker
212         };
213         try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input))) {
214             final byte[] actual = IOUtils.toByteArray(a);
215             assertArrayEquals(new byte[] { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!' }, actual);
216         }
217     }
218 
219     @Test
220     void testReadsUncompressedBlocksUsingSingleByteRead() throws IOException {
221         final byte[] input = { 4, 0x22, 0x4d, 0x18, // signature
222                 0x60, // flag - Version 01, block independent, no block checksum, no content size, no content checksum
223                 0x70, // block size 4MB
224                 115, // checksum
225                 13, 0, 0, (byte) 0x80, // 13 bytes length and uncompressed bit set
226                 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // content
227                 0, 0, 0, 0, // empty block marker
228         };
229         try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input))) {
230             final int h = a.read();
231             assertEquals('H', h);
232         }
233     }
234 
235     @Test
236     void testRejectsBlocksWithoutChecksum() {
237         final byte[] input = { 4, 0x22, 0x4d, 0x18, // signature
238                 0x70, // flag - Version 01, block independent, with block checksum, no content size, no content checksum
239                 0x70, // block size 4MB
240                 114, // checksum
241                 13, 0, 0, (byte) 0x80, // 13 bytes length and uncompressed bit set
242                 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // content
243         };
244         final IOException ex = assertThrows(IOException.class, () -> {
245             try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input))) {
246                 IOUtils.toByteArray(a);
247             }
248         }, "expected exception");
249         assertTrue(ex.getMessage().contains("block checksum"));
250     }
251 
252     @Test
253     void testRejectsFileWithBadHeaderChecksum() {
254         final byte[] input = { 4, 0x22, 0x4d, 0x18, // signature
255                 0x64, // flag - Version 01, block independent, no block checksum, no content size, with content checksum
256                 0x70, // block size 4MB
257                 0, };
258         final IOException ex = assertThrows(IOException.class, () -> {
259             try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input))) {
260                 // do nothing
261             }
262         }, "expected exception");
263         assertTrue(ex.getMessage().contains("header checksum mismatch"));
264     }
265 
266     @Test
267     void testRejectsFileWithInsufficientContentSize() {
268         final byte[] input = { 4, 0x22, 0x4d, 0x18, // signature
269                 0x6C, // flag - Version 01, block independent, no block checksum, with content size, with content checksum
270                 0x70, // block size 4MB
271         };
272         final IOException ex = assertThrows(IOException.class, () -> {
273             try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input))) {
274                 // do nothing
275             }
276         }, "expected exception");
277         assertTrue(ex.getMessage().contains("content size"));
278     }
279 
280     @Test
281     void testRejectsFileWithoutBlockSizeByte() {
282         final byte[] input = { 4, 0x22, 0x4d, 0x18, // signature
283                 0x64, // flag - Version 01, block independent, no block checksum, no content size, with content checksum
284         };
285         final IOException ex = assertThrows(IOException.class, () -> {
286             try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input))) {
287                 // do nothing
288             }
289         }, "expected exception");
290         assertTrue(ex.getMessage().contains("BD byte"));
291     }
292 
293     @Test
294     void testRejectsFileWithoutFrameDescriptor() {
295         final byte[] input = { 4, 0x22, 0x4d, 0x18 // signature
296         };
297         final IOException ex = assertThrows(IOException.class, () -> {
298             try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input))) {
299                 // do nothing
300             }
301         }, "expected exception");
302         assertTrue(ex.getMessage().contains("frame flags"));
303     }
304 
305     @Test
306     void testRejectsFileWithoutHeaderChecksum() {
307         final byte[] input = { 4, 0x22, 0x4d, 0x18, // signature
308                 0x64, // flag - Version 01, block independent, no block checksum, no content size, with content checksum
309                 0x70, // block size 4MB
310         };
311         final IOException ex = assertThrows(IOException.class, () -> {
312             try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input))) {
313                 // do nothing
314             }
315         }, "expected exception");
316         assertTrue(ex.getMessage().contains("header checksum"));
317     }
318 
319     @Test
320     void testRejectsFileWithWrongVersion() {
321         final byte[] input = { 4, 0x22, 0x4d, 0x18, // signature
322                 0x24, // flag - Version 00, block independent, no block checksum, no content size, with content checksum
323         };
324         final IOException ex = assertThrows(IOException.class, () -> {
325             try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input))) {
326                 // do nothing
327             }
328         }, "expected exception");
329         assertTrue(ex.getMessage().contains("version"));
330     }
331 
332     @Test
333     void testRejectsNonLZ4Stream() {
334         assertThrows(IOException.class, () -> new FramedLZ4CompressorInputStream(newInputStream("bla.tar")));
335     }
336 
337     @Test
338     void testRejectsSkippableFrameFollowedByJunk() {
339         final byte[] input = { 4, 0x22, 0x4d, 0x18, // signature
340                 0x60, // flag - Version 01, block independent, no block checksum, no content size, no content checksum
341                 0x70, // block size 4MB
342                 115, // checksum
343                 13, 0, 0, (byte) 0x80, // 13 bytes length and uncompressed bit set
344                 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // content
345                 0, 0, 0, 0, // empty block marker
346                 0x50, 0x2a, 0x4d, 0x18, // skippable frame signature
347                 2, 0, 0, 0, // skippable frame has length 2
348                 1, 2, // content of skippable frame
349                 1, 0x22, 0x4d, 0x18, // bad signature
350         };
351         final IOException ex = assertThrows(IOException.class, () -> {
352             try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input), true)) {
353                 IOUtils.toByteArray(a);
354             }
355         }, "expected exception");
356         assertTrue(ex.getMessage().contains("garbage"));
357     }
358 
359     @Test
360     void testRejectsSkippableFrameFollowedByTooFewBytes() {
361         final byte[] input = { 4, 0x22, 0x4d, 0x18, // signature
362                 0x60, // flag - Version 01, block independent, no block checksum, no content size, no content checksum
363                 0x70, // block size 4MB
364                 115, // checksum
365                 13, 0, 0, (byte) 0x80, // 13 bytes length and uncompressed bit set
366                 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // content
367                 0, 0, 0, 0, // empty block marker
368                 0x52, 0x2a, 0x4d, 0x18, // skippable frame signature
369                 2, 0, 0, 0, // skippable frame has length 2
370                 1, 2, // content of skippable frame
371                 4, // too short for signature
372         };
373         final IOException ex = assertThrows(IOException.class, () -> {
374             try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input), true)) {
375                 IOUtils.toByteArray(a);
376             }
377         }, "expected exception");
378         assertTrue(ex.getMessage().contains("garbage"));
379     }
380 
381     @Test
382     void testRejectsSkippableFrameWithBadSignaturePrefix() {
383         final byte[] input = { 4, 0x22, 0x4d, 0x18, // signature
384                 0x60, // flag - Version 01, block independent, no block checksum, no content size, no content checksum
385                 0x70, // block size 4MB
386                 115, // checksum
387                 13, 0, 0, (byte) 0x80, // 13 bytes length and uncompressed bit set
388                 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // content
389                 0, 0, 0, 0, // empty block marker
390                 0x60, 0x2a, 0x4d, 0x18, // broken skippable frame signature
391         };
392         final IOException ex = assertThrows(IOException.class, () -> {
393             try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input), true)) {
394                 IOUtils.toByteArray(a);
395                 fail();
396             }
397         }, "expected exception");
398         assertTrue(ex.getMessage().contains("garbage"));
399     }
400 
401     @Test
402     void testRejectsSkippableFrameWithBadSignatureTrailer() {
403         final byte[] input = { 4, 0x22, 0x4d, 0x18, // signature
404                 0x60, // flag - Version 01, block independent, no block checksum, no content size, no content checksum
405                 0x70, // block size 4MB
406                 115, // checksum
407                 13, 0, 0, (byte) 0x80, // 13 bytes length and uncompressed bit set
408                 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // content
409                 0, 0, 0, 0, // empty block marker
410                 0x51, 0x2a, 0x4d, 0x17, // broken skippable frame signature
411         };
412         final IOException ex = assertThrows(IOException.class, () -> {
413             try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input), true)) {
414                 IOUtils.toByteArray(a);
415             }
416         }, "expected exception");
417         assertTrue(ex.getMessage().contains("garbage"));
418     }
419 
420     @Test
421     void testRejectsSkippableFrameWithPrematureEnd() {
422         final byte[] input = { 4, 0x22, 0x4d, 0x18, // signature
423                 0x60, // flag - Version 01, block independent, no block checksum, no content size, no content checksum
424                 0x70, // block size 4MB
425                 115, // checksum
426                 13, 0, 0, (byte) 0x80, // 13 bytes length and uncompressed bit set
427                 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // content
428                 0, 0, 0, 0, // empty block marker
429                 0x50, 0x2a, 0x4d, 0x18, // skippable frame signature
430                 2, 0, 0, 0, // skippable frame has length 2
431                 1, // content of skippable frame (should be two bytes)
432         };
433         final IOException ex = assertThrows(IOException.class, () -> {
434             try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input), true)) {
435                 IOUtils.toByteArray(a);
436             }
437         }, "expected exception");
438         assertTrue(ex.getMessage().contains("Premature end of stream while skipping frame"));
439     }
440 
441     @Test
442     void testRejectsSkippableFrameWithPrematureEndInLengthBytes() {
443         final byte[] input = { 4, 0x22, 0x4d, 0x18, // signature
444                 0x60, // flag - Version 01, block independent, no block checksum, no content size, no content checksum
445                 0x70, // block size 4MB
446                 115, // checksum
447                 13, 0, 0, (byte) 0x80, // 13 bytes length and uncompressed bit set
448                 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // content
449                 0, 0, 0, 0, // empty block marker
450                 0x55, 0x2a, 0x4d, 0x18, // skippable frame signature
451                 2, 0, 0, // should be four byte length
452         };
453         final IOException ex = assertThrows(IOException.class, () -> {
454             try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input), true)) {
455                 IOUtils.toByteArray(a);
456             }
457         }, "expected exception");
458         assertTrue(ex.getMessage().contains("Premature end of data"));
459     }
460 
461     @Test
462     void testRejectsStreamsWithBadContentChecksum() {
463         final byte[] input = { 4, 0x22, 0x4d, 0x18, // signature
464                 0x64, // flag - Version 01, block independent, no block checksum, no content size, with content checksum
465                 0x70, // block size 4MB
466                 (byte) 185, // checksum
467                 13, 0, 0, (byte) 0x80, // 13 bytes length and uncompressed bit set
468                 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // content
469                 0, 0, 0, 0, // empty block marker
470                 1, 2, 3, 4, };
471         final IOException ex = assertThrows(IOException.class, () -> {
472             try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input))) {
473                 IOUtils.toByteArray(a);
474             }
475         }, "expected exception");
476         assertTrue(ex.getMessage().contains("content checksum mismatch"));
477     }
478 
479     @Test
480     void testRejectsStreamsWithoutContentChecksum() {
481         final byte[] input = { 4, 0x22, 0x4d, 0x18, // signature
482                 0x64, // flag - Version 01, block independent, no block checksum, no content size, with content checksum
483                 0x70, // block size 4MB
484                 (byte) 185, // checksum
485                 13, 0, 0, (byte) 0x80, // 13 bytes length and uncompressed bit set
486                 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // content
487                 0, 0, 0, 0, // empty block marker
488         };
489         final IOException ex = assertThrows(IOException.class, () -> {
490             try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input))) {
491                 IOUtils.toByteArray(a);
492             }
493         }, "expected exception");
494         assertTrue(ex.getMessage().contains("content checksum"));
495     }
496 
497     @Test
498     void testRejectsTrailingBytesAfterValidFrame() {
499         final byte[] input = { 4, 0x22, 0x4d, 0x18, // signature
500                 0x60, // flag - Version 01, block independent, no block checksum, no content size, no content checksum
501                 0x70, // block size 4MB
502                 115, // checksum
503                 13, 0, 0, (byte) 0x80, // 13 bytes length and uncompressed bit set
504                 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // content
505                 0, 0, 0, 0, // empty block marker
506                 0x56, 0x2a, 0x4d, // too short for any signature
507         };
508         final IOException ex = assertThrows(IOException.class, () -> {
509             try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input), true)) {
510                 IOUtils.toByteArray(a);
511             }
512         }, "expected exception");
513         assertTrue(ex.getMessage().contains("garbage"));
514     }
515 
516     @Test
517     void testSingleByteReadConsistentlyReturnsMinusOneAtEof() throws IOException {
518         final File input = getFile("bla.tar.lz4");
519         try (InputStream is = Files.newInputStream(input.toPath());
520                 FramedLZ4CompressorInputStream in = new FramedLZ4CompressorInputStream(is);) {
521             IOUtils.toByteArray(in);
522             assertEquals(-1, in.read());
523             assertEquals(-1, in.read());
524         }
525     }
526 
527     @Test
528     void testSkipsOverSkippableFrames() throws IOException {
529         final byte[] input = { 4, 0x22, 0x4d, 0x18, // signature
530                 0x60, // flag - Version 01, block independent, no block checksum, no content size, no content checksum
531                 0x70, // block size 4MB
532                 115, // checksum
533                 13, 0, 0, (byte) 0x80, // 13 bytes length and uncompressed bit set
534                 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // content
535                 0, 0, 0, 0, // empty block marker
536                 0x5f, 0x2a, 0x4d, 0x18, // skippable frame signature
537                 2, 0, 0, 0, // skippable frame has length 2
538                 1, 2, // content of skippable frame
539                 4, 0x22, 0x4d, 0x18, // signature
540                 0x60, // flag - Version 01, block independent, no block checksum, no content size, no content checksum
541                 0x70, // block size 4MB
542                 115, // checksum
543                 1, 0, 0, (byte) 0x80, // 1 bytes length and uncompressed bit set
544                 '!', // content
545                 0, 0, 0, 0, // empty block marker
546         };
547         try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input), true)) {
548             final byte[] actual = IOUtils.toByteArray(a);
549             assertArrayEquals(new byte[] { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '!' }, actual);
550         }
551     }
552 
553     @Test
554     void testSkipsOverTrailingSkippableFrames() throws IOException {
555         final byte[] input = { 4, 0x22, 0x4d, 0x18, // signature
556                 0x60, // flag - Version 01, block independent, no block checksum, no content size, no content checksum
557                 0x70, // block size 4MB
558                 115, // checksum
559                 13, 0, 0, (byte) 0x80, // 13 bytes length and uncompressed bit set
560                 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // content
561                 0, 0, 0, 0, // empty block marker
562                 0x51, 0x2a, 0x4d, 0x18, // skippable frame signature
563                 2, 0, 0, 0, // skippable frame has length 2
564                 1, 2, // content of skippable frame
565         };
566         try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input), true)) {
567             final byte[] actual = IOUtils.toByteArray(a);
568             assertArrayEquals(new byte[] { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!' }, actual);
569         }
570     }
571 
572 }