package lbms.plugins.mldht.kad;
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedSet;
import lbms.plugins.mldht.kad.DHT.LogLevel;
/**
* @author The_8472, Damokles
*
*/
public class PopulationEstimator {
static final int KEYSPACE_BITS = Key.SHA1_HASH_LENGTH * 8;
static final double KEYSPACE_SIZE = Math.pow(2, KEYSPACE_BITS);
// try to close in to a rough estimate fast
static final double DISTANCE_INITIAL_WEIGHT = 0.1;
static final int INITIAL_WEIGHT_COUNT = 45;
// apply less weight to individual estimates later on
static final double DISTANCE_WEIGHT = 0.03;
private double averageNodeDistanceExp2 = KEYSPACE_BITS;
private int updateCount = 0;
private List<PopulationListener> listeners = new ArrayList<PopulationListener>(1);
private static final int MAX_RECENT_LOOKUP_CACHE_SIZE = 40;
private Deque<Key> recentlySeenPrefixes = new LinkedList<Key>();
public long getEstimate () {
return (long) (KEYSPACE_SIZE / Math.pow(2, averageNodeDistanceExp2));
}
double getRawDistanceEstimate() {
return averageNodeDistanceExp2;
}
void setInitialRawDistanceEstimate(double initialValue) {
averageNodeDistanceExp2 = initialValue;
if(averageNodeDistanceExp2 > KEYSPACE_BITS)
averageNodeDistanceExp2 = KEYSPACE_BITS;
}
public void update (SortedSet<Key> neighbors) {
if(neighbors.size() < 2)
return;
DHT.log("Estimator: new node group of "+neighbors.size(), LogLevel.Debug);
Key prefix = Key.getCommonPrefix(neighbors);
synchronized (recentlySeenPrefixes)
{
for(Key oldPrefix : recentlySeenPrefixes)
{
if(prefix.isPrefixOf(oldPrefix) || oldPrefix.isPrefixOf(prefix))
{
/*
* displace old entry, narrower entries will also replace
* wider ones, to clean out accidents like prefixes covering
* huge fractions of the keyspace
*/
recentlySeenPrefixes.remove(oldPrefix);
recentlySeenPrefixes.addLast(prefix);
return;
}
}
// no match found => add
recentlySeenPrefixes.addLast(prefix);
if(recentlySeenPrefixes.size() > MAX_RECENT_LOOKUP_CACHE_SIZE)
recentlySeenPrefixes.removeFirst();
}
Key previous = null;
for (Key entry : neighbors) {
if (previous == null) {
previous = entry;
continue;
}
byte[] rawDistance = previous.distance(entry).getHash();
double distance = 0;
int nonZeroBytes = 0;
for (int i = 0; i < Key.SHA1_HASH_LENGTH; i++) {
if (rawDistance[i] == 0) {
continue;
}
if (nonZeroBytes == 4) {
break;
}
nonZeroBytes++;
distance += (rawDistance[i] & 0xFF)
* Math.pow(2, KEYSPACE_BITS - (i + 1) * 8 - 1);
}
/*
* weighted average of the exponents (since single results can be
* off by several orders of magnitude -> logarithm dampens that
* exponentially)
*/
distance = Math.log(distance)/Math.log(2);
double weight;
if (updateCount < INITIAL_WEIGHT_COUNT)
weight = DISTANCE_INITIAL_WEIGHT;
else
weight = DISTANCE_WEIGHT;
synchronized (PopulationEstimator.class)
{
averageNodeDistanceExp2 = distance * weight + averageNodeDistanceExp2 * (1. - weight);
}
updateCount++;
DHT.log("Estimator: distance update #"+updateCount+": " + distance + " avg:" + averageNodeDistanceExp2, LogLevel.Debug);
previous = entry;
}
DHT.log("Estimator: new estimate:"+getEstimate(), LogLevel.Info);
fireUpdateEvent();
}
public void addListener (PopulationListener l) {
listeners.add(l);
}
public void removeListener (PopulationListener l) {
listeners.remove(l);
}
private void fireUpdateEvent () {
long estimated = getEstimate();
for (int i = 0; i < listeners.size(); i++) {
listeners.get(i).populationUpdated(estimated);
}
}
}