1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.compression;
17
18 import com.ning.compress.lzf.ChunkDecoder;
19 import com.ning.compress.lzf.LZFChunk;
20 import com.ning.compress.lzf.LZFException;
21 import com.ning.compress.lzf.util.ChunkDecoderFactory;
22 import io.netty.buffer.ByteBuf;
23 import io.netty.buffer.ByteBufAllocator;
24 import io.netty.util.internal.UnstableApi;
25
26 import static com.ning.compress.lzf.LZFChunk.BLOCK_TYPE_COMPRESSED;
27 import static com.ning.compress.lzf.LZFChunk.BLOCK_TYPE_NON_COMPRESSED;
28 import static com.ning.compress.lzf.LZFChunk.BYTE_V;
29 import static com.ning.compress.lzf.LZFChunk.BYTE_Z;
30 import static com.ning.compress.lzf.LZFChunk.HEADER_LEN_NOT_COMPRESSED;
31
32
33
34
35
36
37
38 @UnstableApi
39 public final class LzfDecompressor extends InputBufferingDecompressor {
40
41
42
43 private enum State {
44 INIT_BLOCK,
45 INIT_ORIGINAL_LENGTH,
46 DECOMPRESS_DATA,
47 END,
48 }
49
50 private State currentState = State.INIT_BLOCK;
51
52
53
54
55 private static final short MAGIC_NUMBER = BYTE_Z << 8 | BYTE_V;
56
57 private final ChunkDecoder decoder;
58
59
60
61
62 private int chunkLength;
63
64
65
66
67
68 private int originalLength;
69
70
71
72
73 private boolean isCompressed;
74
75 LzfDecompressor(Builder builder, ByteBufAllocator allocator) {
76 super(allocator);
77 decoder = builder.safeInstance ?
78 ChunkDecoderFactory.safeInstance()
79 : ChunkDecoderFactory.optimalInstance();
80 }
81
82 @Override
83 void processInput(ByteBuf buf) throws DecompressionException {
84 switch (currentState) {
85 case INIT_BLOCK:
86 if (buf.readableBytes() < HEADER_LEN_NOT_COMPRESSED) {
87 break;
88 }
89 final int magic = buf.readUnsignedShort();
90 if (magic != MAGIC_NUMBER) {
91 throw new DecompressionException("unexpected block identifier");
92 }
93
94 final int type = buf.readByte();
95 switch (type) {
96 case BLOCK_TYPE_NON_COMPRESSED:
97 isCompressed = false;
98 currentState = State.DECOMPRESS_DATA;
99 break;
100 case BLOCK_TYPE_COMPRESSED:
101 isCompressed = true;
102 currentState = State.INIT_ORIGINAL_LENGTH;
103 break;
104 default:
105 throw new DecompressionException(String.format(
106 "unknown type of chunk: %d (expected: %d or %d)",
107 type, BLOCK_TYPE_NON_COMPRESSED, BLOCK_TYPE_COMPRESSED));
108 }
109 chunkLength = buf.readUnsignedShort();
110
111
112
113
114 if (chunkLength > LZFChunk.MAX_CHUNK_LEN) {
115 throw new DecompressionException(String.format(
116 "chunk length exceeds maximum: %d (expected: =< %d)",
117 chunkLength, LZFChunk.MAX_CHUNK_LEN));
118 }
119
120 if (type != BLOCK_TYPE_COMPRESSED) {
121 break;
122 }
123
124 case INIT_ORIGINAL_LENGTH:
125 if (buf.readableBytes() < 2) {
126 break;
127 }
128 originalLength = buf.readUnsignedShort();
129
130
131
132
133 if (originalLength > LZFChunk.MAX_CHUNK_LEN) {
134 throw new DecompressionException(String.format(
135 "original length exceeds maximum: %d (expected: =< %d)",
136 originalLength, LZFChunk.MAX_CHUNK_LEN));
137 }
138
139 currentState = State.DECOMPRESS_DATA;
140
141 case DECOMPRESS_DATA:
142
143 break;
144 default:
145 throw new IllegalStateException();
146 }
147 }
148
149 @Override
150 public Status status() throws DecompressionException {
151 switch (currentState) {
152 case INIT_BLOCK:
153 case INIT_ORIGINAL_LENGTH:
154 return Status.NEED_INPUT;
155 case DECOMPRESS_DATA:
156 return available() < chunkLength ? Status.NEED_INPUT : Status.NEED_OUTPUT;
157 case END:
158 return Status.COMPLETE;
159 default:
160 throw new AssertionError("Unknown state: " + currentState);
161 }
162 }
163
164 @Override
165 public void endOfInput() throws DecompressionException {
166 if (currentState != State.INIT_BLOCK || available() != 0) {
167 throw new DecompressionException("Incomplete block");
168 }
169 currentState = State.END;
170 }
171
172 @Override
173 ByteBuf processOutput(ByteBuf in) throws DecompressionException {
174 final int chunkLength = this.chunkLength;
175 if (in.readableBytes() < chunkLength) {
176 throw new IllegalStateException("Not in state NEED_OUTPUT");
177 }
178 final int originalLength = this.originalLength;
179
180 if (isCompressed) {
181 ByteBuf arrayView;
182 if (!in.hasArray()) {
183 arrayView = allocator.heapBuffer(chunkLength, chunkLength);
184 arrayView.writeBytes(in, in.readerIndex(), chunkLength);
185 } else {
186 arrayView = in;
187 }
188 final byte[] inputArray = arrayView.array();
189 final int inPos = arrayView.arrayOffset() + arrayView.readerIndex();
190
191 ByteBuf uncompressed = null;
192 try {
193 uncompressed = allocator.heapBuffer(originalLength, originalLength);
194 final byte[] outputArray = uncompressed.array();
195 final int outPos = uncompressed.arrayOffset() + uncompressed.writerIndex();
196 decoder.decodeChunk(
197 inputArray, inPos, inPos + chunkLength,
198 outputArray, outPos, outPos + originalLength);
199 uncompressed.writerIndex(uncompressed.writerIndex() + originalLength);
200 in.skipBytes(chunkLength);
201 currentState = State.INIT_BLOCK;
202 ByteBuf output = uncompressed;
203 uncompressed = null;
204 return output;
205 } catch (LZFException e) {
206 throw new DecompressionException(e);
207 } finally {
208 if (uncompressed != null) {
209 uncompressed.release();
210 }
211 if (arrayView != in) {
212 arrayView.release();
213 }
214 }
215 } else {
216 currentState = State.INIT_BLOCK;
217 return in.readRetainedSlice(chunkLength);
218 }
219 }
220
221 @UnstableApi
222 public static Builder builder() {
223 return new Builder();
224 }
225
226 @UnstableApi
227 public static final class Builder extends AbstractDecompressorBuilder {
228 private boolean safeInstance;
229
230 Builder() {
231 }
232
233
234
235
236
237
238
239
240
241
242 @UnstableApi
243 public Builder safeInstance(boolean safeInstance) {
244 this.safeInstance = safeInstance;
245 return this;
246 }
247
248 @Override
249 @UnstableApi
250 public Decompressor build(ByteBufAllocator allocator) throws DecompressionException {
251 return new DefensiveDecompressor(new LzfDecompressor(this, allocator));
252 }
253 }
254 }