package net.i2p.router.networkdb.kademlia;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
import net.i2p.data.Hash;
import net.i2p.data.router.RouterInfo;
import net.i2p.kademlia.KBucketSet;
import net.i2p.kademlia.SelectionCollector;
import net.i2p.router.RouterContext;
import net.i2p.router.util.HashDistance;
import net.i2p.util.Log;
/**
* Mostly unused, see overrides in FloodfillPeerSelector
*/
class PeerSelector {
protected final Log _log;
protected final RouterContext _context;
public PeerSelector(RouterContext ctx) {
_context = ctx;
_log = _context.logManager().getLog(getClass());
}
/**
* UNUSED - See FloodfillPeerSelector override
* Search through the kbucket set to find the most reliable peers close to the
* given key, skipping all of the ones already checked
* List will not include our own hash.
*
* @return ordered list of Hash objects
*/
List<Hash> selectMostReliablePeers(Hash key, int numClosest, Set<Hash> alreadyChecked, KBucketSet<Hash> kbuckets) {
// get the peers closest to the key
return selectNearestExplicit(key, numClosest, alreadyChecked, kbuckets);
}
/**
* Ignore KBucket ordering and do the XOR explicitly per key. Runs in O(n*log(n))
* time (n=routing table size with c ~ 32 xor ops). This gets strict ordering
* on closest
* List will not include our own hash.
*
* @return List of Hash for the peers selected, ordered by bucket (but intra bucket order is not defined)
*/
List<Hash> selectNearestExplicit(Hash key, int maxNumRouters, Set<Hash> peersToIgnore, KBucketSet<Hash> kbuckets) {
//if (true)
return selectNearestExplicitThin(key, maxNumRouters, peersToIgnore, kbuckets);
/******
if (peersToIgnore == null)
peersToIgnore = new HashSet(1);
peersToIgnore.add(_context.routerHash());
Set allHashes = kbuckets.getAll(peersToIgnore);
removeFailingPeers(allHashes);
Map diffMap = new HashMap(allHashes.size());
for (Iterator iter = allHashes.iterator(); iter.hasNext(); ) {
Hash cur = (Hash)iter.next();
BigInteger diff = getDistance(key, cur);
diffMap.put(diff, cur);
}
// n*log(n)
Map sortedMap = new TreeMap(diffMap);
List peerHashes = new ArrayList(maxNumRouters);
for (Iterator iter = sortedMap.values().iterator(); iter.hasNext(); ) {
if (peerHashes.size() >= maxNumRouters) break;
peerHashes.add(iter.next());
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Searching for " + maxNumRouters + " peers close to " + key + ": "
+ peerHashes + " (not including " + peersToIgnore + ") [allHashes.size = "
+ allHashes.size() + "]");
return peerHashes;
******/
}
/**
* UNUSED - See FloodfillPeerSelector override
* Ignore KBucket ordering and do the XOR explicitly per key. Runs in O(n*log(n))
* time (n=routing table size with c ~ 32 xor ops). This gets strict ordering
* on closest
* List will not include our own hash.
*
* @return List of Hash for the peers selected, ordered by bucket (but intra bucket order is not defined)
*/
List<Hash> selectNearestExplicitThin(Hash key, int maxNumRouters, Set<Hash> peersToIgnore, KBucketSet<Hash> kbuckets) {
if (peersToIgnore == null)
peersToIgnore = new HashSet<Hash>(1);
peersToIgnore.add(_context.routerHash());
MatchSelectionCollector matches = new MatchSelectionCollector(key, peersToIgnore);
kbuckets.getAll(matches);
List<Hash> rv = matches.get(maxNumRouters);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Searching for " + maxNumRouters + " peers close to " + key + ": "
+ rv + " (not including " + peersToIgnore + ") [allHashes.size = "
+ matches.size() + "]");
return rv;
}
/** UNUSED */
private class MatchSelectionCollector implements SelectionCollector<Hash> {
private final TreeMap<BigInteger, Hash> _sorted;
private final Hash _key;
private final Set<Hash> _toIgnore;
private int _matches;
public MatchSelectionCollector(Hash key, Set<Hash> toIgnore) {
_key = key;
_sorted = new TreeMap<BigInteger, Hash>();
_toIgnore = toIgnore;
}
public void add(Hash entry) {
// deadlock seen here, and we don't mark profiles failing anymore
//if (_context.profileOrganizer().isFailing(entry))
// return;
if (_toIgnore.contains(entry))
return;
RouterInfo info = _context.netDb().lookupRouterInfoLocally(entry);
if (info == null)
return;
if (info.isHidden())
return;
BigInteger diff = HashDistance.getDistance(_key, entry);
_sorted.put(diff, entry);
_matches++;
}
/** get the first $howMany entries matching */
public List<Hash> get(int howMany) {
List<Hash> rv = new ArrayList<Hash>(howMany);
for (int i = 0; i < howMany; i++) {
if (_sorted.isEmpty())
break;
rv.add(_sorted.remove(_sorted.firstKey()));
}
return rv;
}
public int size() { return _matches; }
}
/**
* strip out all of the peers that are failing
*
*/
/********
private void removeFailingPeers(Set peerHashes) {
List failing = null;
for (Iterator iter = peerHashes.iterator(); iter.hasNext(); ) {
Hash cur = (Hash)iter.next();
if (_context.profileOrganizer().isFailing(cur)) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Peer " + cur.toBase64() + " is failing, don't include them in the peer selection");
if (failing == null)
failing = new ArrayList(4);
failing.add(cur);
} else if (_context.profileOrganizer().peerSendsBadReplies(cur)) {
if (true) {
_log.warn("Peer " + cur.toBase64() + " sends us bad replies (but we still query them)");
} else {
if (failing == null)
failing = new ArrayList(4);
failing.add(cur);
if (_log.shouldLog(Log.WARN)) {
PeerProfile profile = _context.profileOrganizer().getProfile(cur);
if (profile != null) {
RateStat invalidReplyRateStat = profile.getDBHistory().getInvalidReplyRate();
Rate invalidReplyRate = invalidReplyRateStat.getRate(60*60*1000l);
_log.warn("Peer " + cur.toBase64() + " sends us bad replies: current hour: "
+ invalidReplyRate.getCurrentEventCount() + " and last hour: "
+ invalidReplyRate.getLastEventCount() + ":\n" + invalidReplyRate.toString());
}
}
}
}
}
if (failing != null)
peerHashes.removeAll(failing);
}
**********/
/**
* UNUSED - See FloodfillPeerSelector override
* Generic KBucket filtering to find the hashes close to a key, regardless of other considerations.
* This goes through the kbuckets, starting with the key's location, moving towards us, and then away from the
* key's location's bucket, selecting peers until we have numClosest.
* List MAY INCLUDE our own router - add to peersToIgnore if you don't want
*
* @param key the original key (NOT the routing key)
* @param peersToIgnore can be null
* @return List of Hash for the peers selected, ordered by bucket (but intra bucket order is not defined)
*/
List<Hash> selectNearest(Hash key, int maxNumRouters, Set<Hash> peersToIgnore, KBucketSet<Hash> kbuckets) {
// sure, this may not be exactly correct per kademlia (peers on the border of a kbucket in strict kademlia
// would behave differently) but I can see no reason to keep around an /additional/ more complicated algorithm.
// later if/when selectNearestExplicit gets costly, we may revisit this (since kbuckets let us cache the distance()
// into a simple bucket selection algo + random select rather than an n*log(n) op)
return selectNearestExplicit(key, maxNumRouters, peersToIgnore, kbuckets);
}
}