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.math.BigInteger;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.data.DataHelper;
|
import net.i2p.data.DataHelper;
|
||||||
@ -20,20 +23,21 @@ import net.i2p.util.RandomSource;
|
|||||||
|
|
||||||
class KBucketImpl implements KBucket {
|
class KBucketImpl implements KBucket {
|
||||||
private Log _log;
|
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 Hash _local;
|
||||||
private int _begin; // if any bits equal or higher to this bit (in big endian order),
|
/** include 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 int _begin;
|
||||||
private BigInteger _lowerBounds; // lowest distance allowed from local
|
/** include if no bits higher than this bit (inclusive) are set */
|
||||||
private BigInteger _upperBounds; // one higher than the highest distance allowed from local
|
private int _end;
|
||||||
private int _size; // integer value of the number of bits that can fit between lower and upper bounds
|
|
||||||
private I2PAppContext _context;
|
private I2PAppContext _context;
|
||||||
|
|
||||||
public KBucketImpl(I2PAppContext context, Hash local) {
|
public KBucketImpl(I2PAppContext context, Hash local) {
|
||||||
_context = context;
|
_context = context;
|
||||||
_log = context.logManager().getLog(KBucketImpl.class);
|
_log = context.logManager().getLog(KBucketImpl.class);
|
||||||
_entries = new HashSet();
|
_entries = new HashSet();
|
||||||
_local = local;
|
setLocal(local);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getRangeBegin() { return _begin; }
|
public int getRangeBegin() { return _begin; }
|
||||||
@ -41,25 +45,6 @@ class KBucketImpl implements KBucket {
|
|||||||
public void setRange(int lowOrderBitLimit, int highOrderBitLimit) {
|
public void setRange(int lowOrderBitLimit, int highOrderBitLimit) {
|
||||||
_begin = lowOrderBitLimit;
|
_begin = lowOrderBitLimit;
|
||||||
_end = highOrderBitLimit;
|
_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() {
|
public int getKeyCount() {
|
||||||
synchronized (_entries) {
|
synchronized (_entries) {
|
||||||
@ -68,13 +53,41 @@ class KBucketImpl implements KBucket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Hash getLocal() { return _local; }
|
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) {
|
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) {
|
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!
|
// woohah, incredibly excessive object creation! whee!
|
||||||
BigInteger kv = new BigInteger(1, distanceFromLocal(key));
|
BigInteger kv = new BigInteger(1, distanceFromLocal(key));
|
||||||
int lowComp = kv.compareTo(_lowerBounds);
|
int lowComp = kv.compareTo(_lowerBounds);
|
||||||
@ -84,6 +97,90 @@ class KBucketImpl implements KBucket {
|
|||||||
|
|
||||||
if ( (lowComp >= 0) && (highComp < 0) ) return true;
|
if ( (lowComp >= 0) && (highComp < 0) ) return true;
|
||||||
return false;
|
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() {
|
public Set getEntries() {
|
||||||
@ -127,8 +224,8 @@ class KBucketImpl implements KBucket {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public Hash generateRandomKey() {
|
public Hash generateRandomKey() {
|
||||||
BigInteger variance = new BigInteger(_size-1, _context.random());
|
BigInteger variance = new BigInteger((_end-_begin)-1, _context.random());
|
||||||
variance = variance.add(_lowerBounds);
|
variance = variance.setBit(_begin);
|
||||||
//_log.debug("Random variance for " + _size + " bits: " + variance);
|
//_log.debug("Random variance for " + _size + " bits: " + variance);
|
||||||
byte data[] = variance.toByteArray();
|
byte data[] = variance.toByteArray();
|
||||||
byte hash[] = new byte[Hash.HASH_LENGTH];
|
byte hash[] = new byte[Hash.HASH_LENGTH];
|
||||||
@ -150,7 +247,7 @@ class KBucketImpl implements KBucket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Hash getRangeBeginKey() {
|
public Hash getRangeBeginKey() {
|
||||||
BigInteger lowerBounds = _lowerBounds;
|
BigInteger lowerBounds = getLowerBounds();
|
||||||
if ( (_local != null) && (_local.getData() != null) ) {
|
if ( (_local != null) && (_local.getData() != null) ) {
|
||||||
lowerBounds = lowerBounds.xor(new BigInteger(1, _local.getData()));
|
lowerBounds = lowerBounds.xor(new BigInteger(1, _local.getData()));
|
||||||
}
|
}
|
||||||
@ -167,7 +264,7 @@ class KBucketImpl implements KBucket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Hash getRangeEndKey() {
|
public Hash getRangeEndKey() {
|
||||||
BigInteger upperBounds = _upperBounds;
|
BigInteger upperBounds = getUpperBounds();
|
||||||
if ( (_local != null) && (_local.getData() != null) ) {
|
if ( (_local != null) && (_local.getData() != null) ) {
|
||||||
upperBounds = upperBounds.xor(new BigInteger(1, _local.getData()));
|
upperBounds = upperBounds.xor(new BigInteger(1, _local.getData()));
|
||||||
}
|
}
|
||||||
@ -182,6 +279,16 @@ class KBucketImpl implements KBucket {
|
|||||||
return key;
|
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() {
|
public String toString() {
|
||||||
StringBuffer buf = new StringBuffer(1024);
|
StringBuffer buf = new StringBuffer(1024);
|
||||||
buf.append("KBucketImpl: ");
|
buf.append("KBucketImpl: ");
|
||||||
@ -198,8 +305,8 @@ class KBucketImpl implements KBucket {
|
|||||||
buf.append(toString(getRangeBeginKey().getData())).append('\n');
|
buf.append(toString(getRangeBeginKey().getData())).append('\n');
|
||||||
buf.append(toString(getRangeEndKey().getData())).append('\n');
|
buf.append(toString(getRangeEndKey().getData())).append('\n');
|
||||||
buf.append("Low and high deltas:\n");
|
buf.append("Low and high deltas:\n");
|
||||||
buf.append(_lowerBounds.toString(2)).append('\n');
|
buf.append(getLowerBounds().toString(2)).append('\n');
|
||||||
buf.append(_upperBounds.toString(2)).append('\n');
|
buf.append(getUpperBounds().toString(2)).append('\n');
|
||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,10 +317,27 @@ class KBucketImpl implements KBucket {
|
|||||||
public static void main(String args[]) {
|
public static void main(String args[]) {
|
||||||
testRand2();
|
testRand2();
|
||||||
testRand();
|
testRand();
|
||||||
|
testLimits();
|
||||||
|
|
||||||
try { Thread.sleep(10000); } catch (InterruptedException ie) {}
|
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() {
|
private static void testRand() {
|
||||||
StringBuffer buf = new StringBuffer(2048);
|
StringBuffer buf = new StringBuffer(2048);
|
||||||
int low = 1;
|
int low = 1;
|
||||||
@ -223,14 +347,20 @@ class KBucketImpl implements KBucket {
|
|||||||
bucket.setRange(low, high);
|
bucket.setRange(low, high);
|
||||||
Hash lowerBoundKey = bucket.getRangeBeginKey();
|
Hash lowerBoundKey = bucket.getRangeBeginKey();
|
||||||
Hash upperBoundKey = bucket.getRangeEndKey();
|
Hash upperBoundKey = bucket.getRangeEndKey();
|
||||||
for (int i = 0; i < 100; i++) {
|
for (int i = 0; i < 100000; i++) {
|
||||||
Hash rnd = bucket.generateRandomKey();
|
Hash rnd = bucket.generateRandomKey();
|
||||||
//buf.append(toString(rnd.getData())).append('\n');
|
//buf.append(toString(rnd.getData())).append('\n');
|
||||||
boolean ok = bucket.shouldContain(rnd);
|
boolean ok = bucket.shouldContain(rnd);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
byte diff[] = DataHelper.xor(rnd.getData(), bucket.getLocal().getData());
|
byte diff[] = bucket.getLocal().cachedXor(rnd);
|
||||||
BigInteger dv = new BigInteger(1, diff);
|
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) {}
|
try { Thread.sleep(1000); } catch (Exception e) {}
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
} else {
|
} else {
|
||||||
@ -238,7 +368,7 @@ class KBucketImpl implements KBucket {
|
|||||||
}
|
}
|
||||||
//_log.info("Low/High:\n" + toString(lowBounds.toByteArray()) + "\n" + toString(highBounds.toByteArray()));
|
//_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() {
|
private static void testRand2() {
|
||||||
@ -252,24 +382,31 @@ class KBucketImpl implements KBucket {
|
|||||||
bucket.setRange(low, high);
|
bucket.setRange(low, high);
|
||||||
Hash lowerBoundKey = bucket.getRangeBeginKey();
|
Hash lowerBoundKey = bucket.getRangeBeginKey();
|
||||||
Hash upperBoundKey = bucket.getRangeEndKey();
|
Hash upperBoundKey = bucket.getRangeEndKey();
|
||||||
for (int i = 0; i < 1000; i++) {
|
for (int i = 0; i < 100000; i++) {
|
||||||
Hash rnd = bucket.generateRandomKey();
|
Hash rnd = bucket.generateRandomKey();
|
||||||
buf.append(toString(rnd.getData())).append('\n');
|
//buf.append(toString(rnd.getData())).append('\n');
|
||||||
boolean ok = bucket.shouldContain(rnd);
|
boolean ok = bucket.shouldContain(rnd);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
byte diff[] = DataHelper.xor(rnd.getData(), bucket.getLocal().getData());
|
byte diff[] = bucket.getLocal().cachedXor(rnd);
|
||||||
BigInteger dv = new BigInteger(1, diff);
|
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) {}
|
try { Thread.sleep(1000); } catch (Exception e) {}
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
} else {
|
} else {
|
||||||
//_log.debug("Ok, bucket wants: \n" + toString(rnd.getData()));
|
//_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[]) {
|
private final static String toString(byte b[]) {
|
||||||
|
if (true) return DataHelper.toHexString(b);
|
||||||
StringBuffer buf = new StringBuffer(b.length);
|
StringBuffer buf = new StringBuffer(b.length);
|
||||||
for (int i = 0; i < b.length; i++) {
|
for (int i = 0; i < b.length; i++) {
|
||||||
buf.append(toString(b[i]));
|
buf.append(toString(b[i]));
|
||||||
|
Reference in New Issue
Block a user