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); } }