org.redisson.misc.HighwayHash Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of redisson Show documentation
Show all versions of redisson Show documentation
Redis Java client with features of In-Memory Data Grid
/**
* Copyright (c) 2013-2024 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright (C) 2018 The HighwayHash Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.misc;
/**
* HighwayHash algorithm. See
* HighwayHash on GitHub
*/
@SuppressWarnings({"OperatorWrap", "BooleanExpressionComplexity", "UnnecessaryParentheses", "WhitespaceAfter", "ParameterName", "LocalVariableName"})
public final class HighwayHash {
private final long[] v0 = new long[4];
private final long[] v1 = new long[4];
private final long[] mul0 = new long[4];
private final long[] mul1 = new long[4];
private boolean done = false;
/**
* @param key0 first 8 bytes of the key
* @param key1 next 8 bytes of the key
* @param key2 next 8 bytes of the key
* @param key3 last 8 bytes of the key
*/
public HighwayHash(long key0, long key1, long key2, long key3) {
reset(key0, key1, key2, key3);
}
/**
* @param key array of size 4 with the key to initialize the hash with
*/
public HighwayHash(long[] key) {
if (key.length != 4) {
throw new IllegalArgumentException(String.format("Key length (%s) must be 4", key.length));
}
reset(key[0], key[1], key[2], key[3]);
}
/**
* Updates the hash with 32 bytes of data. If you can read 4 long values
* from your data efficiently, prefer using update() instead for more speed.
* @param packet data array which has a length of at least pos + 32
* @param pos position in the array to read the first of 32 bytes from
*/
public void updatePacket(byte[] packet, int pos) {
if (pos < 0) {
throw new IllegalArgumentException(String.format("Pos (%s) must be positive", pos));
}
if (pos + 32 > packet.length) {
throw new IllegalArgumentException("packet must have at least 32 bytes after pos");
}
long a0 = read64(packet, pos + 0);
long a1 = read64(packet, pos + 8);
long a2 = read64(packet, pos + 16);
long a3 = read64(packet, pos + 24);
update(a0, a1, a2, a3);
}
/**
* Updates the hash with 32 bytes of data given as 4 longs. This function is
* more efficient than updatePacket when you can use it.
* @param a0 first 8 bytes in little endian 64-bit long
* @param a1 next 8 bytes in little endian 64-bit long
* @param a2 next 8 bytes in little endian 64-bit long
* @param a3 last 8 bytes in little endian 64-bit long
*/
public void update(long a0, long a1, long a2, long a3) {
if (done) {
throw new IllegalStateException("Can compute a hash only once per instance");
}
v1[0] += mul0[0] + a0;
v1[1] += mul0[1] + a1;
v1[2] += mul0[2] + a2;
v1[3] += mul0[3] + a3;
for (int i = 0; i < 4; ++i) {
mul0[i] ^= (v1[i] & 0xffffffffL) * (v0[i] >>> 32);
v0[i] += mul1[i];
mul1[i] ^= (v0[i] & 0xffffffffL) * (v1[i] >>> 32);
}
v0[0] += zipperMerge0(v1[1], v1[0]);
v0[1] += zipperMerge1(v1[1], v1[0]);
v0[2] += zipperMerge0(v1[3], v1[2]);
v0[3] += zipperMerge1(v1[3], v1[2]);
v1[0] += zipperMerge0(v0[1], v0[0]);
v1[1] += zipperMerge1(v0[1], v0[0]);
v1[2] += zipperMerge0(v0[3], v0[2]);
v1[3] += zipperMerge1(v0[3], v0[2]);
}
/**
* Updates the hash with the last 1 to 31 bytes of the data. You must use
* updatePacket first per 32 bytes of the data, if and only if 1 to 31 bytes
* of the data are not processed after that, updateRemainder must be used for
* those final bytes.
* @param bytes data array which has a length of at least pos + size_mod32
* @param pos position in the array to start reading size_mod32 bytes from
* @param size_mod32 the amount of bytes to read
*/
public void updateRemainder(byte[] bytes, int pos, int size_mod32) {
if (pos < 0) {
throw new IllegalArgumentException(String.format("Pos (%s) must be positive", pos));
}
if (size_mod32 < 0 || size_mod32 >= 32) {
throw new IllegalArgumentException(
String.format("size_mod32 (%s) must be between 0 and 31", size_mod32));
}
if (pos + size_mod32 > bytes.length) {
throw new IllegalArgumentException("bytes must have at least size_mod32 bytes after pos");
}
int size_mod4 = size_mod32 & 3;
int remainder = size_mod32 & ~3;
byte[] packet = new byte[32];
for (int i = 0; i < 4; ++i) {
v0[i] += ((long)size_mod32 << 32) + size_mod32;
}
rotate32By(size_mod32, v1);
for (int i = 0; i < remainder; i++) {
packet[i] = bytes[pos + i];
}
if ((size_mod32 & 16) != 0) {
for (int i = 0; i < 4; i++) {
packet[28 + i] = bytes[pos + remainder + i + size_mod4 - 4];
}
} else {
if (size_mod4 != 0) {
packet[16 + 0] = bytes[pos + remainder + 0];
packet[16 + 1] = bytes[pos + remainder + (size_mod4 >>> 1)];
packet[16 + 2] = bytes[pos + remainder + (size_mod4 - 1)];
}
}
updatePacket(packet, 0);
}
/**
* Computes the hash value after all bytes were processed. Invalidates the
* state.
*
* NOTE: The 64-bit HighwayHash algorithm is declared stable and no longer subject to change.
*
* @return 64-bit hash
*/
public long finalize64() {
permuteAndUpdate();
permuteAndUpdate();
permuteAndUpdate();
permuteAndUpdate();
done = true;
return v0[0] + v1[0] + mul0[0] + mul1[0];
}
/**
* Computes the hash value after all bytes were processed. Invalidates the
* state.
*
* NOTE: The 128-bit HighwayHash algorithm is not yet frozen and subject to change.
*
* @return array of size 2 containing 128-bit hash
*/
public long[] finalize128() {
permuteAndUpdate();
permuteAndUpdate();
permuteAndUpdate();
permuteAndUpdate();
permuteAndUpdate();
permuteAndUpdate();
done = true;
long[] hash = new long[2];
hash[0] = v0[0] + mul0[0] + v1[2] + mul1[2];
hash[1] = v0[1] + mul0[1] + v1[3] + mul1[3];
return hash;
}
/**
* Computes the hash value after all bytes were processed. Invalidates the
* state.
*
* NOTE: The 256-bit HighwayHash algorithm is not yet frozen and subject to change.
*
* @return array of size 4 containing 256-bit hash
*/
public long[] finalize256() {
permuteAndUpdate();
permuteAndUpdate();
permuteAndUpdate();
permuteAndUpdate();
permuteAndUpdate();
permuteAndUpdate();
permuteAndUpdate();
permuteAndUpdate();
permuteAndUpdate();
permuteAndUpdate();
done = true;
long[] hash = new long[4];
modularReduction(v1[1] + mul1[1], v1[0] + mul1[0],
v0[1] + mul0[1], v0[0] + mul0[0],
hash, 0);
modularReduction(v1[3] + mul1[3], v1[2] + mul1[2],
v0[3] + mul0[3], v0[2] + mul0[2],
hash, 2);
return hash;
}
private void reset(long key0, long key1, long key2, long key3) {
mul0[0] = 0xdbe6d5d5fe4cce2fL;
mul0[1] = 0xa4093822299f31d0L;
mul0[2] = 0x13198a2e03707344L;
mul0[3] = 0x243f6a8885a308d3L;
mul1[0] = 0x3bd39e10cb0ef593L;
mul1[1] = 0xc0acf169b5f18a8cL;
mul1[2] = 0xbe5466cf34e90c6cL;
mul1[3] = 0x452821e638d01377L;
v0[0] = mul0[0] ^ key0;
v0[1] = mul0[1] ^ key1;
v0[2] = mul0[2] ^ key2;
v0[3] = mul0[3] ^ key3;
v1[0] = mul1[0] ^ ((key0 >>> 32) | (key0 << 32));
v1[1] = mul1[1] ^ ((key1 >>> 32) | (key1 << 32));
v1[2] = mul1[2] ^ ((key2 >>> 32) | (key2 << 32));
v1[3] = mul1[3] ^ ((key3 >>> 32) | (key3 << 32));
}
private long zipperMerge0(long v1, long v0) {
return (((v0 & 0xff000000L) | (v1 & 0xff00000000L)) >>> 24) |
(((v0 & 0xff0000000000L) | (v1 & 0xff000000000000L)) >>> 16) |
(v0 & 0xff0000L) | ((v0 & 0xff00L) << 32) |
((v1 & 0xff00000000000000L) >>> 8) | (v0 << 56);
}
private long zipperMerge1(long v1, long v0) {
return (((v1 & 0xff000000L) | (v0 & 0xff00000000L)) >>> 24) |
(v1 & 0xff0000L) | ((v1 & 0xff0000000000L) >>> 16) |
((v1 & 0xff00L) << 24) | ((v0 & 0xff000000000000L) >>> 8) |
((v1 & 0xffL) << 48) | (v0 & 0xff00000000000000L);
}
private long read64(byte[] src, int pos) {
// Mask with 0xffL so that it is 0..255 as long (byte can only be -128..127)
return (src[pos + 0] & 0xffL) | ((src[pos + 1] & 0xffL) << 8) |
((src[pos + 2] & 0xffL) << 16) | ((src[pos + 3] & 0xffL) << 24) |
((src[pos + 4] & 0xffL) << 32) | ((src[pos + 5] & 0xffL) << 40) |
((src[pos + 6] & 0xffL) << 48) | ((src[pos + 7] & 0xffL) << 56);
}
private void rotate32By(long count, long[] lanes) {
for (int i = 0; i < 4; ++i) {
long half0 = (lanes[i] & 0xffffffffL);
long half1 = (lanes[i] >>> 32) & 0xffffffffL;
lanes[i] = ((half0 << count) & 0xffffffffL) | (half0 >>> (32 - count));
lanes[i] |= ((long)(((half1 << count) & 0xffffffffL) |
(half1 >>> (32 - count)))) << 32;
}
}
private void permuteAndUpdate() {
update((v0[2] >>> 32) | (v0[2] << 32),
(v0[3] >>> 32) | (v0[3] << 32),
(v0[0] >>> 32) | (v0[0] << 32),
(v0[1] >>> 32) | (v0[1] << 32));
}
private void modularReduction(long a3_unmasked, long a2, long a1,
long a0, long[] hash, int pos) {
long a3 = a3_unmasked & 0x3FFFFFFFFFFFFFFFL;
hash[pos + 1] = a1 ^ ((a3 << 1) | (a2 >>> 63)) ^ ((a3 << 2) | (a2 >>> 62));
hash[pos + 0] = a0 ^ (a2 << 1) ^ (a2 << 2);
}
//////////////////////////////////////////////////////////////////////////////
/**
* NOTE: The 64-bit HighwayHash algorithm is declared stable and no longer subject to change.
*
* @param data array with data bytes
* @param offset position of first byte of data to read from
* @param length number of bytes from data to read
* @param key array of size 4 with the key to initialize the hash with
* @return 64-bit hash for the given data
*/
public static long hash64(byte[] data, int offset, int length, long[] key) {
HighwayHash h = new HighwayHash(key);
h.processAll(data, offset, length);
return h.finalize64();
}
/**
* NOTE: The 128-bit HighwayHash algorithm is not yet frozen and subject to change.
*
* @param data array with data bytes
* @param offset position of first byte of data to read from
* @param length number of bytes from data to read
* @param key array of size 4 with the key to initialize the hash with
* @return array of size 2 containing 128-bit hash for the given data
*/
public static long[] hash128(
byte[] data, int offset, int length, long[] key) {
HighwayHash h = new HighwayHash(key);
h.processAll(data, offset, length);
return h.finalize128();
}
/**
* NOTE: The 256-bit HighwayHash algorithm is not yet frozen and subject to change.
*
* @param data array with data bytes
* @param offset position of first byte of data to read from
* @param length number of bytes from data to read
* @param key array of size 4 with the key to initialize the hash with
* @return array of size 4 containing 256-bit hash for the given data
*/
public static long[] hash256(
byte[] data, int offset, int length, long[] key) {
HighwayHash h = new HighwayHash(key);
h.processAll(data, offset, length);
return h.finalize256();
}
private void processAll(byte[] data, int offset, int length) {
int i;
for (i = 0; i + 32 <= length; i += 32) {
updatePacket(data, offset + i);
}
if ((length & 31) != 0) {
updateRemainder(data, offset + i, length & 31);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy