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 }