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 io.netty5.buffer.api.Buffer;
19
20
21
22
23
24
25
26
27
28
29 final class FastLz {
30
31 private static final int MAX_DISTANCE = 8191;
32 private static final int MAX_FARDISTANCE = 65535 + MAX_DISTANCE - 1;
33
34 private static final int HASH_LOG = 13;
35 private static final int HASH_SIZE = 1 << HASH_LOG;
36 private static final int HASH_MASK = HASH_SIZE - 1;
37
38 private static final int MAX_COPY = 32;
39 private static final int MAX_LEN = 256 + 8;
40
41 private static final int MIN_RECOMENDED_LENGTH_FOR_LEVEL_2 = 1024 * 64;
42
43 static final int MAGIC_NUMBER = 'F' << 16 | 'L' << 8 | 'Z';
44
45 static final byte BLOCK_TYPE_NON_COMPRESSED = 0x00;
46 static final byte BLOCK_TYPE_COMPRESSED = 0x01;
47 static final byte BLOCK_WITHOUT_CHECKSUM = 0x00;
48 static final byte BLOCK_WITH_CHECKSUM = 0x10;
49
50 static final int OPTIONS_OFFSET = 3;
51 static final int CHECKSUM_OFFSET = 4;
52
53 static final int MAX_CHUNK_LENGTH = 0xFFFF;
54
55
56
57
58
59 static final int MIN_LENGTH_TO_COMPRESSION = 32;
60
61
62
63
64
65
66
67 static final int LEVEL_AUTO = 0;
68
69
70
71
72 static final int LEVEL_1 = 1;
73
74
75
76
77 static final int LEVEL_2 = 2;
78
79
80
81
82
83
84 static int calculateOutputBufferLength(int inputLength) {
85 final int outputLength = (int) (inputLength * 1.06);
86 return Math.max(outputLength, 66);
87 }
88
89
90
91
92
93
94
95 @SuppressWarnings("IdentityBinaryExpression")
96 static int compress(final Buffer input, final int inOffset, final int inLength,
97 final Buffer output, final int outOffset, final int proposedLevel) {
98 final int level;
99 if (proposedLevel == LEVEL_AUTO) {
100 level = inLength < MIN_RECOMENDED_LENGTH_FOR_LEVEL_2 ? LEVEL_1 : LEVEL_2;
101 } else {
102 level = proposedLevel;
103 }
104
105 int ip = 0;
106 int ipBound = ip + inLength - 2;
107 int ipLimit = ip + inLength - 12;
108
109 int op = 0;
110
111
112 int[] htab = new int[HASH_SIZE];
113
114 int hslot;
115
116
117 int hval;
118
119
120 int copy;
121
122
123 if (inLength < 4) {
124 if (inLength != 0) {
125
126 output.setByte(outOffset + op++, (byte) (inLength - 1));
127 ipBound++;
128 while (ip <= ipBound) {
129 output.setByte(outOffset + op++, input.getByte(inOffset + ip++));
130 }
131 return inLength + 1;
132 }
133
134 return 0;
135 }
136
137
138
139 for (hslot = 0; hslot < HASH_SIZE; hslot++) {
140
141 htab[hslot] = ip;
142 }
143
144
145 copy = 2;
146 output.setByte(outOffset + op++, (byte) (MAX_COPY - 1));
147 output.setByte(outOffset + op++, input.getByte(inOffset + ip++));
148 output.setByte(outOffset + op++, input.getByte(inOffset + ip++));
149
150
151 while (ip < ipLimit) {
152 int ref = 0;
153
154 long distance = 0;
155
156
157
158
159 int len = 3;
160
161
162 int anchor = ip;
163
164 boolean matchLabel = false;
165
166
167 if (level == LEVEL_2) {
168
169 if (input.getByte(inOffset + ip) == input.getByte(inOffset + ip - 1) &&
170 readU16(input, inOffset + ip - 1) == readU16(input, inOffset + ip + 1)) {
171 distance = 1;
172 ip += 3;
173 ref = anchor + (3 - 1);
174
175
176
177
178 matchLabel = true;
179 }
180 }
181 if (!matchLabel) {
182
183
184 hval = hashFunction(input, inOffset + ip);
185
186 hslot = hval;
187
188 ref = htab[hval];
189
190
191 distance = anchor - ref;
192
193
194
195 htab[hslot] = anchor;
196
197
198 if (distance == 0
199 || (level == LEVEL_1 ? distance >= MAX_DISTANCE : distance >= MAX_FARDISTANCE)
200 || input.getByte(inOffset + ref++) != input.getByte(inOffset + ip++)
201 || input.getByte(inOffset + ref++) != input.getByte(inOffset + ip++)
202 || input.getByte(inOffset + ref++) != input.getByte(inOffset + ip++)) {
203
204
205
206 output.setByte(outOffset + op++, input.getByte(inOffset + anchor++));
207 ip = anchor;
208 copy++;
209 if (copy == MAX_COPY) {
210 copy = 0;
211 output.setByte(outOffset + op++, (byte) (MAX_COPY - 1));
212 }
213 continue;
214 }
215
216 if (level == LEVEL_2) {
217
218 if (distance >= MAX_DISTANCE) {
219 if (input.getByte(inOffset + ip++) != input.getByte(inOffset + ref++)
220 || input.getByte(inOffset + ip++) != input.getByte(inOffset + ref++)) {
221
222
223
224 output.setByte(outOffset + op++, input.getByte(inOffset + anchor++));
225 ip = anchor;
226 copy++;
227 if (copy == MAX_COPY) {
228 copy = 0;
229 output.setByte(outOffset + op++, (byte) (MAX_COPY - 1));
230 }
231 continue;
232 }
233 len += 2;
234 }
235 }
236 }
237
238
239
240
241 ip = anchor + len;
242
243
244 distance--;
245
246 if (distance == 0) {
247
248
249 byte x = input.getByte(inOffset + ip - 1);
250 while (ip < ipBound) {
251 if (input.getByte(inOffset + ref++) != x) {
252 break;
253 } else {
254 ip++;
255 }
256 }
257 } else {
258
259 boolean missMatch = false;
260 for (int i = 0; i < 8; i++) {
261 if (input.getByte(inOffset + ref++) != input.getByte(inOffset + ip++)) {
262 missMatch = true;
263 break;
264 }
265 }
266 if (!missMatch) {
267 while (ip < ipBound) {
268 if (input.getByte(inOffset + ref++) != input.getByte(inOffset + ip++)) {
269 break;
270 }
271 }
272 }
273 }
274
275
276 if (copy != 0) {
277
278
279 output.setByte(outOffset + op - copy - 1, (byte) (copy - 1));
280 } else {
281
282 op--;
283 }
284
285
286 copy = 0;
287
288
289 ip -= 3;
290 len = ip - anchor;
291
292
293 if (level == LEVEL_2) {
294 if (distance < MAX_DISTANCE) {
295 if (len < 7) {
296 output.setByte(outOffset + op++, (byte) ((len << 5) + (distance >>> 8)));
297 output.setByte(outOffset + op++, (byte) (distance & 255));
298 } else {
299 output.setByte(outOffset + op++, (byte) ((7 << 5) + (distance >>> 8)));
300 for (len -= 7; len >= 255; len -= 255) {
301 output.setByte(outOffset + op++, (byte) 255);
302 }
303 output.setByte(outOffset + op++, (byte) len);
304 output.setByte(outOffset + op++, (byte) (distance & 255));
305 }
306 } else {
307
308 if (len < 7) {
309 distance -= MAX_DISTANCE;
310 output.setByte(outOffset + op++, (byte) ((len << 5) + 31));
311 output.setByte(outOffset + op++, (byte) 255);
312 output.setByte(outOffset + op++, (byte) (distance >>> 8));
313 output.setByte(outOffset + op++, (byte) (distance & 255));
314 } else {
315 distance -= MAX_DISTANCE;
316 output.setByte(outOffset + op++, (byte) ((7 << 5) + 31));
317 for (len -= 7; len >= 255; len -= 255) {
318 output.setByte(outOffset + op++, (byte) 255);
319 }
320 output.setByte(outOffset + op++, (byte) len);
321 output.setByte(outOffset + op++, (byte) 255);
322 output.setByte(outOffset + op++, (byte) (distance >>> 8));
323 output.setByte(outOffset + op++, (byte) (distance & 255));
324 }
325 }
326 } else {
327 if (len > MAX_LEN - 2) {
328 while (len > MAX_LEN - 2) {
329 output.setByte(outOffset + op++, (byte) ((7 << 5) + (distance >>> 8)));
330 output.setByte(outOffset + op++, (byte) (MAX_LEN - 2 - 7 - 2));
331 output.setByte(outOffset + op++, (byte) (distance & 255));
332 len -= MAX_LEN - 2;
333 }
334 }
335
336 if (len < 7) {
337 output.setByte(outOffset + op++, (byte) ((len << 5) + (distance >>> 8)));
338 output.setByte(outOffset + op++, (byte) (distance & 255));
339 } else {
340 output.setByte(outOffset + op++, (byte) ((7 << 5) + (distance >>> 8)));
341 output.setByte(outOffset + op++, (byte) (len - 7));
342 output.setByte(outOffset + op++, (byte) (distance & 255));
343 }
344 }
345
346
347
348 hval = hashFunction(input, inOffset + ip);
349 htab[hval] = ip++;
350
351
352 hval = hashFunction(input, inOffset + ip);
353 htab[hval] = ip++;
354
355
356 output.setByte(outOffset + op++, (byte) (MAX_COPY - 1));
357
358 continue;
359
360
361
362
363
364
365
366
367
368
369
370
371
372 }
373
374
375 ipBound++;
376 while (ip <= ipBound) {
377 output.setByte(outOffset + op++, input.getByte(inOffset + ip++));
378 copy++;
379 if (copy == MAX_COPY) {
380 copy = 0;
381 output.setByte(outOffset + op++, (byte) (MAX_COPY - 1));
382 }
383 }
384
385
386 if (copy != 0) {
387
388 output.setByte(outOffset + op - copy - 1, (byte) (copy - 1));
389 } else {
390 op--;
391 }
392
393 if (level == LEVEL_2) {
394
395 output.setByte(outOffset, (byte) (output.getByte(outOffset) | 1 << 5));
396 }
397
398 return op;
399 }
400
401
402
403
404
405
406
407
408
409 static int decompress(final Buffer input, final int inOffset, final int inLength,
410 final Buffer output, final int outOffset, final int outLength) {
411
412 final int level = (input.getByte(inOffset) >> 5) + 1;
413 if (level != LEVEL_1 && level != LEVEL_2) {
414 throw new DecompressionException(String.format(
415 "invalid level: %d (expected: %d or %d)", level, LEVEL_1, LEVEL_2
416 ));
417 }
418
419
420 int ip = 0;
421
422 int op = 0;
423
424 long ctrl = input.getByte(inOffset + ip++) & 31;
425
426 int loop = 1;
427 do {
428
429 int ref = op;
430
431 long len = ctrl >> 5;
432
433 long ofs = (ctrl & 31) << 8;
434
435 if (ctrl >= 32) {
436 len--;
437
438 ref -= ofs;
439
440 int code;
441 if (len == 6) {
442 if (level == LEVEL_1) {
443
444 len += input.getUnsignedByte(inOffset + ip++);
445 } else {
446 do {
447 code = input.getUnsignedByte(inOffset + ip++);
448 len += code;
449 } while (code == 255);
450 }
451 }
452 if (level == LEVEL_1) {
453
454 ref -= input.getUnsignedByte(inOffset + ip++);
455 } else {
456 code = input.getUnsignedByte(inOffset + ip++);
457 ref -= code;
458
459
460
461
462 if (code == 255 && ofs == 31 << 8) {
463 ofs = input.getUnsignedByte(inOffset + ip++) << 8;
464 ofs += input.getUnsignedByte(inOffset + ip++);
465
466 ref = (int) (op - ofs - MAX_DISTANCE);
467 }
468 }
469
470
471 if (op + len + 3 > outLength) {
472 return 0;
473 }
474
475
476
477
478 if (ref - 1 < 0) {
479 return 0;
480 }
481
482 if (ip < inLength) {
483 ctrl = input.getUnsignedByte(inOffset + ip++);
484 } else {
485 loop = 0;
486 }
487
488 if (ref == op) {
489
490
491 byte b = output.getByte(outOffset + ref - 1);
492 output.setByte(outOffset + op++, b);
493 output.setByte(outOffset + op++, b);
494 output.setByte(outOffset + op++, b);
495 while (len != 0) {
496 output.setByte(outOffset + op++, b);
497 --len;
498 }
499 } else {
500
501 ref--;
502
503
504 output.setByte(outOffset + op++, output.getByte(outOffset + ref++));
505 output.setByte(outOffset + op++, output.getByte(outOffset + ref++));
506 output.setByte(outOffset + op++, output.getByte(outOffset + ref++));
507
508 while (len != 0) {
509 output.setByte(outOffset + op++, output.getByte(outOffset + ref++));
510 --len;
511 }
512 }
513 } else {
514 ctrl++;
515
516 if (op + ctrl > outLength) {
517 return 0;
518 }
519 if (ip + ctrl > inLength) {
520 return 0;
521 }
522
523
524 output.setByte(outOffset + op++, input.getByte(inOffset + ip++));
525
526 for (--ctrl; ctrl != 0; ctrl--) {
527
528 output.setByte(outOffset + op++, input.getByte(inOffset + ip++));
529 }
530
531 loop = ip < inLength ? 1 : 0;
532 if (loop != 0) {
533
534 ctrl = input.getUnsignedByte(inOffset + ip++);
535 }
536 }
537
538
539 } while (loop != 0);
540
541
542 return op;
543 }
544
545 private static int hashFunction(Buffer p, int offset) {
546 int v = readU16(p, offset);
547 v ^= readU16(p, offset + 1) ^ v >> 16 - HASH_LOG;
548 v &= HASH_MASK;
549 return v;
550 }
551
552 private static int readU16(Buffer data, int offset) {
553 if (offset + 1 >= data.readableBytes()) {
554 return data.getUnsignedByte(offset);
555 }
556 return data.getUnsignedByte(offset + 1) << 8 | data.getUnsignedByte(offset);
557 }
558
559 private FastLz() { }
560 }