View Javadoc
1   /*
2    * Copyright 2019 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 net.jpountz.xxhash.StreamingXXHash32;
20  import net.jpountz.xxhash.XXHash32;
21  import net.jpountz.xxhash.XXHashFactory;
22  
23  import java.nio.ByteBuffer;
24  import java.util.zip.Checksum;
25  
26  /**
27   * A special-purpose {@link ByteBufChecksum} implementation for use with
28   * {@link Lz4FrameEncoder} and {@link Lz4FrameDecoder}.
29   *
30   * {@link StreamingXXHash32#asChecksum()} has a particularly nasty implementation
31   * of {@link Checksum#update(int)} that allocates a single-element byte array for
32   * every invocation.
33   *
34   * In addition to that, it doesn't implement an overload that accepts a {@link ByteBuffer}
35   * as an argument.
36   *
37   * Combined, this means that we can't use {@code ReflectiveByteBufChecksum} at all,
38   * and can't use {@code SlowByteBufChecksum} because of its atrocious performance
39   * with direct byte buffers (allocating an array and making a JNI call for every byte
40   * checksummed might be considered sub-optimal by some).
41   *
42   * Block version of xxHash32 ({@link XXHash32}), however, does provide
43   * {@link XXHash32#hash(ByteBuffer, int)} method that is efficient and does exactly
44   * what we need, with a caveat that we can only invoke it once before having to reset.
45   * This, however, is fine for our purposes, given the way we use it in
46   * {@link Lz4FrameEncoder} and {@link Lz4FrameDecoder}:
47   * {@code reset()}, followed by one {@code update()}, followed by {@code getValue()}.
48   */
49  public final class Lz4XXHash32 extends ByteBufChecksum {
50  
51      private static final XXHash32 XXHASH32 = XXHashFactory.fastestInstance().hash32();
52  
53      private final int seed;
54      private boolean used;
55      private int value;
56  
57      @SuppressWarnings("WeakerAccess")
58      public Lz4XXHash32(int seed) {
59          this.seed = seed;
60      }
61  
62      @Override
63      public void update(int b) {
64          throw new UnsupportedOperationException();
65      }
66  
67      @Override
68      public void update(byte[] b, int off, int len) {
69          if (used) {
70              throw new IllegalStateException();
71          }
72          value = XXHASH32.hash(b, off, len, seed);
73          used = true;
74      }
75  
76      @Override
77      public void update(ByteBuf b, int off, int len) {
78          if (used) {
79              throw new IllegalStateException();
80          }
81          if (b.hasArray()) {
82              value = XXHASH32.hash(b.array(), b.arrayOffset() + off, len, seed);
83          } else {
84              value = XXHASH32.hash(CompressionUtil.safeNioBuffer(b, off, len), seed);
85          }
86          used = true;
87      }
88  
89      @Override
90      public long getValue() {
91          if (!used) {
92              throw new IllegalStateException();
93          }
94          /*
95           * If you look carefully, you'll notice that the most significant nibble
96           * is being discarded; we believe this to be a bug, but this is what
97           * StreamingXXHash32#asChecksum() implementation of getValue() does,
98           * so we have to retain this behaviour for compatibility reasons.
99           */
100         return value & 0xFFFFFFFL;
101     }
102 
103     @Override
104     public void reset() {
105         used = false;
106     }
107 }