/*
* Copyright (c) [2016] [ <ether.camp> ]
* This file is part of the ethereumJ library.
*
* The ethereumJ library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The ethereumJ library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>.
*/
package org.ethereum.net.shh;
import java.util.BitSet;
/**
* Created by Anton Nashatyrev on 24.09.2015.
*/
public class BloomFilter implements Cloneable {
private static final int BITS_PER_BLOOM = 3;
private static final int BLOOM_BYTES = 64;
BitSet mask = new BitSet(BLOOM_BYTES * 8);
int[] counters = new int[BLOOM_BYTES * 8];
private BloomFilter() {
}
public static BloomFilter createNone() {
return new BloomFilter();
}
public static BloomFilter createAll() {
BloomFilter bloomFilter = new BloomFilter();
bloomFilter.mask.set(0, bloomFilter.mask.length());
return bloomFilter;
}
public BloomFilter(Topic topic) {
addTopic(topic);
}
public BloomFilter(byte[] bloomMask) {
if (bloomMask.length != BLOOM_BYTES) throw new RuntimeException("Invalid bloom filter array length: " + bloomMask.length);
mask = BitSet.valueOf(bloomMask);
}
private void incCounters(BitSet bs) {
int idx = -1;
while(true) {
idx = bs.nextSetBit(idx + 1);
if (idx < 0) break;
counters[idx]++;
}
}
private void decCounters(BitSet bs) {
int idx = -1;
while(true) {
idx = bs.nextSetBit(idx + 1);
if (idx < 0) break;
if (counters[idx] > 0) counters[idx]--;
}
}
private BitSet getTopicMask(Topic topic) {
BitSet topicMask = new BitSet(BLOOM_BYTES * 8);
for (int i = 0; i < BITS_PER_BLOOM; i++) {
int x = topic.getBytes()[i] & 0xFF;
if ((topic.getBytes()[BITS_PER_BLOOM] & (1 << i)) != 0) {
x += 256;
}
topicMask.set(x);
}
return topicMask;
}
public void addTopic(Topic topic) {
BitSet topicMask = getTopicMask(topic);
incCounters(topicMask);
mask.or(topicMask);
}
public void removeTopic(Topic topic) {
BitSet topicMask = getTopicMask(topic);
decCounters(topicMask);
int idx = -1;
while(true) {
idx = topicMask.nextSetBit(idx + 1);
if (idx < 0) break;
if (counters[idx] == 0) mask.clear(idx);
}
}
public boolean hasTopic(Topic topic) {
BitSet m = new BloomFilter(topic).mask;
BitSet m1 = (BitSet) m.clone();
m1.and(mask);
return m1.equals(m);
}
public byte[] toBytes() {
byte[] ret = new byte[BLOOM_BYTES];
byte[] bytes = mask.toByteArray();
System.arraycopy(bytes, 0, ret, 0, bytes.length);
return ret;
}
@Override
public boolean equals(Object obj) {
return obj instanceof BloomFilter && mask.equals(((BloomFilter) obj).mask);
}
@Override
protected BloomFilter clone() throws CloneNotSupportedException {
try {
return (BloomFilter) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}