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.BufferRecycler;
19 import com.ning.compress.lzf.ChunkDecoder;
20 import com.ning.compress.lzf.LZFChunk;
21 import com.ning.compress.lzf.util.ChunkDecoderFactory;
22 import io.netty.buffer.ByteBuf;
23 import io.netty.channel.ChannelHandlerContext;
24 import io.netty.handler.codec.ByteToMessageDecoder;
25
26 import java.util.List;
27
28 import static com.ning.compress.lzf.LZFChunk.BLOCK_TYPE_COMPRESSED;
29 import static com.ning.compress.lzf.LZFChunk.BLOCK_TYPE_NON_COMPRESSED;
30 import static com.ning.compress.lzf.LZFChunk.BYTE_V;
31 import static com.ning.compress.lzf.LZFChunk.BYTE_Z;
32 import static com.ning.compress.lzf.LZFChunk.HEADER_LEN_NOT_COMPRESSED;
33
34
35
36
37
38
39
40 public class LzfDecoder extends ByteToMessageDecoder {
41
42
43
44 private enum State {
45 INIT_BLOCK,
46 INIT_ORIGINAL_LENGTH,
47 DECOMPRESS_DATA,
48 CORRUPTED
49 }
50
51 private State currentState = State.INIT_BLOCK;
52
53
54
55
56 private static final short MAGIC_NUMBER = BYTE_Z << 8 | BYTE_V;
57
58
59
60
61 private ChunkDecoder decoder;
62
63
64
65
66 private BufferRecycler recycler;
67
68
69
70
71 private int chunkLength;
72
73
74
75
76
77 private int originalLength;
78
79
80
81
82 private boolean isCompressed;
83
84
85
86
87
88
89
90 public LzfDecoder() {
91 this(false);
92 }
93
94
95
96
97
98
99
100
101
102
103 public LzfDecoder(boolean safeInstance) {
104 decoder = safeInstance ?
105 ChunkDecoderFactory.safeInstance()
106 : ChunkDecoderFactory.optimalInstance();
107
108 recycler = BufferRecycler.instance();
109 }
110
111 @Override
112 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
113 try {
114 switch (currentState) {
115 case INIT_BLOCK:
116 if (in.readableBytes() < HEADER_LEN_NOT_COMPRESSED) {
117 break;
118 }
119 final int magic = in.readUnsignedShort();
120 if (magic != MAGIC_NUMBER) {
121 throw new DecompressionException("unexpected block identifier");
122 }
123
124 final int type = in.readByte();
125 switch (type) {
126 case BLOCK_TYPE_NON_COMPRESSED:
127 isCompressed = false;
128 currentState = State.DECOMPRESS_DATA;
129 break;
130 case BLOCK_TYPE_COMPRESSED:
131 isCompressed = true;
132 currentState = State.INIT_ORIGINAL_LENGTH;
133 break;
134 default:
135 throw new DecompressionException(String.format(
136 "unknown type of chunk: %d (expected: %d or %d)",
137 type, BLOCK_TYPE_NON_COMPRESSED, BLOCK_TYPE_COMPRESSED));
138 }
139 chunkLength = in.readUnsignedShort();
140
141
142
143
144 if (chunkLength > LZFChunk.MAX_CHUNK_LEN) {
145 throw new DecompressionException(String.format(
146 "chunk length exceeds maximum: %d (expected: =< %d)",
147 chunkLength, LZFChunk.MAX_CHUNK_LEN));
148 }
149
150 if (type != BLOCK_TYPE_COMPRESSED) {
151 break;
152 }
153
154 case INIT_ORIGINAL_LENGTH:
155 if (in.readableBytes() < 2) {
156 break;
157 }
158 originalLength = in.readUnsignedShort();
159
160
161
162
163 if (originalLength > LZFChunk.MAX_CHUNK_LEN) {
164 throw new DecompressionException(String.format(
165 "original length exceeds maximum: %d (expected: =< %d)",
166 chunkLength, LZFChunk.MAX_CHUNK_LEN));
167 }
168
169 currentState = State.DECOMPRESS_DATA;
170
171 case DECOMPRESS_DATA:
172 final int chunkLength = this.chunkLength;
173 if (in.readableBytes() < chunkLength) {
174 break;
175 }
176 final int originalLength = this.originalLength;
177
178 if (isCompressed) {
179 final int idx = in.readerIndex();
180
181 final byte[] inputArray;
182 final int inPos;
183 if (in.hasArray()) {
184 inputArray = in.array();
185 inPos = in.arrayOffset() + idx;
186 } else {
187 inputArray = recycler.allocInputBuffer(chunkLength);
188 in.getBytes(idx, inputArray, 0, chunkLength);
189 inPos = 0;
190 }
191
192 ByteBuf uncompressed = ctx.alloc().heapBuffer(originalLength, originalLength);
193 final byte[] outputArray;
194 final int outPos;
195 if (uncompressed.hasArray()) {
196 outputArray = uncompressed.array();
197 outPos = uncompressed.arrayOffset() + uncompressed.writerIndex();
198 } else {
199 outputArray = new byte[originalLength];
200 outPos = 0;
201 }
202
203 boolean success = false;
204 try {
205 decoder.decodeChunk(inputArray, inPos, outputArray, outPos, outPos + originalLength);
206 if (uncompressed.hasArray()) {
207 uncompressed.writerIndex(uncompressed.writerIndex() + originalLength);
208 } else {
209 uncompressed.writeBytes(outputArray);
210 }
211 out.add(uncompressed);
212 in.skipBytes(chunkLength);
213 success = true;
214 } finally {
215 if (!success) {
216 uncompressed.release();
217 }
218 }
219
220 if (!in.hasArray()) {
221 recycler.releaseInputBuffer(inputArray);
222 }
223 } else if (chunkLength > 0) {
224 out.add(in.readRetainedSlice(chunkLength));
225 }
226
227 currentState = State.INIT_BLOCK;
228 break;
229 case CORRUPTED:
230 in.skipBytes(in.readableBytes());
231 break;
232 default:
233 throw new IllegalStateException();
234 }
235 } catch (Exception e) {
236 currentState = State.CORRUPTED;
237 decoder = null;
238 recycler = null;
239 throw e;
240 }
241 }
242 }