1 /*
2 * Copyright 2012 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
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 * Uncompresses an input {@link ByteBuf} encoded with Snappy compression into an
27 * output {@link ByteBuf}.
28 * <p>
29 * See <a href="https://github.com/google/snappy/blob/master/format_description.txt">snappy format</a>.
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 // The offset of a copy is encoded as an unsigned 16 bit value, so we can't reference data that is further back.
36 private static final int MAX_COPY_OFFSET = 0xFFFF;
37
38 // used as a return value to indicate that we haven't yet read our full preamble
39 private static final int PREAMBLE_NOT_FULL = -1;
40 private static final int NOT_ENOUGH_INPUT = -1;
41
42 // constants for the tag types
43 private static final int LITERAL = 0;
44 private static final int COPY_1_BYTE_OFFSET = 1;
45 private static final int COPY_2_BYTE_OFFSET = 2;
46 private static final int COPY_4_BYTE_OFFSET = 3;
47
48 // Hash table used to compress, shared between subsequent call to .encode()
49 private static final FastThreadLocal<short[]> HASH_TABLE = new FastThreadLocal<short[]>();
50
51 private static final boolean DEFAULT_REUSE_HASHTABLE =
52 SystemPropertyUtil.getBoolean("io.netty.handler.codec.compression.snappy.reuseHashTable", false);
53
54 public Snappy() {
55 this(DEFAULT_REUSE_HASHTABLE);
56 }
57
58 Snappy(boolean reuseHashtable) {
59 this.reuseHashtable = reuseHashtable;
60 }
61
62 public static Snappy withHashTableReuse() {
63 return new Snappy(true);
64 }
65
66 private final boolean reuseHashtable;
67 private State state = State.READING_PREAMBLE;
68 private byte tag;
69 private int written;
70
71 private enum State {
72 READING_PREAMBLE,
73 READING_TAG,
74 READING_LITERAL,
75 READING_COPY
76 }
77
78 public void reset() {
79 state = State.READING_PREAMBLE;
80 tag = 0;
81 written = 0;
82 }
83
84 public void encode(final ByteBuf in, final ByteBuf out, final int length) {
85 // Write the preamble length to the output buffer
86 for (int i = 0;; i ++) {
87 int b = length >>> i * 7;
88 if ((b & 0xFFFFFF80) != 0) {
89 out.writeByte(b & 0x7f | 0x80);
90 } else {
91 out.writeByte(b);
92 break;
93 }
94 }
95
96 int inIndex = in.readerIndex();
97 final int baseIndex = inIndex;
98 final int endIndex = baseIndex + length;
99
100 int hashTableSize = MathUtil.findNextPositivePowerOfTwo(length);
101 hashTableSize = Math.min(hashTableSize, MAX_HT_SIZE);
102 final short[] table = getHashTable(hashTableSize);
103 final int shift = Integer.numberOfLeadingZeros(hashTableSize) + 1;
104
105 int nextEmit = inIndex;
106
107 if (length >= MIN_COMPRESSIBLE_BYTES) {
108 int nextHash = hash(in, ++inIndex, shift);
109 outer: while (true) {
110 int skip = 32;
111
112 int candidate;
113 int nextIndex = inIndex;
114 do {
115 inIndex = nextIndex;
116 int hash = nextHash;
117 int bytesBetweenHashLookups = skip++ >> 5;
118 nextIndex = inIndex + bytesBetweenHashLookups;
119
120 // We need at least 4 remaining bytes to read the hash
121 if (nextIndex > endIndex - 4) {
122 break outer;
123 }
124
125 nextHash = hash(in, nextIndex, shift);
126
127 candidate = candidate(table[hash], inIndex, baseIndex);
128
129 table[hash] = (short) (inIndex - baseIndex);
130 }
131 while (!isMatch(in, inIndex, candidate));
132
133 encodeLiteral(in, out, inIndex - nextEmit);
134
135 int insertTail;
136 do {
137 int base = inIndex;
138 int matched = 4 + findMatchingLength(in, candidate + 4, inIndex + 4, endIndex);
139 inIndex += matched;
140 int offset = base - candidate;
141 encodeCopy(out, offset, matched);
142 in.readerIndex(in.readerIndex() + matched);
143 insertTail = inIndex - 1;
144 nextEmit = inIndex;
145 if (inIndex >= endIndex - 4) {
146 break outer;
147 }
148
149 int prevHash = hash(in, insertTail, shift);
150 table[prevHash] = (short) (inIndex - baseIndex - 1);
151 int currentHash = hash(in, insertTail + 1, shift);
152 candidate = candidate(table[currentHash], insertTail + 1, baseIndex);
153 table[currentHash] = (short) (inIndex - baseIndex);
154 }
155 while (isMatch(in, insertTail + 1, candidate));
156
157 nextHash = hash(in, insertTail + 2, shift);
158 ++inIndex;
159 }
160 }
161
162 // If there are any remaining characters, write them out as a literal
163 if (nextEmit < endIndex) {
164 encodeLiteral(in, out, endIndex - nextEmit);
165 }
166 }
167
168 /**
169 * Returns the position of the closest earlier occurrence of the hash table entry, that is at most
170 * {@link #MAX_COPY_OFFSET} bytes before the index.
171 * <p>
172 * The hash table only stores the lower 16 bits of a position, so for an input longer than 65535 bytes we
173 * need to restore the upper bits from the index. The result is only a candidate and needs to be verified with
174 * {@link #isMatch(ByteBuf, int, int)}.
175 */
176 private static int candidate(short entry, int index, int baseIndex) {
177 return index - ((index - baseIndex - (entry & 0xFFFF)) & MAX_COPY_OFFSET);
178 }
179
180 /**
181 * Returns {@code true} if the candidate is before the index (so it can be referenced with a copy) and the
182 * 4 bytes at the candidate are the same as at the index.
183 */
184 private static boolean isMatch(ByteBuf in, int index, int candidate) {
185 return candidate < index && in.getInt(index) == in.getInt(candidate);
186 }
187
188 /**
189 * Hashes the 4 bytes located at index, shifting the resulting hash into
190 * the appropriate range for our hash table.
191 *
192 * @param in The input buffer to read 4 bytes from
193 * @param index The index to read at
194 * @param shift The shift value, for ensuring that the resulting value is
195 * within the range of our hash table size
196 * @return A 32-bit hash of 4 bytes located at index
197 */
198 private static int hash(ByteBuf in, int index, int shift) {
199 return in.getInt(index) * 0x1e35a7bd >>> shift;
200 }
201
202 /**
203 * Returns a short[] to be used as a hashtable
204 *
205 * @param hashTableSize the size for the hashtable
206 * @return An appropriately sized empty hashtable
207 */
208 private short[] getHashTable(int hashTableSize) {
209 if (reuseHashtable) {
210 return getHashTableFastThreadLocalArrayFill(hashTableSize);
211 }
212 return new short[hashTableSize];
213 }
214
215 /**
216 * Returns a short[] from a FastThreadLocal, zeroing for correctness
217 * creating a new one and resizing it if necessary
218 *
219 * @return An appropriately sized empty hashtable
220 * @param hashTableSize
221 */
222 public static short[] getHashTableFastThreadLocalArrayFill(int hashTableSize) {
223 short[] hashTable = HASH_TABLE.get();
224 if (hashTable == null || hashTable.length < hashTableSize) {
225 hashTable = new short[hashTableSize];
226 HASH_TABLE.set(hashTable);
227 return hashTable;
228 }
229
230 Arrays.fill(hashTable, 0, hashTableSize, (short) 0);
231 return hashTable;
232 }
233
234 /**
235 * Iterates over the supplied input buffer between the supplied minIndex and
236 * maxIndex to find how long our matched copy overlaps with an already-written
237 * literal value.
238 *
239 * @param in The input buffer to scan over
240 * @param minIndex The index in the input buffer to start scanning from
241 * @param inIndex The index of the start of our copy
242 * @param maxIndex The length of our input buffer
243 * @return The number of bytes for which our candidate copy is a repeat of
244 */
245 private static int findMatchingLength(ByteBuf in, int minIndex, int inIndex, int maxIndex) {
246 int matched = 0;
247
248 while (inIndex <= maxIndex - 4 &&
249 in.getInt(inIndex) == in.getInt(minIndex + matched)) {
250 inIndex += 4;
251 matched += 4;
252 }
253
254 while (inIndex < maxIndex && in.getByte(minIndex + matched) == in.getByte(inIndex)) {
255 ++inIndex;
256 ++matched;
257 }
258
259 return matched;
260 }
261
262 /**
263 * Calculates the minimum number of bits required to encode a value. This can
264 * then in turn be used to calculate the number of septets or octets (as
265 * appropriate) to use to encode a length parameter.
266 *
267 * @param value The value to calculate the minimum number of bits required to encode
268 * @return The minimum number of bits required to encode the supplied value
269 */
270 private static int bitsToEncode(int value) {
271 int highestOneBit = Integer.highestOneBit(value);
272 int bitLength = 0;
273 while ((highestOneBit >>= 1) != 0) {
274 bitLength++;
275 }
276
277 return bitLength;
278 }
279
280 /**
281 * Writes a literal to the supplied output buffer by directly copying from
282 * the input buffer. The literal is taken from the current readerIndex
283 * up to the supplied length.
284 *
285 * @param in The input buffer to copy from
286 * @param out The output buffer to copy to
287 * @param length The length of the literal to copy
288 */
289 static void encodeLiteral(ByteBuf in, ByteBuf out, int length) {
290 if (length < 61) {
291 out.writeByte(length - 1 << 2);
292 } else {
293 int bitLength = bitsToEncode(length - 1);
294 int bytesToEncode = 1 + bitLength / 8;
295 out.writeByte(59 + bytesToEncode << 2);
296 for (int i = 0; i < bytesToEncode; i++) {
297 out.writeByte(length - 1 >> i * 8 & 0x0ff);
298 }
299 }
300
301 out.writeBytes(in, length);
302 }
303
304 private static void encodeCopyWithOffset(ByteBuf out, int offset, int length) {
305 if (length < 12 && offset < 2048) {
306 out.writeByte(COPY_1_BYTE_OFFSET | length - 4 << 2 | offset >> 8 << 5);
307 out.writeByte(offset & 0x0ff);
308 } else {
309 out.writeByte(COPY_2_BYTE_OFFSET | length - 1 << 2);
310 out.writeByte(offset & 0x0ff);
311 out.writeByte(offset >> 8 & 0x0ff);
312 }
313 }
314
315 /**
316 * Encodes a series of copies, each at most 64 bytes in length.
317 *
318 * @param out The output buffer to write the copy pointer to
319 * @param offset The offset at which the original instance lies
320 * @param length The length of the original instance
321 */
322 private static void encodeCopy(ByteBuf out, int offset, int length) {
323 while (length >= 68) {
324 encodeCopyWithOffset(out, offset, 64);
325 length -= 64;
326 }
327
328 if (length > 64) {
329 encodeCopyWithOffset(out, offset, 60);
330 length -= 60;
331 }
332
333 encodeCopyWithOffset(out, offset, length);
334 }
335
336 public void decode(ByteBuf in, ByteBuf out) {
337 while (in.isReadable()) {
338 switch (state) {
339 case READING_PREAMBLE:
340 int uncompressedLength = readPreamble(in);
341 if (uncompressedLength == PREAMBLE_NOT_FULL) {
342 // We've not yet read all of the preamble, so wait until we can
343 return;
344 }
345 if (uncompressedLength == 0) {
346 // Should never happen, but it does mean we have nothing further to do
347 return;
348 }
349 out.ensureWritable(uncompressedLength);
350 state = State.READING_TAG;
351 // fall through
352 case READING_TAG:
353 if (!in.isReadable()) {
354 return;
355 }
356 tag = in.readByte();
357 switch (tag & 0x03) {
358 case LITERAL:
359 state = State.READING_LITERAL;
360 break;
361 case COPY_1_BYTE_OFFSET:
362 case COPY_2_BYTE_OFFSET:
363 case COPY_4_BYTE_OFFSET:
364 state = State.READING_COPY;
365 break;
366 }
367 break;
368 case READING_LITERAL:
369 int literalWritten = decodeLiteral(tag, in, out);
370 if (literalWritten != NOT_ENOUGH_INPUT) {
371 state = State.READING_TAG;
372 written += literalWritten;
373 } else {
374 // Need to wait for more data
375 return;
376 }
377 break;
378 case READING_COPY:
379 int decodeWritten;
380 switch (tag & 0x03) {
381 case COPY_1_BYTE_OFFSET:
382 decodeWritten = decodeCopyWith1ByteOffset(tag, in, out, written);
383 if (decodeWritten != NOT_ENOUGH_INPUT) {
384 state = State.READING_TAG;
385 written += decodeWritten;
386 } else {
387 // Need to wait for more data
388 return;
389 }
390 break;
391 case COPY_2_BYTE_OFFSET:
392 decodeWritten = decodeCopyWith2ByteOffset(tag, in, out, written);
393 if (decodeWritten != NOT_ENOUGH_INPUT) {
394 state = State.READING_TAG;
395 written += decodeWritten;
396 } else {
397 // Need to wait for more data
398 return;
399 }
400 break;
401 case COPY_4_BYTE_OFFSET:
402 decodeWritten = decodeCopyWith4ByteOffset(tag, in, out, written);
403 if (decodeWritten != NOT_ENOUGH_INPUT) {
404 state = State.READING_TAG;
405 written += decodeWritten;
406 } else {
407 // Need to wait for more data
408 return;
409 }
410 break;
411 }
412 }
413 }
414 }
415
416 /**
417 * Reads the length varint (a series of bytes, where the lower 7 bits
418 * are data and the upper bit is a flag to indicate more bytes to be
419 * read).
420 *
421 * @param in The input buffer to read the preamble from
422 * @return The calculated length based on the input buffer, or 0 if
423 * no preamble is able to be calculated
424 */
425 private static int readPreamble(ByteBuf in) {
426 int length = 0;
427 int byteIndex = 0;
428 while (in.isReadable()) {
429 int current = in.readUnsignedByte();
430 length |= (current & 0x7f) << byteIndex++ * 7;
431 if ((current & 0x80) == 0) {
432 return length;
433 }
434
435 if (byteIndex >= 4) {
436 throw new DecompressionException("Preamble is greater than 4 bytes");
437 }
438 }
439
440 return 0;
441 }
442
443 /**
444 * Get the length varint (a series of bytes, where the lower 7 bits
445 * are data and the upper bit is a flag to indicate more bytes to be
446 * read).
447 *
448 * @param in The input buffer to get the preamble from
449 * @return The calculated length based on the input buffer, or 0 if
450 * no preamble is able to be calculated
451 */
452 int getPreamble(ByteBuf in) {
453 if (state == State.READING_PREAMBLE) {
454 int readerIndex = in.readerIndex();
455 try {
456 return readPreamble(in);
457 } finally {
458 in.readerIndex(readerIndex);
459 }
460 }
461 return 0;
462 }
463
464 /**
465 * Reads a literal from the input buffer directly to the output buffer.
466 * A "literal" is an uncompressed segment of data stored directly in the
467 * byte stream.
468 *
469 * @param tag The tag that identified this segment as a literal is also
470 * used to encode part of the length of the data
471 * @param in The input buffer to read the literal from
472 * @param out The output buffer to write the literal to
473 * @return The number of bytes appended to the output buffer, or -1 to indicate "try again later"
474 */
475 static int decodeLiteral(byte tag, ByteBuf in, ByteBuf out) {
476 in.markReaderIndex();
477 int length;
478 switch(tag >> 2 & 0x3F) {
479 case 60:
480 if (!in.isReadable()) {
481 return NOT_ENOUGH_INPUT;
482 }
483 length = in.readUnsignedByte();
484 break;
485 case 61:
486 if (in.readableBytes() < 2) {
487 return NOT_ENOUGH_INPUT;
488 }
489 length = in.readUnsignedShortLE();
490 break;
491 case 62:
492 if (in.readableBytes() < 3) {
493 return NOT_ENOUGH_INPUT;
494 }
495 length = in.readUnsignedMediumLE();
496 break;
497 case 63:
498 if (in.readableBytes() < 4) {
499 return NOT_ENOUGH_INPUT;
500 }
501 // The length - 1 is stored as unsigned 32-bit integer.
502 long literalLength = in.readUnsignedIntLE();
503 if (literalLength >= Integer.MAX_VALUE) {
504 throw new DecompressionException("Literal length too large: " + (literalLength + 1));
505 }
506 length = (int) literalLength;
507 break;
508 default:
509 length = tag >> 2 & 0x3F;
510 }
511 length += 1;
512
513 if (in.readableBytes() < length) {
514 in.resetReaderIndex();
515 return NOT_ENOUGH_INPUT;
516 }
517
518 out.writeBytes(in, length);
519 return length;
520 }
521
522 /**
523 * Reads a compressed reference offset and length from the supplied input
524 * buffer, seeks back to the appropriate place in the input buffer and
525 * writes the found data to the supplied output stream.
526 *
527 * @param tag The tag used to identify this as a copy is also used to encode
528 * the length and part of the offset
529 * @param in The input buffer to read from
530 * @param out The output buffer to write to
531 * @return The number of bytes appended to the output buffer, or -1 to indicate
532 * "try again later"
533 * @throws DecompressionException If the read offset is invalid
534 */
535 private static int decodeCopyWith1ByteOffset(byte tag, ByteBuf in, ByteBuf out, int writtenSoFar) {
536 if (!in.isReadable()) {
537 return NOT_ENOUGH_INPUT;
538 }
539
540 int initialIndex = out.writerIndex();
541 int length = 4 + ((tag & 0x01c) >> 2);
542 int offset = (tag & 0x0e0) << 8 >> 5 | in.readUnsignedByte();
543
544 validateOffset(offset, writtenSoFar);
545
546 out.markReaderIndex();
547 if (offset < length) {
548 int copies = length / offset;
549 for (; copies > 0; copies--) {
550 out.readerIndex(initialIndex - offset);
551 out.readBytes(out, offset);
552 }
553 if (length % offset != 0) {
554 out.readerIndex(initialIndex - offset);
555 out.readBytes(out, length % offset);
556 }
557 } else {
558 out.readerIndex(initialIndex - offset);
559 out.readBytes(out, length);
560 }
561 out.resetReaderIndex();
562
563 return length;
564 }
565
566 /**
567 * Reads a compressed reference offset and length from the supplied input
568 * buffer, seeks back to the appropriate place in the input buffer and
569 * writes the found data to the supplied output stream.
570 *
571 * @param tag The tag used to identify this as a copy is also used to encode
572 * the length and part of the offset
573 * @param in The input buffer to read from
574 * @param out The output buffer to write to
575 * @throws DecompressionException If the read offset is invalid
576 * @return The number of bytes appended to the output buffer, or -1 to indicate
577 * "try again later"
578 */
579 private static int decodeCopyWith2ByteOffset(byte tag, ByteBuf in, ByteBuf out, int writtenSoFar) {
580 if (in.readableBytes() < 2) {
581 return NOT_ENOUGH_INPUT;
582 }
583
584 int initialIndex = out.writerIndex();
585 int length = 1 + (tag >> 2 & 0x03f);
586 int offset = in.readUnsignedShortLE();
587
588 validateOffset(offset, writtenSoFar);
589
590 out.markReaderIndex();
591 if (offset < length) {
592 int copies = length / offset;
593 for (; copies > 0; copies--) {
594 out.readerIndex(initialIndex - offset);
595 out.readBytes(out, offset);
596 }
597 if (length % offset != 0) {
598 out.readerIndex(initialIndex - offset);
599 out.readBytes(out, length % offset);
600 }
601 } else {
602 out.readerIndex(initialIndex - offset);
603 out.readBytes(out, length);
604 }
605 out.resetReaderIndex();
606
607 return length;
608 }
609
610 /**
611 * Reads a compressed reference offset and length from the supplied input
612 * buffer, seeks back to the appropriate place in the input buffer and
613 * writes the found data to the supplied output stream.
614 *
615 * @param tag The tag used to identify this as a copy is also used to encode
616 * the length and part of the offset
617 * @param in The input buffer to read from
618 * @param out The output buffer to write to
619 * @return The number of bytes appended to the output buffer, or -1 to indicate
620 * "try again later"
621 * @throws DecompressionException If the read offset is invalid
622 */
623 private static int decodeCopyWith4ByteOffset(byte tag, ByteBuf in, ByteBuf out, int writtenSoFar) {
624 if (in.readableBytes() < 4) {
625 return NOT_ENOUGH_INPUT;
626 }
627
628 int initialIndex = out.writerIndex();
629 int length = 1 + (tag >> 2 & 0x03F);
630 int offset = in.readIntLE();
631
632 validateOffset(offset, writtenSoFar);
633
634 out.markReaderIndex();
635 if (offset < length) {
636 int copies = length / offset;
637 for (; copies > 0; copies--) {
638 out.readerIndex(initialIndex - offset);
639 out.readBytes(out, offset);
640 }
641 if (length % offset != 0) {
642 out.readerIndex(initialIndex - offset);
643 out.readBytes(out, length % offset);
644 }
645 } else {
646 out.readerIndex(initialIndex - offset);
647 out.readBytes(out, length);
648 }
649 out.resetReaderIndex();
650
651 return length;
652 }
653
654 /**
655 * Validates that the offset extracted from a compressed reference is within
656 * the permissible bounds of an offset (0 < offset < Integer.MAX_VALUE), and does not
657 * exceed the length of the chunk currently read so far.
658 *
659 * @param offset The offset extracted from the compressed reference
660 * @param chunkSizeSoFar The number of bytes read so far from this chunk
661 * @throws DecompressionException if the offset is invalid
662 */
663 private static void validateOffset(int offset, int chunkSizeSoFar) {
664 if (offset == 0) {
665 throw new DecompressionException("Offset is less than minimum permissible value");
666 }
667
668 if (offset < 0) {
669 // Due to arithmetic overflow
670 throw new DecompressionException("Offset is greater than maximum value supported by this implementation");
671 }
672
673 if (offset > chunkSizeSoFar) {
674 throw new DecompressionException("Offset exceeds size of chunk");
675 }
676 }
677
678 /**
679 * Computes the CRC32C checksum of the supplied data and performs the "mask" operation
680 * on the computed checksum
681 *
682 * @param data The input data to calculate the CRC32C checksum of
683 */
684 static int calculateChecksum(ByteBuf data) {
685 return calculateChecksum(data, data.readerIndex(), data.readableBytes());
686 }
687
688 /**
689 * Computes the CRC32C checksum of the supplied data and performs the "mask" operation
690 * on the computed checksum
691 *
692 * @param data The input data to calculate the CRC32C checksum of
693 */
694 static int calculateChecksum(ByteBuf data, int offset, int length) {
695 Crc32c crc32 = new Crc32c();
696 try {
697 crc32.update(data, offset, length);
698 return maskChecksum(crc32.getValue());
699 } finally {
700 crc32.reset();
701 }
702 }
703
704 /**
705 * Computes the CRC32C checksum of the supplied data, performs the "mask" operation
706 * on the computed checksum, and then compares the resulting masked checksum to the
707 * supplied checksum.
708 *
709 * @param expectedChecksum The checksum decoded from the stream to compare against
710 * @param data The input data to calculate the CRC32C checksum of
711 * @throws DecompressionException If the calculated and supplied checksums do not match
712 */
713 static void validateChecksum(int expectedChecksum, ByteBuf data) {
714 validateChecksum(expectedChecksum, data, data.readerIndex(), data.readableBytes());
715 }
716
717 /**
718 * Computes the CRC32C checksum of the supplied data, performs the "mask" operation
719 * on the computed checksum, and then compares the resulting masked checksum to the
720 * supplied checksum.
721 *
722 * @param expectedChecksum The checksum decoded from the stream to compare against
723 * @param data The input data to calculate the CRC32C checksum of
724 * @throws DecompressionException If the calculated and supplied checksums do not match
725 */
726 static void validateChecksum(int expectedChecksum, ByteBuf data, int offset, int length) {
727 final int actualChecksum = calculateChecksum(data, offset, length);
728 if (actualChecksum != expectedChecksum) {
729 throw new DecompressionException(
730 "mismatching checksum: " + Integer.toHexString(actualChecksum) +
731 " (expected: " + Integer.toHexString(expectedChecksum) + ')');
732 }
733 }
734
735 /**
736 * From the spec:
737 * <p>
738 * "Checksums are not stored directly, but masked, as checksumming data and
739 * then its own checksum can be problematic. The masking is the same as used
740 * in Apache Hadoop: Rotate the checksum by 15 bits, then add the constant
741 * 0xa282ead8 (using wraparound as normal for unsigned integers)."
742 *
743 * @param checksum The actual checksum of the data
744 * @return The masked checksum
745 */
746 static int maskChecksum(long checksum) {
747 return (int) ((checksum >> 15 | checksum << 17) + 0xa282ead8);
748 }
749 }