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:
jrandom
2004-06-20 00:18:28 +00:00
committed by zzz
parent 3e3749f011
commit 6d84b8c02f

View File

@ -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]));