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 io.netty.buffer.ByteBuf;
19 import io.netty.util.concurrent.FastThreadLocal;
20 import io.netty.util.internal.MathUtil;
21 import io.netty.util.internal.SystemPropertyUtil;
22
23 import java.util.Arrays;
24
25
26
27
28
29
30
31 public final class Snappy {
32
33 private static final int MAX_HT_SIZE = 1 << 14;
34 private static final int MIN_COMPRESSIBLE_BYTES = 15;
35
36
37 private static final int PREAMBLE_NOT_FULL = -1;
38 private static final int NOT_ENOUGH_INPUT = -1;
39
40
41 private static final int LITERAL = 0;
42 private static final int COPY_1_BYTE_OFFSET = 1;
43 private static final int COPY_2_BYTE_OFFSET = 2;
44 private static final int COPY_4_BYTE_OFFSET = 3;
45
46
47 private static final FastThreadLocal<short[]> HASH_TABLE = new FastThreadLocal<short[]>();
48
49 private static final boolean DEFAULT_REUSE_HASHTABLE =
50 SystemPropertyUtil.getBoolean("io.netty.handler.codec.compression.snappy.reuseHashTable", false);
51
52 public Snappy() {
53 this(DEFAULT_REUSE_HASHTABLE);
54 }
55
56 Snappy(boolean reuseHashtable) {
57 this.reuseHashtable = reuseHashtable;
58 }
59
60 public static Snappy withHashTableReuse() {
61 return new Snappy(true);
62 }
63
64 private final boolean reuseHashtable;
65 private State state = State.READING_PREAMBLE;
66 private byte tag;
67 private int written;
68
69 private enum State {
70 READING_PREAMBLE,
71 READING_TAG,
72 READING_LITERAL,
73 READING_COPY
74 }
75
76 public void reset() {
77 state = State.READING_PREAMBLE;
78 tag = 0;
79 written = 0;
80 }
81
82 public void encode(final ByteBuf in, final ByteBuf out, final int length) {
83
84 for (int i = 0;; i ++) {
85 int b = length >>> i * 7;
86 if ((b & 0xFFFFFF80) != 0) {
87 out.writeByte(b & 0x7f | 0x80);
88 } else {
89 out.writeByte(b);
90 break;
91 }
92 }
93
94 int inIndex = in.readerIndex();
95 final int baseIndex = inIndex;
96
97 int hashTableSize = MathUtil.findNextPositivePowerOfTwo(length);
98 hashTableSize = Math.min(hashTableSize, MAX_HT_SIZE);
99 final short[] table = getHashTable(hashTableSize);
100 final int shift = Integer.numberOfLeadingZeros(hashTableSize) + 1;
101
102 int nextEmit = inIndex;
103
104 if (length - inIndex >= MIN_COMPRESSIBLE_BYTES) {
105 int nextHash = hash(in, ++inIndex, shift);
106 outer: while (true) {
107 int skip = 32;
108
109 int candidate;
110 int nextIndex = inIndex;
111 do {
112 inIndex = nextIndex;
113 int hash = nextHash;
114 int bytesBetweenHashLookups = skip++ >> 5;
115 nextIndex = inIndex + bytesBetweenHashLookups;
116
117
118 if (nextIndex > length - 4) {
119 break outer;
120 }
121
122 nextHash = hash(in, nextIndex, shift);
123
124
125
126 candidate = baseIndex + ((int) table[hash]) & 0xffff;
127
128 table[hash] = (short) (inIndex - baseIndex);
129 }
130 while (in.getInt(inIndex) != in.getInt(candidate));
131
132 encodeLiteral(in, out, inIndex - nextEmit);
133
134 int insertTail;
135 do {
136 int base = inIndex;
137 int matched = 4 + findMatchingLength(in, candidate + 4, inIndex + 4, length);
138 inIndex += matched;
139 int offset = base - candidate;
140 encodeCopy(out, offset, matched);
141 in.readerIndex(in.readerIndex() + matched);
142 insertTail = inIndex - 1;
143 nextEmit = inIndex;
144 if (inIndex >= length - 4) {
145 break outer;
146 }
147
148 int prevHash = hash(in, insertTail, shift);
149 table[prevHash] = (short) (inIndex - baseIndex - 1);
150 int currentHash = hash(in, insertTail + 1, shift);
151 candidate = baseIndex + table[currentHash];
152 table[currentHash] = (short) (inIndex - baseIndex);
153 }
154 while (in.getInt(insertTail + 1) == in.getInt(candidate));
155
156 nextHash = hash(in, insertTail + 2, shift);
157 ++inIndex;
158 }
159 }
160
161
162 if (nextEmit < length) {
163 encodeLiteral(in, out, length - nextEmit);
164 }
165 }
166
167
168
169
170
171
172
173
174
175
176
177 private static int hash(ByteBuf in, int index, int shift) {
178 return in.getInt(index) * 0x1e35a7bd >>> shift;
179 }
180
181
182
183
184
185
186
187 private short[] getHashTable(int hashTableSize) {
188 if (reuseHashtable) {
189 return getHashTableFastThreadLocalArrayFill(hashTableSize);
190 }
191 return new short[hashTableSize];
192 }
193
194
195
196
197
198
199
200
201 public static short[] getHashTableFastThreadLocalArrayFill(int hashTableSize) {
202 short[] hashTable = HASH_TABLE.get();
203 if (hashTable == null || hashTable.length < hashTableSize) {
204 hashTable = new short[hashTableSize];
205 HASH_TABLE.set(hashTable);
206 return hashTable;
207 }
208
209 Arrays.fill(hashTable, 0, hashTableSize, (short) 0);
210 return hashTable;
211 }
212
213
214
215
216
217
218
219
220
221
222
223
224 private static int findMatchingLength(ByteBuf in, int minIndex, int inIndex, int maxIndex) {
225 int matched = 0;
226
227 while (inIndex <= maxIndex - 4 &&
228 in.getInt(inIndex) == in.getInt(minIndex + matched)) {
229 inIndex += 4;
230 matched += 4;
231 }
232
233 while (inIndex < maxIndex && in.getByte(minIndex + matched) == in.getByte(inIndex)) {
234 ++inIndex;
235 ++matched;
236 }
237
238 return matched;
239 }
240
241
242
243
244
245
246
247
248
249 private static int bitsToEncode(int value) {
250 int highestOneBit = Integer.highestOneBit(value);
251 int bitLength = 0;
252 while ((highestOneBit >>= 1) != 0) {
253 bitLength++;
254 }
255
256 return bitLength;
257 }
258
259
260
261
262
263
264
265
266
267
268 static void encodeLiteral(ByteBuf in, ByteBuf out, int length) {
269 if (length < 61) {
270 out.writeByte(length - 1 << 2);
271 } else {
272 int bitLength = bitsToEncode(length - 1);
273 int bytesToEncode = 1 + bitLength / 8;
274 out.writeByte(59 + bytesToEncode << 2);
275 for (int i = 0; i < bytesToEncode; i++) {
276 out.writeByte(length - 1 >> i * 8 & 0x0ff);
277 }
278 }
279
280 out.writeBytes(in, length);
281 }
282
283 private static void encodeCopyWithOffset(ByteBuf out, int offset, int length) {
284 if (length < 12 && offset < 2048) {
285 out.writeByte(COPY_1_BYTE_OFFSET | length - 4 << 2 | offset >> 8 << 5);
286 out.writeByte(offset & 0x0ff);
287 } else {
288 out.writeByte(COPY_2_BYTE_OFFSET | length - 1 << 2);
289 out.writeByte(offset & 0x0ff);
290 out.writeByte(offset >> 8 & 0x0ff);
291 }
292 }
293
294
295
296
297
298
299
300
301 private static void encodeCopy(ByteBuf out, int offset, int length) {
302 while (length >= 68) {
303 encodeCopyWithOffset(out, offset, 64);
304 length -= 64;
305 }
306
307 if (length > 64) {
308 encodeCopyWithOffset(out, offset, 60);
309 length -= 60;
310 }
311
312 encodeCopyWithOffset(out, offset, length);
313 }
314
315 public void decode(ByteBuf in, ByteBuf out) {
316 while (in.isReadable()) {
317 switch (state) {
318 case READING_PREAMBLE:
319 int uncompressedLength = readPreamble(in);
320 if (uncompressedLength == PREAMBLE_NOT_FULL) {
321
322 return;
323 }
324 if (uncompressedLength == 0) {
325
326 return;
327 }
328 out.ensureWritable(uncompressedLength);
329 state = State.READING_TAG;
330
331 case READING_TAG:
332 if (!in.isReadable()) {
333 return;
334 }
335 tag = in.readByte();
336 switch (tag & 0x03) {
337 case LITERAL:
338 state = State.READING_LITERAL;
339 break;
340 case COPY_1_BYTE_OFFSET:
341 case COPY_2_BYTE_OFFSET:
342 case COPY_4_BYTE_OFFSET:
343 state = State.READING_COPY;
344 break;
345 }
346 break;
347 case READING_LITERAL:
348 int literalWritten = decodeLiteral(tag, in, out);
349 if (literalWritten != NOT_ENOUGH_INPUT) {
350 state = State.READING_TAG;
351 written += literalWritten;
352 } else {
353
354 return;
355 }
356 break;
357 case READING_COPY:
358 int decodeWritten;
359 switch (tag & 0x03) {
360 case COPY_1_BYTE_OFFSET:
361 decodeWritten = decodeCopyWith1ByteOffset(tag, in, out, written);
362 if (decodeWritten != NOT_ENOUGH_INPUT) {
363 state = State.READING_TAG;
364 written += decodeWritten;
365 } else {
366
367 return;
368 }
369 break;
370 case COPY_2_BYTE_OFFSET:
371 decodeWritten = decodeCopyWith2ByteOffset(tag, in, out, written);
372 if (decodeWritten != NOT_ENOUGH_INPUT) {
373 state = State.READING_TAG;
374 written += decodeWritten;
375 } else {
376
377 return;
378 }
379 break;
380 case COPY_4_BYTE_OFFSET:
381 decodeWritten = decodeCopyWith4ByteOffset(tag, in, out, written);
382 if (decodeWritten != NOT_ENOUGH_INPUT) {
383 state = State.READING_TAG;
384 written += decodeWritten;
385 } else {
386
387 return;
388 }
389 break;
390 }
391 }
392 }
393 }
394
395
396
397
398
399
400
401
402
403
404 private static int readPreamble(ByteBuf in) {
405 int length = 0;
406 int byteIndex = 0;
407 while (in.isReadable()) {
408 int current = in.readUnsignedByte();
409 length |= (current & 0x7f) << byteIndex++ * 7;
410 if ((current & 0x80) == 0) {
411 return length;
412 }
413
414 if (byteIndex >= 4) {
415 throw new DecompressionException("Preamble is greater than 4 bytes");
416 }
417 }
418
419 return 0;
420 }
421
422
423
424
425
426
427
428
429
430
431 int getPreamble(ByteBuf in) {
432 if (state == State.READING_PREAMBLE) {
433 int readerIndex = in.readerIndex();
434 try {
435 return readPreamble(in);
436 } finally {
437 in.readerIndex(readerIndex);
438 }
439 }
440 return 0;
441 }
442
443
444
445
446
447
448
449
450
451
452
453
454 static int decodeLiteral(byte tag, ByteBuf in, ByteBuf out) {
455 in.markReaderIndex();
456 int length;
457 switch(tag >> 2 & 0x3F) {
458 case 60:
459 if (!in.isReadable()) {
460 return NOT_ENOUGH_INPUT;
461 }
462 length = in.readUnsignedByte();
463 break;
464 case 61:
465 if (in.readableBytes() < 2) {
466 return NOT_ENOUGH_INPUT;
467 }
468 length = in.readUnsignedShortLE();
469 break;
470 case 62:
471 if (in.readableBytes() < 3) {
472 return NOT_ENOUGH_INPUT;
473 }
474 length = in.readUnsignedMediumLE();
475 break;
476 case 63:
477 if (in.readableBytes() < 4) {
478 return NOT_ENOUGH_INPUT;
479 }
480 length = in.readIntLE();
481 break;
482 default:
483 length = tag >> 2 & 0x3F;
484 }
485 length += 1;
486
487 if (in.readableBytes() < length) {
488 in.resetReaderIndex();
489 return NOT_ENOUGH_INPUT;
490 }
491
492 out.writeBytes(in, length);
493 return length;
494 }
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509 private static int decodeCopyWith1ByteOffset(byte tag, ByteBuf in, ByteBuf out, int writtenSoFar) {
510 if (!in.isReadable()) {
511 return NOT_ENOUGH_INPUT;
512 }
513
514 int initialIndex = out.writerIndex();
515 int length = 4 + ((tag & 0x01c) >> 2);
516 int offset = (tag & 0x0e0) << 8 >> 5 | in.readUnsignedByte();
517
518 validateOffset(offset, writtenSoFar);
519
520 out.markReaderIndex();
521 if (offset < length) {
522 int copies = length / offset;
523 for (; copies > 0; copies--) {
524 out.readerIndex(initialIndex - offset);
525 out.readBytes(out, offset);
526 }
527 if (length % offset != 0) {
528 out.readerIndex(initialIndex - offset);
529 out.readBytes(out, length % offset);
530 }
531 } else {
532 out.readerIndex(initialIndex - offset);
533 out.readBytes(out, length);
534 }
535 out.resetReaderIndex();
536
537 return length;
538 }
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553 private static int decodeCopyWith2ByteOffset(byte tag, ByteBuf in, ByteBuf out, int writtenSoFar) {
554 if (in.readableBytes() < 2) {
555 return NOT_ENOUGH_INPUT;
556 }
557
558 int initialIndex = out.writerIndex();
559 int length = 1 + (tag >> 2 & 0x03f);
560 int offset = in.readUnsignedShortLE();
561
562 validateOffset(offset, writtenSoFar);
563
564 out.markReaderIndex();
565 if (offset < length) {
566 int copies = length / offset;
567 for (; copies > 0; copies--) {
568 out.readerIndex(initialIndex - offset);
569 out.readBytes(out, offset);
570 }
571 if (length % offset != 0) {
572 out.readerIndex(initialIndex - offset);
573 out.readBytes(out, length % offset);
574 }
575 } else {
576 out.readerIndex(initialIndex - offset);
577 out.readBytes(out, length);
578 }
579 out.resetReaderIndex();
580
581 return length;
582 }
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597 private static int decodeCopyWith4ByteOffset(byte tag, ByteBuf in, ByteBuf out, int writtenSoFar) {
598 if (in.readableBytes() < 4) {
599 return NOT_ENOUGH_INPUT;
600 }
601
602 int initialIndex = out.writerIndex();
603 int length = 1 + (tag >> 2 & 0x03F);
604 int offset = in.readIntLE();
605
606 validateOffset(offset, writtenSoFar);
607
608 out.markReaderIndex();
609 if (offset < length) {
610 int copies = length / offset;
611 for (; copies > 0; copies--) {
612 out.readerIndex(initialIndex - offset);
613 out.readBytes(out, offset);
614 }
615 if (length % offset != 0) {
616 out.readerIndex(initialIndex - offset);
617 out.readBytes(out, length % offset);
618 }
619 } else {
620 out.readerIndex(initialIndex - offset);
621 out.readBytes(out, length);
622 }
623 out.resetReaderIndex();
624
625 return length;
626 }
627
628
629
630
631
632
633
634
635
636
637 private static void validateOffset(int offset, int chunkSizeSoFar) {
638 if (offset == 0) {
639 throw new DecompressionException("Offset is less than minimum permissible value");
640 }
641
642 if (offset < 0) {
643
644 throw new DecompressionException("Offset is greater than maximum value supported by this implementation");
645 }
646
647 if (offset > chunkSizeSoFar) {
648 throw new DecompressionException("Offset exceeds size of chunk");
649 }
650 }
651
652
653
654
655
656
657
658 static int calculateChecksum(ByteBuf data) {
659 return calculateChecksum(data, data.readerIndex(), data.readableBytes());
660 }
661
662
663
664
665
666
667
668 static int calculateChecksum(ByteBuf data, int offset, int length) {
669 Crc32c crc32 = new Crc32c();
670 try {
671 crc32.update(data, offset, length);
672 return maskChecksum(crc32.getValue());
673 } finally {
674 crc32.reset();
675 }
676 }
677
678
679
680
681
682
683
684
685
686
687 static void validateChecksum(int expectedChecksum, ByteBuf data) {
688 validateChecksum(expectedChecksum, data, data.readerIndex(), data.readableBytes());
689 }
690
691
692
693
694
695
696
697
698
699
700 static void validateChecksum(int expectedChecksum, ByteBuf data, int offset, int length) {
701 final int actualChecksum = calculateChecksum(data, offset, length);
702 if (actualChecksum != expectedChecksum) {
703 throw new DecompressionException(
704 "mismatching checksum: " + Integer.toHexString(actualChecksum) +
705 " (expected: " + Integer.toHexString(expectedChecksum) + ')');
706 }
707 }
708
709
710
711
712
713
714
715
716
717
718
719
720 static int maskChecksum(long checksum) {
721 return (int) ((checksum >> 15 | checksum << 17) + 0xa282ead8);
722 }
723 }