/* Copyright 2016 Mark Gunlogson 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 com.github.mgunlogson.cuckoofilter4j; import java.io.IOException; import static com.google.common.base.Preconditions.checkNotNull; import java.io.ObjectInputStream; import java.io.Serializable; import java.security.SecureRandom; import java.util.Objects; import javax.annotation.Nullable; import com.github.mgunlogson.cuckoofilter4j.Utils.Algorithm; import com.google.common.hash.Funnel; import com.google.common.hash.HashCode; import com.google.common.hash.HashFunction; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; import com.google.common.hash.xxHashFunction; /** * Serializable, salted wrapper class for Guava's HashFunctions exists because * Guava doesn't setup salt and seed automatically and because Guavas's * HashFunction is NOT serializable * * @author Mark Gunlogson * * @param <T> * type of item to hash */ final class SerializableSaltedHasher<T> implements Serializable { /** */ private static final long serialVersionUID = 1L; private final long seedNSalt;// provides some protection against collision // attacks private final long addlSipSeed; private final Algorithm alg; private transient HashFunction hasher; private final Funnel<? super T> funnel; SerializableSaltedHasher(long seedNSalt, long addlSipSeed, Funnel<? super T> funnel, Algorithm alg) { checkNotNull(alg); checkNotNull(funnel); this.alg = alg; this.funnel = funnel; this.seedNSalt = seedNSalt; this.addlSipSeed = addlSipSeed; hasher = configureHash(alg, seedNSalt, addlSipSeed); } static <T> SerializableSaltedHasher<T> create(int hashBitsNeeded, Funnel<? super T> funnel) { if (hashBitsNeeded > 64) return create(Algorithm.Murmur3_128, funnel); return create(Algorithm.xxHash64, funnel); } static <T> SerializableSaltedHasher<T> create(Algorithm alg, Funnel<? super T> funnel) { checkNotNull(alg); checkNotNull(funnel); SecureRandom randomer = new SecureRandom(); long seedNSalt = randomer.nextLong(); long addlSipSeed = randomer.nextLong(); return new SerializableSaltedHasher<>(seedNSalt, addlSipSeed, funnel, alg); } private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { // default deserialization ois.defaultReadObject(); // not serializable so we rebuild here hasher = configureHash(alg, seedNSalt, addlSipSeed); } private static HashFunction configureHash(Algorithm alg, long seedNSalt, long addlSipSeed) { switch (alg) { case xxHash64: return new xxHashFunction(seedNSalt); case Murmur3_128: return Hashing.murmur3_128((int) seedNSalt); case Murmur3_32: return Hashing.murmur3_32((int) seedNSalt); case sha256: return Hashing.sha1(); case sipHash24: return Hashing.sipHash24(seedNSalt, addlSipSeed); default: throw new IllegalArgumentException("Invalid Enum Hashing Algorithm???"); } } HashCode hashObj(T object) { Hasher hashInst = hasher.newHasher(); hashInst.putObject(object, funnel); hashInst.putLong(seedNSalt); return hashInst.hash(); } /** * hashes the object with an additional salt. For purpose of the cuckoo * filter, this is used when the hash generated for an item is all zeros. * All zeros is the same as an empty bucket, so obviously it's not a valid * tag. */ HashCode hashObjWithSalt(T object, int moreSalt) { Hasher hashInst = hasher.newHasher(); hashInst.putObject(object, funnel); hashInst.putLong(seedNSalt); hashInst.putInt(moreSalt); return hashInst.hash(); } int codeBitSize() { return hasher.bits(); } @Override public boolean equals(@Nullable Object object) { if (object == this) { return true; } if (object instanceof SerializableSaltedHasher) { SerializableSaltedHasher<?> that = (SerializableSaltedHasher<?>) object; return this.seedNSalt == that.seedNSalt && this.alg.equals(that.alg) && this.funnel.equals(that.funnel) && this.addlSipSeed == that.addlSipSeed; } return false; } @Override public int hashCode() { return Objects.hash(seedNSalt, alg, funnel, addlSipSeed); } public SerializableSaltedHasher<T> copy() { return new SerializableSaltedHasher<>(seedNSalt, addlSipSeed, funnel, alg); } }