Skip to content

Commit 79fc272

Browse files
committed
Add Highway hash algorithm for client_hash
1 parent 2e1329e commit 79fc272

File tree

3 files changed

+441
-0
lines changed

3 files changed

+441
-0
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/BuiltInOpenTelemetryMetricsProvider.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,11 @@ private static String getProcessId() {
143143
}
144144
}
145145
}
146+
147+
public static String generateClientHash(String clientUid) {
148+
long[] key = {1, 2, 3, 4};
149+
long hash = HighwayHash.hash64(clientUid.getBytes(), 0, 10, key);
150+
String client_hash = HighwayHash.convertToHex(hash, 10);
151+
return client_hash;
152+
}
146153
}
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://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,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.spanner;
18+
19+
/**
20+
* HighwayHash algorithm. See <a href="https://github.com/google/highwayhash">
21+
* HighwayHash on GitHub</a>
22+
*/
23+
public final class HighwayHash {
24+
private final long[] v0 = new long[4];
25+
private final long[] v1 = new long[4];
26+
private final long[] mul0 = new long[4];
27+
private final long[] mul1 = new long[4];
28+
private boolean done = false;
29+
30+
/**
31+
* @param key0 first 8 bytes of the key
32+
* @param key1 next 8 bytes of the key
33+
* @param key2 next 8 bytes of the key
34+
* @param key3 last 8 bytes of the key
35+
*/
36+
public HighwayHash(long key0, long key1, long key2, long key3) {
37+
reset(key0, key1, key2, key3);
38+
}
39+
40+
/**
41+
* @param key array of size 4 with the key to initialize the hash with
42+
*/
43+
public HighwayHash(long[] key) {
44+
if (key.length != 4) {
45+
throw new IllegalArgumentException(String.format("Key length (%s) must be 4", key.length));
46+
}
47+
reset(key[0], key[1], key[2], key[3]);
48+
}
49+
50+
/**
51+
* Updates the hash with 32 bytes of data. If you can read 4 long values
52+
* from your data efficiently, prefer using update() instead for more speed.
53+
* @param packet data array which has a length of at least pos + 32
54+
* @param pos position in the array to read the first of 32 bytes from
55+
*/
56+
public void updatePacket(byte[] packet, int pos) {
57+
if (pos < 0) {
58+
throw new IllegalArgumentException(String.format("Pos (%s) must be positive", pos));
59+
}
60+
if (pos + 32 > packet.length) {
61+
throw new IllegalArgumentException("packet must have at least 32 bytes after pos");
62+
}
63+
long a0 = read64(packet, pos + 0);
64+
long a1 = read64(packet, pos + 8);
65+
long a2 = read64(packet, pos + 16);
66+
long a3 = read64(packet, pos + 24);
67+
update(a0, a1, a2, a3);
68+
}
69+
70+
/**
71+
* Updates the hash with 32 bytes of data given as 4 longs. This function is
72+
* more efficient than updatePacket when you can use it.
73+
* @param a0 first 8 bytes in little endian 64-bit long
74+
* @param a1 next 8 bytes in little endian 64-bit long
75+
* @param a2 next 8 bytes in little endian 64-bit long
76+
* @param a3 last 8 bytes in little endian 64-bit long
77+
*/
78+
public void update(long a0, long a1, long a2, long a3) {
79+
if (done) {
80+
throw new IllegalStateException("Can compute a hash only once per instance");
81+
}
82+
v1[0] += mul0[0] + a0;
83+
v1[1] += mul0[1] + a1;
84+
v1[2] += mul0[2] + a2;
85+
v1[3] += mul0[3] + a3;
86+
for (int i = 0; i < 4; ++i) {
87+
mul0[i] ^= (v1[i] & 0xffffffffL) * (v0[i] >>> 32);
88+
v0[i] += mul1[i];
89+
mul1[i] ^= (v0[i] & 0xffffffffL) * (v1[i] >>> 32);
90+
}
91+
v0[0] += zipperMerge0(v1[1], v1[0]);
92+
v0[1] += zipperMerge1(v1[1], v1[0]);
93+
v0[2] += zipperMerge0(v1[3], v1[2]);
94+
v0[3] += zipperMerge1(v1[3], v1[2]);
95+
v1[0] += zipperMerge0(v0[1], v0[0]);
96+
v1[1] += zipperMerge1(v0[1], v0[0]);
97+
v1[2] += zipperMerge0(v0[3], v0[2]);
98+
v1[3] += zipperMerge1(v0[3], v0[2]);
99+
}
100+
101+
102+
/**
103+
* Updates the hash with the last 1 to 31 bytes of the data. You must use
104+
* updatePacket first per 32 bytes of the data, if and only if 1 to 31 bytes
105+
* of the data are not processed after that, updateRemainder must be used for
106+
* those final bytes.
107+
* @param bytes data array which has a length of at least pos + size_mod32
108+
* @param pos position in the array to start reading size_mod32 bytes from
109+
* @param size_mod32 the amount of bytes to read
110+
*/
111+
public void updateRemainder(byte[] bytes, int pos, int size_mod32) {
112+
if (pos < 0) {
113+
throw new IllegalArgumentException(String.format("Pos (%s) must be positive", pos));
114+
}
115+
if (size_mod32 < 0 || size_mod32 >= 32) {
116+
throw new IllegalArgumentException(
117+
String.format("size_mod32 (%s) must be between 0 and 31", size_mod32));
118+
}
119+
if (pos + size_mod32 > bytes.length) {
120+
throw new IllegalArgumentException("bytes must have at least size_mod32 bytes after pos");
121+
}
122+
int size_mod4 = size_mod32 & 3;
123+
int remainder = size_mod32 & ~3;
124+
byte[] packet = new byte[32];
125+
for (int i = 0; i < 4; ++i) {
126+
v0[i] += ((long)size_mod32 << 32) + size_mod32;
127+
}
128+
rotate32By(size_mod32, v1);
129+
for (int i = 0; i < remainder; i++) {
130+
packet[i] = bytes[pos + i];
131+
}
132+
if ((size_mod32 & 16) != 0) {
133+
for (int i = 0; i < 4; i++) {
134+
packet[28 + i] = bytes[pos + remainder + i + size_mod4 - 4];
135+
}
136+
} else {
137+
if (size_mod4 != 0) {
138+
packet[16 + 0] = bytes[pos + remainder + 0];
139+
packet[16 + 1] = bytes[pos + remainder + (size_mod4 >>> 1)];
140+
packet[16 + 2] = bytes[pos + remainder + (size_mod4 - 1)];
141+
}
142+
}
143+
updatePacket(packet, 0);
144+
}
145+
146+
/**
147+
* Computes the hash value after all bytes were processed. Invalidates the
148+
* state.
149+
*
150+
* NOTE: The 64-bit HighwayHash algorithm is declared stable and no longer subject to change.
151+
*
152+
* @return 64-bit hash
153+
*/
154+
public long finalize64() {
155+
permuteAndUpdate();
156+
permuteAndUpdate();
157+
permuteAndUpdate();
158+
permuteAndUpdate();
159+
done = true;
160+
return v0[0] + v1[0] + mul0[0] + mul1[0];
161+
}
162+
163+
/**
164+
* Computes the hash value after all bytes were processed. Invalidates the state.
165+
*
166+
* @return array of size 2 containing 128-bit hash
167+
*/
168+
public long[] finalize128() {
169+
permuteAndUpdate();
170+
permuteAndUpdate();
171+
permuteAndUpdate();
172+
permuteAndUpdate();
173+
permuteAndUpdate();
174+
permuteAndUpdate();
175+
done = true;
176+
long[] hash = new long[2];
177+
hash[0] = v0[0] + mul0[0] + v1[2] + mul1[2];
178+
hash[1] = v0[1] + mul0[1] + v1[3] + mul1[3];
179+
return hash;
180+
}
181+
182+
/**
183+
* Computes the hash value after all bytes were processed. Invalidates the state.
184+
*
185+
* @return array of size 4 containing 256-bit hash
186+
*/
187+
public long[] finalize256() {
188+
permuteAndUpdate();
189+
permuteAndUpdate();
190+
permuteAndUpdate();
191+
permuteAndUpdate();
192+
permuteAndUpdate();
193+
permuteAndUpdate();
194+
permuteAndUpdate();
195+
permuteAndUpdate();
196+
permuteAndUpdate();
197+
permuteAndUpdate();
198+
done = true;
199+
long[] hash = new long[4];
200+
modularReduction(v1[1] + mul1[1], v1[0] + mul1[0],
201+
v0[1] + mul0[1], v0[0] + mul0[0],
202+
hash, 0);
203+
modularReduction(v1[3] + mul1[3], v1[2] + mul1[2],
204+
v0[3] + mul0[3], v0[2] + mul0[2],
205+
hash, 2);
206+
return hash;
207+
}
208+
private void reset(long key0, long key1, long key2, long key3) {
209+
mul0[0] = 0xdbe6d5d5fe4cce2fL;
210+
mul0[1] = 0xa4093822299f31d0L;
211+
mul0[2] = 0x13198a2e03707344L;
212+
mul0[3] = 0x243f6a8885a308d3L;
213+
mul1[0] = 0x3bd39e10cb0ef593L;
214+
mul1[1] = 0xc0acf169b5f18a8cL;
215+
mul1[2] = 0xbe5466cf34e90c6cL;
216+
mul1[3] = 0x452821e638d01377L;
217+
v0[0] = mul0[0] ^ key0;
218+
v0[1] = mul0[1] ^ key1;
219+
v0[2] = mul0[2] ^ key2;
220+
v0[3] = mul0[3] ^ key3;
221+
v1[0] = mul1[0] ^ ((key0 >>> 32) | (key0 << 32));
222+
v1[1] = mul1[1] ^ ((key1 >>> 32) | (key1 << 32));
223+
v1[2] = mul1[2] ^ ((key2 >>> 32) | (key2 << 32));
224+
v1[3] = mul1[3] ^ ((key3 >>> 32) | (key3 << 32));
225+
}
226+
227+
private long zipperMerge0(long v1, long v0) {
228+
return (((v0 & 0xff000000L) | (v1 & 0xff00000000L)) >>> 24) |
229+
(((v0 & 0xff0000000000L) | (v1 & 0xff000000000000L)) >>> 16) |
230+
(v0 & 0xff0000L) | ((v0 & 0xff00L) << 32) |
231+
((v1 & 0xff00000000000000L) >>> 8) | (v0 << 56);
232+
}
233+
234+
private long zipperMerge1(long v1, long v0) {
235+
return (((v1 & 0xff000000L) | (v0 & 0xff00000000L)) >>> 24) |
236+
(v1 & 0xff0000L) | ((v1 & 0xff0000000000L) >>> 16) |
237+
((v1 & 0xff00L) << 24) | ((v0 & 0xff000000000000L) >>> 8) |
238+
((v1 & 0xffL) << 48) | (v0 & 0xff00000000000000L);
239+
}
240+
241+
private long read64(byte[] src, int pos) {
242+
// Mask with 0xffL so that it is 0..255 as long (byte can only be -128..127)
243+
return (src[pos + 0] & 0xffL) | ((src[pos + 1] & 0xffL) << 8) |
244+
((src[pos + 2] & 0xffL) << 16) | ((src[pos + 3] & 0xffL) << 24) |
245+
((src[pos + 4] & 0xffL) << 32) | ((src[pos + 5] & 0xffL) << 40) |
246+
((src[pos + 6] & 0xffL) << 48) | ((src[pos + 7] & 0xffL) << 56);
247+
}
248+
249+
private void rotate32By(long count, long[] lanes) {
250+
for (int i = 0; i < 4; ++i) {
251+
long half0 = (lanes[i] & 0xffffffffL);
252+
long half1 = (lanes[i] >>> 32) & 0xffffffffL;
253+
lanes[i] = ((half0 << count) & 0xffffffffL) | (half0 >>> (32 - count));
254+
lanes[i] |= ((long)(((half1 << count) & 0xffffffffL) |
255+
(half1 >>> (32 - count)))) << 32;
256+
}
257+
}
258+
259+
private void permuteAndUpdate() {
260+
update((v0[2] >>> 32) | (v0[2] << 32),
261+
(v0[3] >>> 32) | (v0[3] << 32),
262+
(v0[0] >>> 32) | (v0[0] << 32),
263+
(v0[1] >>> 32) | (v0[1] << 32));
264+
}
265+
266+
private void modularReduction(long a3_unmasked, long a2, long a1,
267+
long a0, long[] hash, int pos) {
268+
long a3 = a3_unmasked & 0x3FFFFFFFFFFFFFFFL;
269+
hash[pos + 1] = a1 ^ ((a3 << 1) | (a2 >>> 63)) ^ ((a3 << 2) | (a2 >>> 62));
270+
hash[pos + 0] = a0 ^ (a2 << 1) ^ (a2 << 2);
271+
}
272+
273+
//////////////////////////////////////////////////////////////////////////////
274+
275+
/**
276+
* NOTE: The 64-bit HighwayHash algorithm is declared stable and no longer subject to change.
277+
*
278+
* @param data array with data bytes
279+
* @param offset position of first byte of data to read from
280+
* @param length number of bytes from data to read
281+
* @param key array of size 4 with the key to initialize the hash with
282+
* @return 64-bit hash for the given data
283+
*/
284+
public static long hash64(byte[] data, int offset, int length, long[] key) {
285+
HighwayHash h = new HighwayHash(key);
286+
h.processAll(data, offset, length);
287+
return h.finalize64();
288+
}
289+
290+
/**
291+
* @param data array with data bytes
292+
* @param offset position of first byte of data to read from
293+
* @param length number of bytes from data to read
294+
* @param key array of size 4 with the key to initialize the hash with
295+
* @return array of size 2 containing 128-bit hash for the given data
296+
*/
297+
public static long[] hash128(byte[] data, int offset, int length, long[] key) {
298+
HighwayHash h = new HighwayHash(key);
299+
h.processAll(data, offset, length);
300+
return h.finalize128();
301+
}
302+
303+
/**
304+
* @param data array with data bytes
305+
* @param offset position of first byte of data to read from
306+
* @param length number of bytes from data to read
307+
* @param key array of size 4 with the key to initialize the hash with
308+
* @return array of size 4 containing 256-bit hash for the given data
309+
*/
310+
public static long[] hash256(byte[] data, int offset, int length, long[] key) {
311+
HighwayHash h = new HighwayHash(key);
312+
h.processAll(data, offset, length);
313+
return h.finalize256();
314+
}
315+
316+
private void processAll(byte[] data, int offset, int length) {
317+
int i;
318+
for (i = 0; i + 32 <= length; i += 32) {
319+
updatePacket(data, offset + i);
320+
}
321+
if ((length & 31) != 0) {
322+
updateRemainder(data, offset + i, length & 31);
323+
}
324+
}
325+
326+
public static String convertToHex(long hash, int kPrefixLength) {
327+
// Perform bitwise right shift
328+
long shiftedValue = hash >> (64 - kPrefixLength);
329+
// Convert to hexadecimal and zero-pad to 6 characters
330+
return String.format("%06X", shiftedValue); // %06X for zero-padded hex string of width 6
331+
}
332+
}

0 commit comments

Comments
 (0)