1) use cachedXor to cut down on the, uh, xor-ing (which involves at least one new byte[32])
2) implement an optimized 'should contain' algorithm, rather than being a wuss and building + comparing a BigInteger of the xor. 3) more unit tests this stuff is called a *lot*, since we need to pick what bucket things go in all the time.
This commit is contained in:
@ -11,6 +11,9 @@ package net.i2p.router.networkdb.kademlia;
|
||||
import java.math.BigInteger;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Iterator;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
@ -20,20 +23,21 @@ import net.i2p.util.RandomSource;
|
||||
|
||||
class KBucketImpl implements KBucket {
|
||||
private Log _log;
|
||||
private Set _entries; // PeerInfo structures
|
||||
/** set of Hash objects for the peers in the kbucket */
|
||||
private Set _entries;
|
||||
/** we center the kbucket set on the given hash, and derive distances from this */
|
||||
private Hash _local;
|
||||
private int _begin; // if any bits equal or higher to this bit (in big endian order),
|
||||
private int _end; // and no values higher than this bit (inclusive), include
|
||||
private BigInteger _lowerBounds; // lowest distance allowed from local
|
||||
private BigInteger _upperBounds; // one higher than the highest distance allowed from local
|
||||
private int _size; // integer value of the number of bits that can fit between lower and upper bounds
|
||||
/** include if any bits equal or higher to this bit (in big endian order) */
|
||||
private int _begin;
|
||||
/** include if no bits higher than this bit (inclusive) are set */
|
||||
private int _end;
|
||||
private I2PAppContext _context;
|
||||
|
||||
public KBucketImpl(I2PAppContext context, Hash local) {
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(KBucketImpl.class);
|
||||
_entries = new HashSet();
|
||||
_local = local;
|
||||
setLocal(local);
|
||||
}
|
||||
|
||||
public int getRangeBegin() { return _begin; }
|
||||
@ -41,25 +45,6 @@ class KBucketImpl implements KBucket {
|
||||
public void setRange(int lowOrderBitLimit, int highOrderBitLimit) {
|
||||
_begin = lowOrderBitLimit;
|
||||
_end = highOrderBitLimit;
|
||||
if (_begin == 0)
|
||||
_lowerBounds = BigInteger.ZERO;
|
||||
else
|
||||
_lowerBounds = BigInteger.ZERO.setBit(_begin);
|
||||
_upperBounds = BigInteger.ZERO.setBit(_end);
|
||||
BigInteger diff = _upperBounds.subtract(_lowerBounds);
|
||||
_size = diff.bitLength();
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
buf.append("Set range: ").append(lowOrderBitLimit).append(" through ").append(highOrderBitLimit).append('\n');
|
||||
buf.append("Local key, lowest allowed key, and highest allowed key: \n");
|
||||
Hash low = getRangeBeginKey();
|
||||
Hash high = getRangeEndKey();
|
||||
if ( (_local == null) || (_local.getData() == null) )
|
||||
buf.append(toString(Hash.FAKE_HASH.getData())).append('\n');
|
||||
else
|
||||
buf.append(toString(_local.getData())).append('\n');
|
||||
buf.append(toString(low.getData())).append('\n');
|
||||
buf.append(toString(high.getData()));
|
||||
//_log.debug(buf.toString());
|
||||
}
|
||||
public int getKeyCount() {
|
||||
synchronized (_entries) {
|
||||
@ -68,13 +53,41 @@ class KBucketImpl implements KBucket {
|
||||
}
|
||||
|
||||
public Hash getLocal() { return _local; }
|
||||
public void setLocal(Hash local) { _local = local; }
|
||||
private void setLocal(Hash local) {
|
||||
_local = local;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Local hash reset to " + (local == null ? "null" : DataHelper.toHexString(local.getData())));
|
||||
}
|
||||
|
||||
private byte[] distanceFromLocal(Hash key) {
|
||||
return DataHelper.xor(key.getData(), _local.getData());
|
||||
if (key == null)
|
||||
throw new IllegalArgumentException("Null key for distanceFromLocal?");
|
||||
return _local.cachedXor(key);
|
||||
}
|
||||
|
||||
public boolean shouldContain(Hash key) {
|
||||
byte distance[] = distanceFromLocal(key);
|
||||
// rather than use a BigInteger and compare, we do it manually by
|
||||
// checking the bits
|
||||
boolean tooLarge = distanceIsTooLarge(distance);
|
||||
if (tooLarge) {
|
||||
if (false && _log.shouldLog(Log.DEBUG))
|
||||
_log.debug("too large [" + _begin + "-->" + _end + "] "
|
||||
+ "\nLow: " + BigInteger.ZERO.setBit(_begin).toString(16)
|
||||
+ "\nCur: " + DataHelper.toHexString(distance)
|
||||
+ "\nHigh: " + BigInteger.ZERO.setBit(_end).toString(16));
|
||||
return false;
|
||||
}
|
||||
boolean tooSmall = distanceIsTooSmall(distance);
|
||||
if (tooSmall) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("too small [" + _begin + "-->" + _end + "] distance: " + DataHelper.toHexString(distance));
|
||||
return false;
|
||||
}
|
||||
// this bed is juuuuust right
|
||||
return true;
|
||||
|
||||
/*
|
||||
// woohah, incredibly excessive object creation! whee!
|
||||
BigInteger kv = new BigInteger(1, distanceFromLocal(key));
|
||||
int lowComp = kv.compareTo(_lowerBounds);
|
||||
@ -84,6 +97,90 @@ class KBucketImpl implements KBucket {
|
||||
|
||||
if ( (lowComp >= 0) && (highComp < 0) ) return true;
|
||||
return false;
|
||||
*/
|
||||
}
|
||||
|
||||
private final boolean distanceIsTooLarge(byte distance[]) {
|
||||
int upperLimitBit = Hash.HASH_LENGTH*8 - _end;
|
||||
// It is too large if there are any bits set before the upperLimitBit
|
||||
int upperLimitByte = upperLimitBit > 0 ? upperLimitBit / 8 : 0;
|
||||
|
||||
if (upperLimitBit <= 0)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < distance.length; i++) {
|
||||
if ( (i < upperLimitByte) && (distance[i] != 0x00) ) {
|
||||
// outright too large
|
||||
return true;
|
||||
} else {
|
||||
int upperVal = 1 << (upperLimitBit % 8);
|
||||
if (distance[i] > upperVal) {
|
||||
// still too large, but close
|
||||
return true;
|
||||
} else if (distance[i] == upperVal) {
|
||||
// ok, it *may* equal the upper limit,
|
||||
// if the rest of the bytes are 0
|
||||
for (int j = i+1; j < distance.length; i++) {
|
||||
if (distance[j] != 0x00) {
|
||||
// nope
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// w00t, the rest is made of 0x00 bytes, so it
|
||||
// exactly matches the upper limit. kooky, very improbable,
|
||||
// but possible
|
||||
return false;
|
||||
} else {
|
||||
// no bits set before or at the upper limit, so its
|
||||
// definitely not too large
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
_log.log(Log.CRIT, "wtf, gravity broke: distance=" + DataHelper.toHexString(distance)
|
||||
+ ", end=" + _end, new Exception("moo"));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the distance too small?
|
||||
*
|
||||
*/
|
||||
private final boolean distanceIsTooSmall(byte distance[]) {
|
||||
int beginBit = Hash.HASH_LENGTH*8 - _begin;
|
||||
// It is too small if there are no bits set before the beginBit
|
||||
int beginByte = beginBit > 0 ? beginBit / 8 : 0;
|
||||
|
||||
if (beginByte >= distance.length) {
|
||||
if (_begin == 0)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < distance.length; i++) {
|
||||
if ( (i < beginByte) && (distance[i] != 0x00) ) {
|
||||
return false;
|
||||
} else {
|
||||
if (i != beginByte) {
|
||||
// zero value and too early... keep going
|
||||
continue;
|
||||
} else {
|
||||
int beginVal = 1 << (_begin % 8);
|
||||
if (distance[i] >= beginVal) {
|
||||
return false;
|
||||
} else {
|
||||
// no bits set prior to the beginVal
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_log.log(Log.CRIT, "wtf, gravity broke! distance=" + DataHelper.toHexString(distance)
|
||||
+ " begin=" + _begin
|
||||
+ " beginBit=" + beginBit
|
||||
+ " beginByte=" + beginByte, new Exception("moo"));
|
||||
return true;
|
||||
}
|
||||
|
||||
public Set getEntries() {
|
||||
@ -127,8 +224,8 @@ class KBucketImpl implements KBucket {
|
||||
*
|
||||
*/
|
||||
public Hash generateRandomKey() {
|
||||
BigInteger variance = new BigInteger(_size-1, _context.random());
|
||||
variance = variance.add(_lowerBounds);
|
||||
BigInteger variance = new BigInteger((_end-_begin)-1, _context.random());
|
||||
variance = variance.setBit(_begin);
|
||||
//_log.debug("Random variance for " + _size + " bits: " + variance);
|
||||
byte data[] = variance.toByteArray();
|
||||
byte hash[] = new byte[Hash.HASH_LENGTH];
|
||||
@ -150,7 +247,7 @@ class KBucketImpl implements KBucket {
|
||||
}
|
||||
|
||||
public Hash getRangeBeginKey() {
|
||||
BigInteger lowerBounds = _lowerBounds;
|
||||
BigInteger lowerBounds = getLowerBounds();
|
||||
if ( (_local != null) && (_local.getData() != null) ) {
|
||||
lowerBounds = lowerBounds.xor(new BigInteger(1, _local.getData()));
|
||||
}
|
||||
@ -167,7 +264,7 @@ class KBucketImpl implements KBucket {
|
||||
}
|
||||
|
||||
public Hash getRangeEndKey() {
|
||||
BigInteger upperBounds = _upperBounds;
|
||||
BigInteger upperBounds = getUpperBounds();
|
||||
if ( (_local != null) && (_local.getData() != null) ) {
|
||||
upperBounds = upperBounds.xor(new BigInteger(1, _local.getData()));
|
||||
}
|
||||
@ -182,6 +279,16 @@ class KBucketImpl implements KBucket {
|
||||
return key;
|
||||
}
|
||||
|
||||
private BigInteger getUpperBounds() {
|
||||
return BigInteger.ZERO.setBit(_end);
|
||||
}
|
||||
private BigInteger getLowerBounds() {
|
||||
if (_begin == 0)
|
||||
return BigInteger.ZERO;
|
||||
else
|
||||
return BigInteger.ZERO.setBit(_begin);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
buf.append("KBucketImpl: ");
|
||||
@ -198,8 +305,8 @@ class KBucketImpl implements KBucket {
|
||||
buf.append(toString(getRangeBeginKey().getData())).append('\n');
|
||||
buf.append(toString(getRangeEndKey().getData())).append('\n');
|
||||
buf.append("Low and high deltas:\n");
|
||||
buf.append(_lowerBounds.toString(2)).append('\n');
|
||||
buf.append(_upperBounds.toString(2)).append('\n');
|
||||
buf.append(getLowerBounds().toString(2)).append('\n');
|
||||
buf.append(getUpperBounds().toString(2)).append('\n');
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@ -210,10 +317,27 @@ class KBucketImpl implements KBucket {
|
||||
public static void main(String args[]) {
|
||||
testRand2();
|
||||
testRand();
|
||||
testLimits();
|
||||
|
||||
try { Thread.sleep(10000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
|
||||
private static void testLimits() {
|
||||
int low = 1;
|
||||
int high = 3;
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(KBucketImpl.class);
|
||||
KBucketImpl bucket = new KBucketImpl(I2PAppContext.getGlobalContext(), Hash.FAKE_HASH);
|
||||
bucket.setRange(low, high);
|
||||
Hash lowerBoundKey = bucket.getRangeBeginKey();
|
||||
Hash upperBoundKey = bucket.getRangeEndKey();
|
||||
boolean okLow = bucket.shouldContain(lowerBoundKey);
|
||||
boolean okHigh = bucket.shouldContain(upperBoundKey);
|
||||
if (okLow && okHigh)
|
||||
log.debug("Limit test ok");
|
||||
else
|
||||
log.error("Limit test failed! ok low? " + okLow + " ok high? " + okHigh);
|
||||
}
|
||||
|
||||
private static void testRand() {
|
||||
StringBuffer buf = new StringBuffer(2048);
|
||||
int low = 1;
|
||||
@ -223,14 +347,20 @@ class KBucketImpl implements KBucket {
|
||||
bucket.setRange(low, high);
|
||||
Hash lowerBoundKey = bucket.getRangeBeginKey();
|
||||
Hash upperBoundKey = bucket.getRangeEndKey();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
Hash rnd = bucket.generateRandomKey();
|
||||
//buf.append(toString(rnd.getData())).append('\n');
|
||||
boolean ok = bucket.shouldContain(rnd);
|
||||
if (!ok) {
|
||||
byte diff[] = DataHelper.xor(rnd.getData(), bucket.getLocal().getData());
|
||||
byte diff[] = bucket.getLocal().cachedXor(rnd);
|
||||
BigInteger dv = new BigInteger(1, diff);
|
||||
log.error("WTF! bucket doesn't want: \n" + toString(rnd.getData()) + "\nDelta: \n" + toString(diff) + "\nDelta val: \n" + dv.toString(2) + "\nBucket: \n"+bucket, new Exception("WTF"));
|
||||
//log.error("WTF! bucket doesn't want: \n" + toString(rnd.getData())
|
||||
// + "\nDelta: \n" + toString(diff) + "\nDelta val: \n" + dv.toString(2)
|
||||
// + "\nBucket: \n"+bucket, new Exception("WTF"));
|
||||
log.error("wtf, bucket doesnt want a key that it generated. i == " + i);
|
||||
log.error("\nLow: " + DataHelper.toHexString(bucket.getRangeBeginKey().getData())
|
||||
+ "\nVal: " + DataHelper.toHexString(rnd.getData())
|
||||
+ "\nHigh:" + DataHelper.toHexString(bucket.getRangeEndKey().getData()));
|
||||
try { Thread.sleep(1000); } catch (Exception e) {}
|
||||
System.exit(0);
|
||||
} else {
|
||||
@ -238,7 +368,7 @@ class KBucketImpl implements KBucket {
|
||||
}
|
||||
//_log.info("Low/High:\n" + toString(lowBounds.toByteArray()) + "\n" + toString(highBounds.toByteArray()));
|
||||
}
|
||||
log.info("Passed 100 random key generations against the null hash");
|
||||
log.info("Passed 100,000 random key generations against the null hash");
|
||||
}
|
||||
|
||||
private static void testRand2() {
|
||||
@ -252,24 +382,31 @@ class KBucketImpl implements KBucket {
|
||||
bucket.setRange(low, high);
|
||||
Hash lowerBoundKey = bucket.getRangeBeginKey();
|
||||
Hash upperBoundKey = bucket.getRangeEndKey();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
Hash rnd = bucket.generateRandomKey();
|
||||
buf.append(toString(rnd.getData())).append('\n');
|
||||
//buf.append(toString(rnd.getData())).append('\n');
|
||||
boolean ok = bucket.shouldContain(rnd);
|
||||
if (!ok) {
|
||||
byte diff[] = DataHelper.xor(rnd.getData(), bucket.getLocal().getData());
|
||||
byte diff[] = bucket.getLocal().cachedXor(rnd);
|
||||
BigInteger dv = new BigInteger(1, diff);
|
||||
log.error("WTF! bucket doesn't want: \n" + toString(rnd.getData()) + "\nDelta: \n" + toString(diff) + "\nDelta val: \n" + dv.toString(2) + "\nBucket: \n"+bucket, new Exception("WTF"));
|
||||
//log.error("WTF! bucket doesn't want: \n" + toString(rnd.getData())
|
||||
// + "\nDelta: \n" + toString(diff) + "\nDelta val: \n" + dv.toString(2)
|
||||
// + "\nBucket: \n"+bucket, new Exception("WTF"));
|
||||
log.error("wtf, bucket doesnt want a key that it generated. i == " + i);
|
||||
log.error("\nLow: " + DataHelper.toHexString(bucket.getRangeBeginKey().getData())
|
||||
+ "\nVal: " + DataHelper.toHexString(rnd.getData())
|
||||
+ "\nHigh:" + DataHelper.toHexString(bucket.getRangeEndKey().getData()));
|
||||
try { Thread.sleep(1000); } catch (Exception e) {}
|
||||
System.exit(0);
|
||||
} else {
|
||||
//_log.debug("Ok, bucket wants: \n" + toString(rnd.getData()));
|
||||
}
|
||||
}
|
||||
log.info("Passed 1000 random key generations against a random hash\n" + buf.toString());
|
||||
log.info("Passed 100,000 random key generations against a random hash\n" + buf.toString());
|
||||
}
|
||||
|
||||
private final static String toString(byte b[]) {
|
||||
if (true) return DataHelper.toHexString(b);
|
||||
StringBuffer buf = new StringBuffer(b.length);
|
||||
for (int i = 0; i < b.length; i++) {
|
||||
buf.append(toString(b[i]));
|
||||
|
Reference in New Issue
Block a user