View Javadoc
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 }