1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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,
206 0x60,
207 0x70,
208 115,
209 13, 0, 0, (byte) 0x80,
210 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!',
211 0, 0, 0, 0,
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,
222 0x60,
223 0x70,
224 115,
225 13, 0, 0, (byte) 0x80,
226 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!',
227 0, 0, 0, 0,
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,
238 0x70,
239 0x70,
240 114,
241 13, 0, 0, (byte) 0x80,
242 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!',
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,
255 0x64,
256 0x70,
257 0, };
258 final IOException ex = assertThrows(IOException.class, () -> {
259 try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input))) {
260
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,
269 0x6C,
270 0x70,
271 };
272 final IOException ex = assertThrows(IOException.class, () -> {
273 try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input))) {
274
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,
283 0x64,
284 };
285 final IOException ex = assertThrows(IOException.class, () -> {
286 try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input))) {
287
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
296 };
297 final IOException ex = assertThrows(IOException.class, () -> {
298 try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input))) {
299
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,
308 0x64,
309 0x70,
310 };
311 final IOException ex = assertThrows(IOException.class, () -> {
312 try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input))) {
313
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,
322 0x24,
323 };
324 final IOException ex = assertThrows(IOException.class, () -> {
325 try (InputStream a = new FramedLZ4CompressorInputStream(new ByteArrayInputStream(input))) {
326
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,
340 0x60,
341 0x70,
342 115,
343 13, 0, 0, (byte) 0x80,
344 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!',
345 0, 0, 0, 0,
346 0x50, 0x2a, 0x4d, 0x18,
347 2, 0, 0, 0,
348 1, 2,
349 1, 0x22, 0x4d, 0x18,
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,
362 0x60,
363 0x70,
364 115,
365 13, 0, 0, (byte) 0x80,
366 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!',
367 0, 0, 0, 0,
368 0x52, 0x2a, 0x4d, 0x18,
369 2, 0, 0, 0,
370 1, 2,
371 4,
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,
384 0x60,
385 0x70,
386 115,
387 13, 0, 0, (byte) 0x80,
388 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!',
389 0, 0, 0, 0,
390 0x60, 0x2a, 0x4d, 0x18,
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,
404 0x60,
405 0x70,
406 115,
407 13, 0, 0, (byte) 0x80,
408 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!',
409 0, 0, 0, 0,
410 0x51, 0x2a, 0x4d, 0x17,
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,
423 0x60,
424 0x70,
425 115,
426 13, 0, 0, (byte) 0x80,
427 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!',
428 0, 0, 0, 0,
429 0x50, 0x2a, 0x4d, 0x18,
430 2, 0, 0, 0,
431 1,
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,
444 0x60,
445 0x70,
446 115,
447 13, 0, 0, (byte) 0x80,
448 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!',
449 0, 0, 0, 0,
450 0x55, 0x2a, 0x4d, 0x18,
451 2, 0, 0,
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,
464 0x64,
465 0x70,
466 (byte) 185,
467 13, 0, 0, (byte) 0x80,
468 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!',
469 0, 0, 0, 0,
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,
482 0x64,
483 0x70,
484 (byte) 185,
485 13, 0, 0, (byte) 0x80,
486 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!',
487 0, 0, 0, 0,
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,
500 0x60,
501 0x70,
502 115,
503 13, 0, 0, (byte) 0x80,
504 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!',
505 0, 0, 0, 0,
506 0x56, 0x2a, 0x4d,
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,
530 0x60,
531 0x70,
532 115,
533 13, 0, 0, (byte) 0x80,
534 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!',
535 0, 0, 0, 0,
536 0x5f, 0x2a, 0x4d, 0x18,
537 2, 0, 0, 0,
538 1, 2,
539 4, 0x22, 0x4d, 0x18,
540 0x60,
541 0x70,
542 115,
543 1, 0, 0, (byte) 0x80,
544 '!',
545 0, 0, 0, 0,
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,
556 0x60,
557 0x70,
558 115,
559 13, 0, 0, (byte) 0x80,
560 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!',
561 0, 0, 0, 0,
562 0x51, 0x2a, 0x4d, 0x18,
563 2, 0, 0, 0,
564 1, 2,
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 }