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 java.io.IOException;
22 import java.io.InputStream;
23
24 import org.apache.commons.compress.compressors.lz77support.AbstractLZ77CompressorInputStream;
25 import org.apache.commons.compress.utils.ByteUtils;
26
27
28
29
30
31
32
33
34 public class BlockLZ4CompressorInputStream extends AbstractLZ77CompressorInputStream {
35
36 private enum State {
37 NO_BLOCK, IN_LITERAL, LOOKING_FOR_BACK_REFERENCE, IN_BACK_REFERENCE, EOF
38 }
39
40 static final int WINDOW_SIZE = 1 << 16;
41 static final int SIZE_BITS = 4;
42 static final int BACK_REFERENCE_SIZE_MASK = (1 << SIZE_BITS) - 1;
43
44 static final int LITERAL_SIZE_MASK = BACK_REFERENCE_SIZE_MASK << SIZE_BITS;
45
46
47 private int nextBackReferenceSize;
48
49
50 private State state = State.NO_BLOCK;
51
52
53
54
55
56
57 public BlockLZ4CompressorInputStream(final InputStream is) {
58 super(is, WINDOW_SIZE);
59 }
60
61
62
63
64 private boolean initializeBackReference() throws IOException {
65 int backReferenceOffset;
66 try {
67 backReferenceOffset = (int) ByteUtils.fromLittleEndian(supplier, 2);
68 } catch (final IOException ex) {
69 if (nextBackReferenceSize == 0) {
70 return false;
71 }
72 throw ex;
73 }
74 long backReferenceSize = nextBackReferenceSize;
75 if (nextBackReferenceSize == BACK_REFERENCE_SIZE_MASK) {
76 backReferenceSize += readSizeBytes();
77 }
78
79 if (backReferenceSize < 0) {
80 throw new IOException("Illegal block with a negative match length found");
81 }
82 try {
83 startBackReference(backReferenceOffset, backReferenceSize + 4);
84 } catch (final IllegalArgumentException ex) {
85 throw new IOException("Illegal block with bad offset found", ex);
86 }
87 state = State.IN_BACK_REFERENCE;
88 return true;
89 }
90
91
92
93
94 @Override
95 public int read(final byte[] b, final int off, final int len) throws IOException {
96 if (len == 0) {
97 return 0;
98 }
99 switch (state) {
100 case EOF:
101 return -1;
102 case NO_BLOCK:
103 readSizes();
104
105 case IN_LITERAL:
106 final int litLen = readLiteral(b, off, len);
107 if (!hasMoreDataInBlock()) {
108 state = State.LOOKING_FOR_BACK_REFERENCE;
109 }
110 return litLen > 0 ? litLen : read(b, off, len);
111 case LOOKING_FOR_BACK_REFERENCE:
112 if (!initializeBackReference()) {
113 state = State.EOF;
114 return -1;
115 }
116
117 case IN_BACK_REFERENCE:
118 final int backReferenceLen = readBackReference(b, off, len);
119 if (!hasMoreDataInBlock()) {
120 state = State.NO_BLOCK;
121 }
122 return backReferenceLen > 0 ? backReferenceLen : read(b, off, len);
123 default:
124 throw new IOException("Unknown stream state " + state);
125 }
126 }
127
128 private long readSizeBytes() throws IOException {
129 long accum = 0;
130 int nextByte;
131 do {
132 nextByte = readOneByte();
133 if (nextByte == -1) {
134 throw new IOException("Premature end of stream while parsing length");
135 }
136 accum += nextByte;
137 } while (nextByte == 255);
138 return accum;
139 }
140
141 private void readSizes() throws IOException {
142 final int nextBlock = readOneByte();
143 if (nextBlock == -1) {
144 throw new IOException("Premature end of stream while looking for next block");
145 }
146 nextBackReferenceSize = nextBlock & BACK_REFERENCE_SIZE_MASK;
147 long literalSizePart = (nextBlock & LITERAL_SIZE_MASK) >> SIZE_BITS;
148 if (literalSizePart == BACK_REFERENCE_SIZE_MASK) {
149 literalSizePart += readSizeBytes();
150 }
151 if (literalSizePart < 0) {
152 throw new IOException("Illegal block with a negative literal size found");
153 }
154 startLiteral(literalSizePart);
155 state = State.IN_LITERAL;
156 }
157 }