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.snappy;
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
35
36
37
38
39 public class SnappyCompressorInputStream extends AbstractLZ77CompressorInputStream {
40
41 private enum State {
42 NO_BLOCK, IN_LITERAL, IN_BACK_REFERENCE
43 }
44
45
46 private static final int TAG_MASK = 0x03;
47
48
49 public static final int DEFAULT_BLOCK_SIZE = 32768;
50
51
52 private final int size;
53
54
55 private int uncompressedBytesRemaining;
56
57
58 private State state = State.NO_BLOCK;
59
60 private boolean endReached;
61
62
63
64
65
66
67
68 public SnappyCompressorInputStream(final InputStream is) throws IOException {
69 this(is, DEFAULT_BLOCK_SIZE);
70 }
71
72
73
74
75
76
77
78
79
80 public SnappyCompressorInputStream(final InputStream is, final int blockSize) throws IOException {
81 super(is, blockSize);
82 uncompressedBytesRemaining = size = (int) readSize();
83 }
84
85
86
87
88 private void fill() throws IOException {
89 if (uncompressedBytesRemaining == 0) {
90 endReached = true;
91 return;
92 }
93
94 int b = readOneByte();
95 if (b == -1) {
96 throw new IOException("Premature end of stream reading block start");
97 }
98 int length = 0;
99 int offset = 0;
100
101 switch (b & TAG_MASK) {
102
103 case 0x00:
104
105 length = readLiteralLength(b);
106 if (length < 0) {
107 throw new IOException("Illegal block with a negative literal size found");
108 }
109 uncompressedBytesRemaining -= length;
110 startLiteral(length);
111 state = State.IN_LITERAL;
112 break;
113
114 case 0x01:
115
116
117
118
119
120
121
122 length = 4 + (b >> 2 & 0x07);
123 uncompressedBytesRemaining -= length;
124 offset = (b & 0xE0) << 3;
125 b = readOneByte();
126 if (b == -1) {
127 throw new IOException("Premature end of stream reading back-reference length");
128 }
129 offset |= b;
130
131 try {
132 startBackReference(offset, length);
133 } catch (final IllegalArgumentException ex) {
134 throw new IOException("Illegal block with bad offset found", ex);
135 }
136 state = State.IN_BACK_REFERENCE;
137 break;
138
139 case 0x02:
140
141
142
143
144
145
146 length = (b >> 2) + 1;
147 if (length < 0) {
148 throw new IOException("Illegal block with a negative match length found");
149 }
150 uncompressedBytesRemaining -= length;
151
152 offset = (int) ByteUtils.fromLittleEndian(supplier, 2);
153
154 try {
155 startBackReference(offset, length);
156 } catch (final IllegalArgumentException ex) {
157 throw new IOException("Illegal block with bad offset found", ex);
158 }
159 state = State.IN_BACK_REFERENCE;
160 break;
161
162 case 0x03:
163
164
165
166
167
168
169 length = (b >> 2) + 1;
170 if (length < 0) {
171 throw new IOException("Illegal block with a negative match length found");
172 }
173 uncompressedBytesRemaining -= length;
174
175 offset = (int) ByteUtils.fromLittleEndian(supplier, 4) & 0x7fffffff;
176
177 try {
178 startBackReference(offset, length);
179 } catch (final IllegalArgumentException ex) {
180 throw new IOException("Illegal block with bad offset found", ex);
181 }
182 state = State.IN_BACK_REFERENCE;
183 break;
184 default:
185
186 break;
187 }
188 }
189
190
191
192
193
194
195 @Override
196 public int getSize() {
197 return size;
198 }
199
200
201
202
203 @Override
204 public int read(final byte[] b, final int off, final int len) throws IOException {
205 if (len == 0) {
206 return 0;
207 }
208 if (endReached) {
209 return -1;
210 }
211 switch (state) {
212 case NO_BLOCK:
213 fill();
214 return read(b, off, len);
215 case IN_LITERAL:
216 final int litLen = readLiteral(b, off, len);
217 if (!hasMoreDataInBlock()) {
218 state = State.NO_BLOCK;
219 }
220 return litLen > 0 ? litLen : read(b, off, len);
221 case IN_BACK_REFERENCE:
222 final int backReferenceLen = readBackReference(b, off, len);
223 if (!hasMoreDataInBlock()) {
224 state = State.NO_BLOCK;
225 }
226 return backReferenceLen > 0 ? backReferenceLen : read(b, off, len);
227 default:
228 throw new IOException("Unknown stream state " + state);
229 }
230 }
231
232
233
234
235
236
237 private int readLiteralLength(final int b) throws IOException {
238 final int length;
239 switch (b >> 2) {
240 case 60:
241 length = readOneByte();
242 if (length == -1) {
243 throw new IOException("Premature end of stream reading literal length");
244 }
245 break;
246 case 61:
247 length = (int) ByteUtils.fromLittleEndian(supplier, 2);
248 break;
249 case 62:
250 length = (int) ByteUtils.fromLittleEndian(supplier, 3);
251 break;
252 case 63:
253 length = (int) ByteUtils.fromLittleEndian(supplier, 4);
254 break;
255 default:
256 length = b >> 2;
257 break;
258 }
259
260 return length + 1;
261 }
262
263
264
265
266
267
268
269
270
271 private long readSize() throws IOException {
272 int index = 0;
273 long sz = 0;
274 int b = 0;
275
276 do {
277 b = readOneByte();
278 if (b == -1) {
279 throw new IOException("Premature end of stream reading size");
280 }
281 sz |= (b & 0x7f) << index++ * 7;
282 } while (0 != (b & 0x80));
283 return sz;
284 }
285 }