1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty5.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.LZFException;
22 import com.ning.compress.lzf.util.ChunkDecoderFactory;
23 import io.netty5.buffer.api.Buffer;
24 import io.netty5.buffer.api.BufferAllocator;
25
26 import java.util.function.Supplier;
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 final class LzfDecompressor implements Decompressor {
41
42
43
44
45 private enum State {
46 INIT_BLOCK,
47 INIT_ORIGINAL_LENGTH,
48 DECOMPRESS_DATA,
49 CORRUPTED,
50 FINISHED,
51 CLOSED
52 }
53
54 private State currentState = State.INIT_BLOCK;
55
56
57
58
59 private static final short MAGIC_NUMBER = BYTE_Z << 8 | BYTE_V;
60
61
62
63
64 private ChunkDecoder decoder;
65
66
67
68
69 private BufferRecycler recycler;
70
71
72
73
74 private int chunkLength;
75
76
77
78
79
80 private int originalLength;
81
82
83
84
85 private boolean isCompressed;
86
87
88
89
90
91
92
93
94
95
96 private LzfDecompressor(boolean safeInstance) {
97 decoder = safeInstance ?
98 ChunkDecoderFactory.safeInstance()
99 : ChunkDecoderFactory.optimalInstance();
100
101 recycler = BufferRecycler.instance();
102 }
103
104
105
106
107
108
109
110
111
112 public static Supplier<LzfDecompressor> newFactory() {
113 return newFactory(false);
114 }
115
116
117
118
119
120
121
122
123
124
125
126 public static Supplier<LzfDecompressor> newFactory(boolean safeInstance) {
127 return () -> new LzfDecompressor(safeInstance);
128 }
129
130 @Override
131 public Buffer decompress(Buffer in, BufferAllocator allocator) throws DecompressionException {
132 try {
133 switch (currentState) {
134 case FINISHED:
135 case CORRUPTED:
136 return allocator.allocate(0);
137 case CLOSED:
138 throw new DecompressionException("Decompressor closed");
139 case INIT_BLOCK:
140 if (in.readableBytes() < HEADER_LEN_NOT_COMPRESSED) {
141 return null;
142 }
143 final int magic = in.readUnsignedShort();
144 if (magic != MAGIC_NUMBER) {
145 streamCorrupted("unexpected block identifier");
146 }
147
148 final int type = in.readByte();
149 switch (type) {
150 case BLOCK_TYPE_NON_COMPRESSED:
151 isCompressed = false;
152 currentState = State.DECOMPRESS_DATA;
153 break;
154 case BLOCK_TYPE_COMPRESSED:
155 isCompressed = true;
156 currentState = State.INIT_ORIGINAL_LENGTH;
157 break;
158 default:
159 streamCorrupted(String.format(
160 "unknown type of chunk: %d (expected: %d or %d)",
161 type, BLOCK_TYPE_NON_COMPRESSED, BLOCK_TYPE_COMPRESSED));
162 }
163 chunkLength = in.readUnsignedShort();
164
165
166
167
168 if (chunkLength > LZFChunk.MAX_CHUNK_LEN) {
169 streamCorrupted(String.format(
170 "chunk length exceeds maximum: %d (expected: =< %d)",
171 chunkLength, LZFChunk.MAX_CHUNK_LEN));
172 }
173
174 if (type != BLOCK_TYPE_COMPRESSED) {
175 return null;
176 }
177
178 case INIT_ORIGINAL_LENGTH:
179 if (in.readableBytes() < 2) {
180 return null;
181 }
182 originalLength = in.readUnsignedShort();
183
184
185
186
187 if (originalLength > LZFChunk.MAX_CHUNK_LEN) {
188 streamCorrupted(String.format(
189 "original length exceeds maximum: %d (expected: =< %d)",
190 chunkLength, LZFChunk.MAX_CHUNK_LEN));
191 }
192
193 currentState = State.DECOMPRESS_DATA;
194
195 case DECOMPRESS_DATA:
196 final int chunkLength = this.chunkLength;
197 if (in.readableBytes() < chunkLength) {
198 return null;
199 }
200 final int originalLength = this.originalLength;
201
202 if (isCompressed) {
203 if (in.countReadableComponents() == 1) {
204 try (var readableIteration = in.forEachReadable()) {
205 var readableComponent = readableIteration.first();
206 if (readableComponent.hasReadableArray()) {
207 byte[] inputArray = readableComponent.readableArray();
208 int inPos = readableComponent.readableArrayOffset();
209 try {
210 Buffer out = decompress(allocator, inputArray, inPos, originalLength);
211 in.skipReadableBytes(chunkLength);
212 currentState = State.INIT_BLOCK;
213 return out;
214 } finally {
215 if (!readableComponent.hasReadableArray()) {
216 recycler.releaseInputBuffer(inputArray);
217 }
218 }
219 }
220 }
221 }
222 final int idx = in.readerOffset();
223 byte[] inputArray = recycler.allocInputBuffer(chunkLength);
224 in.copyInto(idx, inputArray, 0, chunkLength);
225 try {
226 Buffer out = decompress(allocator, inputArray, 0, originalLength);
227 in.skipReadableBytes(chunkLength);
228 currentState = State.INIT_BLOCK;
229 return out;
230 } finally {
231 recycler.releaseInputBuffer(inputArray);
232 }
233 } else if (chunkLength > 0) {
234 currentState = State.INIT_BLOCK;
235 return in.readSplit(chunkLength);
236 } else {
237 currentState = State.INIT_BLOCK;
238 }
239
240 return null;
241 default:
242 throw new IllegalStateException();
243 }
244 } catch (Exception e) {
245 currentState = State.CORRUPTED;
246 decoder = null;
247 recycler = null;
248 if (e instanceof DecompressionException) {
249 throw (DecompressionException) e;
250 }
251 throw new DecompressionException(e);
252 }
253 }
254
255 private Buffer decompress(BufferAllocator allocator, byte[] inputArray, int offset, int len)
256 throws LZFException {
257 byte[] outputArray = recycler.allocOutputBuffer(len);
258 try {
259 decoder.decodeChunk(inputArray, offset,
260 outputArray, 0, len);
261 return allocator.allocate(len)
262 .writeBytes(outputArray, 0, len);
263 } finally {
264 recycler.releaseOutputBuffer(outputArray);
265 }
266 }
267
268 private void streamCorrupted(String message) {
269 currentState = State.CORRUPTED;
270 throw new DecompressionException(message);
271 }
272
273 @Override
274 public boolean isFinished() {
275 switch (currentState) {
276 case FINISHED:
277 case CLOSED:
278 case CORRUPTED:
279 return true;
280 default:
281 return false;
282 }
283 }
284
285 @Override
286 public boolean isClosed() {
287 return currentState == State.CLOSED;
288 }
289
290 @Override
291 public void close() {
292 currentState = State.CLOSED;
293 }
294 }