Skip to content

Commit 847a6d0

Browse files
committed
CODEC-241 add support for XXHash32
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/codec/trunk@1810226 13f79535-47bb-0310-9956-ffa450edef68
1 parent 949abfb commit 847a6d0

5 files changed

Lines changed: 298 additions & 0 deletions

File tree

src/changes/changes.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ The <action> type attribute can be add,update,fix,remove.
4545
<release version="1.11" date="2017-MM-DD" description="Feature and fix release.">
4646
<!-- The first attribute below should be the issue id; makes it easier to navigate in the IDE outline -->
4747

48+
<action issue="CODEC-241" type="add">Add support for XXHash32</action>
4849
<action issue="CODEC-234" dev="ggregory" type="update" due-to="Christopher Schultz, Sebb">Base32.decode should support lowercase letters</action>
4950
<action issue="CODEC-233" dev="sebb" type="update" due-to="Yossi Tamari">Soundex should support more algorithm variants</action>
5051
<action issue="CODEC-145" dev="sebb" type="fix" due-to="Jesse Glick">Base64.encodeBase64String could better use newStringUsAscii (ditto encodeBase64URLSafeString)</action>
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.commons.codec.digest;
20+
21+
import static java.lang.Integer.rotateLeft;
22+
23+
import java.util.zip.Checksum;
24+
25+
/**
26+
* Implementation of the xxhash32 hash algorithm.
27+
*
28+
* <p>Copied from Commons Compress 1.14
29+
* <a href="https://git-wip-us.apache.org/repos/asf?p=commons-compress.git;a=blob;f=src/main/java/org/apache/commons/compress/compressors/lz4/XXHash32.java;h=a406ffc197449be594d46f0d2712b2d4786a1e68;hb=HEAD">https://git-wip-us.apache.org/repos/asf?p=commons-compress.git;a=blob;f=src/main/java/org/apache/commons/compress/compressors/lz4/XXHash32.java;h=a406ffc197449be594d46f0d2712b2d4786a1e68;hb=HEAD</a></p>
30+
*
31+
* @see <a href="http://cyan4973.github.io/xxHash/">xxHash</a>
32+
* @NotThreadSafe
33+
* @since 1.11
34+
*/
35+
public class XXHash32 implements Checksum {
36+
37+
private static final int BUF_SIZE = 16;
38+
private static final int ROTATE_BITS = 13;
39+
40+
private static final int PRIME1 = (int) 2654435761l;
41+
private static final int PRIME2 = (int) 2246822519l;
42+
private static final int PRIME3 = (int) 3266489917l;
43+
private static final int PRIME4 = 668265263;
44+
private static final int PRIME5 = 374761393;
45+
46+
private final byte[] oneByte = new byte[1];
47+
private final int[] state = new int[4];
48+
// Note: the code used to use ByteBuffer but the manual method is 50% faster
49+
// See: http://git-wip-us.apache.org/repos/asf/commons-compress/diff/2f56fb5c
50+
private final byte[] buffer = new byte[BUF_SIZE];
51+
private final int seed;
52+
53+
private int totalLen;
54+
private int pos;
55+
56+
/**
57+
* Creates an XXHash32 instance with a seed of 0.
58+
*/
59+
public XXHash32() {
60+
this(0);
61+
}
62+
63+
/**
64+
* Creates an XXHash32 instance.
65+
* @param seed the seed to use
66+
*/
67+
public XXHash32(int seed) {
68+
this.seed = seed;
69+
initializeState();
70+
}
71+
72+
@Override
73+
public void reset() {
74+
initializeState();
75+
totalLen = 0;
76+
pos = 0;
77+
}
78+
79+
@Override
80+
public void update(int b) {
81+
oneByte[0] = (byte) (b & 0xff);
82+
update(oneByte, 0, 1);
83+
}
84+
85+
@Override
86+
public void update(byte[] b, int off, final int len) {
87+
if (len <= 0) {
88+
return;
89+
}
90+
totalLen += len;
91+
92+
final int end = off + len;
93+
94+
if (pos + len < BUF_SIZE) {
95+
System.arraycopy(b, off, buffer, pos, len);
96+
pos += len;
97+
return;
98+
}
99+
100+
if (pos > 0) {
101+
final int size = BUF_SIZE - pos;
102+
System.arraycopy(b, off, buffer, pos, size);
103+
process(buffer, 0);
104+
off += size;
105+
}
106+
107+
final int limit = end - BUF_SIZE;
108+
while (off <= limit) {
109+
process(b, off);
110+
off += BUF_SIZE;
111+
}
112+
113+
if (off < end) {
114+
pos = end - off;
115+
System.arraycopy(b, off, buffer, 0, pos);
116+
}
117+
}
118+
119+
@Override
120+
public long getValue() {
121+
int hash;
122+
if (totalLen > BUF_SIZE) {
123+
hash =
124+
rotateLeft(state[0], 1) +
125+
rotateLeft(state[1], 7) +
126+
rotateLeft(state[2], 12) +
127+
rotateLeft(state[3], 18);
128+
} else {
129+
hash = state[2] + PRIME5;
130+
}
131+
hash += totalLen;
132+
133+
int idx = 0;
134+
final int limit = pos - 4;
135+
for (; idx <= limit; idx += 4) {
136+
hash = rotateLeft(hash + getInt(buffer, idx) * PRIME3, 17) * PRIME4;
137+
}
138+
while (idx < pos) {
139+
hash = rotateLeft(hash + (buffer[idx++] & 0xff) * PRIME5, 11) * PRIME1;
140+
}
141+
142+
hash ^= hash >>> 15;
143+
hash *= PRIME2;
144+
hash ^= hash >>> 13;
145+
hash *= PRIME3;
146+
hash ^= hash >>> 16;
147+
return hash & 0xffffffffl;
148+
}
149+
150+
private static int getInt(byte[] buffer, int idx) {
151+
return (int) (fromLittleEndian(buffer, idx, 4) & 0xffffffffl);
152+
}
153+
154+
private void initializeState() {
155+
state[0] = seed + PRIME1 + PRIME2;
156+
state[1] = seed + PRIME2;
157+
state[2] = seed;
158+
state[3] = seed - PRIME1;
159+
}
160+
161+
private void process(byte[] b, int offset) {
162+
// local shadows for performance
163+
int s0 = state[0];
164+
int s1 = state[1];
165+
int s2 = state[2];
166+
int s3 = state[3];
167+
168+
s0 = rotateLeft(s0 + getInt(b, offset) * PRIME2, ROTATE_BITS) * PRIME1;
169+
s1 = rotateLeft(s1 + getInt(b, offset + 4) * PRIME2, ROTATE_BITS) * PRIME1;
170+
s2 = rotateLeft(s2 + getInt(b, offset + 8) * PRIME2, ROTATE_BITS) * PRIME1;
171+
s3 = rotateLeft(s3 + getInt(b, offset + 12) * PRIME2, ROTATE_BITS) * PRIME1;
172+
173+
state[0] = s0;
174+
state[1] = s1;
175+
state[2] = s2;
176+
state[3] = s3;
177+
178+
pos = 0;
179+
}
180+
181+
/**
182+
* Reads the given byte array as a little endian long.
183+
* @param bytes the byte array to convert
184+
* @param off the offset into the array that starts the value
185+
* @param length the number of bytes representing the value
186+
* @return the number read
187+
* @throws IllegalArgumentException if len is bigger than eight
188+
*/
189+
private static long fromLittleEndian(byte[] bytes, final int off, final int length) {
190+
if (length > 8) {
191+
throw new IllegalArgumentException("can't read more than eight bytes into a long value");
192+
}
193+
long l = 0;
194+
for (int i = 0; i < length; i++) {
195+
l |= (bytes[off + i] & 0xffl) << (8 * i);
196+
}
197+
return l;
198+
}
199+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.commons.codec.digest;
20+
21+
import java.io.ByteArrayOutputStream;
22+
import java.io.File;
23+
import java.io.FileInputStream;
24+
import java.io.FileNotFoundException;
25+
import java.io.IOException;
26+
import java.io.InputStream;
27+
import java.io.OutputStream;
28+
import java.net.URI;
29+
import java.net.URL;
30+
import java.util.Arrays;
31+
import java.util.Collection;
32+
33+
import org.junit.Assert;
34+
import org.junit.Test;
35+
import org.junit.runners.Parameterized;
36+
import org.junit.runners.Parameterized.Parameters;
37+
import org.junit.runner.RunWith;
38+
39+
@RunWith(Parameterized.class)
40+
public class XXHash32Test {
41+
42+
private final File file;
43+
private final String expectedChecksum;
44+
45+
public XXHash32Test(String path, String c) throws IOException {
46+
final URL url = XXHash32Test.class.getClassLoader().getResource(path);
47+
if (url == null) {
48+
throw new FileNotFoundException("couldn't find " + path);
49+
}
50+
URI uri = null;
51+
try {
52+
uri = url.toURI();
53+
} catch (final java.net.URISyntaxException ex) {
54+
throw new IOException(ex);
55+
}
56+
file = new File(uri);
57+
expectedChecksum = c;
58+
}
59+
60+
@Parameters
61+
public static Collection<Object[]> factory() {
62+
return Arrays.asList(new Object[][] {
63+
// reference checksums created with xxh32sum
64+
{ "bla.tar", "fbb5c8d1" },
65+
{ "bla.tar.xz", "4106a208" },
66+
});
67+
}
68+
69+
@Test
70+
public void verifyChecksum() throws IOException {
71+
XXHash32 h = new XXHash32();
72+
FileInputStream s = new FileInputStream(file);
73+
try {
74+
byte[] b = toByteArray(s);
75+
h.update(b, 0, b.length);
76+
} finally {
77+
s.close();
78+
}
79+
Assert.assertEquals("checksum for " + file.getName(), expectedChecksum, Long.toHexString(h.getValue()));
80+
}
81+
82+
private static byte[] toByteArray(final InputStream input) throws IOException {
83+
final ByteArrayOutputStream output = new ByteArrayOutputStream();
84+
copy(input, output, 10240);
85+
return output.toByteArray();
86+
}
87+
88+
private static long copy(final InputStream input, final OutputStream output, final int buffersize) throws IOException {
89+
final byte[] buffer = new byte[buffersize];
90+
int n = 0;
91+
long count=0;
92+
while (-1 != (n = input.read(buffer))) {
93+
output.write(buffer, 0, n);
94+
count += n;
95+
}
96+
return count;
97+
}
98+
}

src/test/resources/bla.tar

10 KB
Binary file not shown.

src/test/resources/bla.tar.xz

528 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)