diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/KBucketImpl.java b/router/java/src/net/i2p/router/networkdb/kademlia/KBucketImpl.java index a24ca48ff..f88492789 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/KBucketImpl.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/KBucketImpl.java @@ -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]));