001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.commons.compress.compressors.lz4;
020
021import static java.lang.Integer.rotateLeft;
022import static org.apache.commons.compress.utils.ByteUtils.fromLittleEndian;
023
024import java.util.zip.Checksum;
025
026/**
027 * Implementation of the xxhash32 hash algorithm.
028 *
029 * @see <a href="http://cyan4973.github.io/xxHash/">xxHash</a>
030 * @NotThreadSafe
031 * @since 1.14
032 */
033public class XXHash32 implements Checksum {
034
035    private static final int BUF_SIZE = 16;
036    private static final int ROTATE_BITS = 13;
037
038    private static final int PRIME1 = (int) 2654435761L;
039    private static final int PRIME2 = (int) 2246822519L;
040    private static final int PRIME3 = (int) 3266489917L;
041    private static final int PRIME4 =  668265263;
042    private static final int PRIME5 =  374761393;
043
044    private final byte[] oneByte = new byte[1];
045    private final int[] state = new int[4];
046    // Note: the code used to use ByteBuffer but the manual method is 50% faster
047    // See: https://gitbox.apache.org/repos/asf/commons-compress/diff/2f56fb5c
048    private final byte[] buffer = new byte[BUF_SIZE];
049    private final int seed;
050
051    private int totalLen;
052    private int pos;
053
054    /**
055     * Creates an XXHash32 instance with a seed of 0.
056     */
057    public XXHash32() {
058        this(0);
059    }
060
061    /**
062     * Creates an XXHash32 instance.
063     * @param seed the seed to use
064     */
065    public XXHash32(final int seed) {
066        this.seed = seed;
067        initializeState();
068    }
069
070    @Override
071    public void reset() {
072        initializeState();
073        totalLen = 0;
074        pos = 0;
075    }
076
077    @Override
078    public void update(final int b) {
079        oneByte[0] = (byte) (b & 0xff);
080        update(oneByte, 0, 1);
081    }
082
083    @Override
084    public void update(final byte[] b, int off, final int len) {
085        if (len <= 0) {
086            return;
087        }
088        totalLen += len;
089
090        final int end = off + len;
091
092        if (pos + len < BUF_SIZE) {
093            System.arraycopy(b, off, buffer, pos, len);
094            pos += len;
095            return;
096        }
097
098        if (pos > 0) {
099            final int size = BUF_SIZE - pos;
100            System.arraycopy(b, off, buffer, pos, size);
101            process(buffer, 0);
102            off += size;
103        }
104
105        final int limit = end - BUF_SIZE;
106        while (off <= limit) {
107            process(b, off);
108            off += BUF_SIZE;
109        }
110
111        if (off < end) {
112            pos = end - off;
113            System.arraycopy(b, off, buffer, 0, pos);
114        }
115    }
116
117    @Override
118    public long getValue() {
119        int hash;
120        if (totalLen > BUF_SIZE) {
121            hash =
122                rotateLeft(state[0],  1) +
123                rotateLeft(state[1],  7) +
124                rotateLeft(state[2], 12) +
125                rotateLeft(state[3], 18);
126        } else {
127            hash = state[2] + PRIME5;
128        }
129        hash += totalLen;
130
131        int idx = 0;
132        final int limit = pos - 4;
133        for (; idx <= limit; idx += 4) {
134            hash = rotateLeft(hash + getInt(buffer, idx) * PRIME3, 17) * PRIME4;
135        }
136        while (idx < pos) {
137            hash = rotateLeft(hash + (buffer[idx++] & 0xff) * PRIME5, 11) * PRIME1;
138        }
139
140        hash ^= hash >>> 15;
141        hash *= PRIME2;
142        hash ^= hash >>> 13;
143        hash *= PRIME3;
144        hash ^= hash >>> 16;
145        return hash & 0xffffffffL;
146    }
147
148    private static int getInt(final byte[] buffer, final int idx) {
149        return (int) (fromLittleEndian(buffer, idx, 4) & 0xffffffffL);
150    }
151
152    private void initializeState() {
153        state[0] = seed + PRIME1 + PRIME2;
154        state[1] = seed + PRIME2;
155        state[2] = seed;
156        state[3] = seed - PRIME1;
157    }
158
159    private void process(final byte[] b, final int offset) {
160        // local shadows for performance
161        int s0 = state[0];
162        int s1 = state[1];
163        int s2 = state[2];
164        int s3 = state[3];
165
166        s0 = rotateLeft(s0 + getInt(b, offset) * PRIME2, ROTATE_BITS) * PRIME1;
167        s1 = rotateLeft(s1 + getInt(b, offset + 4) * PRIME2, ROTATE_BITS) * PRIME1;
168        s2 = rotateLeft(s2 + getInt(b, offset + 8) * PRIME2, ROTATE_BITS) * PRIME1;
169        s3 = rotateLeft(s3 + getInt(b, offset + 12) * PRIME2, ROTATE_BITS) * PRIME1;
170
171        state[0] = s0;
172        state[1] = s1;
173        state[2] = s2;
174        state[3] = s3;
175
176        pos = 0;
177    }
178}