/** * Adds the given <code>Contact</code> or updates it if it's already in our <code>RouteTable * </code> */ private synchronized void addLiveContactInfo(Contact node, DHTMessage message) { RouteTable routeTable = context.getRouteTable(); // If the Node is going to shutdown then don't bother // further than this. if (node.isShutdown()) { if (LOG.isInfoEnabled()) { LOG.info(node + " is going to shut down"); } synchronized (routeTable) { // Make sure there's an existing Contact in the RouteTable. // Otherwise don't bother! Contact existing = routeTable.get(node.getNodeID()); if (node.equals(existing)) { // Update the new Contact in the RouteTable and // mark it as shutdown // mark the existing contact as shutdown if its alive or // it will not be removed. if (existing.isAlive()) existing.shutdown(true); routeTable.add(node); node.shutdown(true); } } return; } // Ignore firewalled Nodes if (node.isFirewalled()) { if (LOG.isInfoEnabled()) { LOG.info(node + " is firewalled"); } return; } if (ContactUtils.isPrivateAddress(node)) { if (LOG.isInfoEnabled()) { LOG.info(node + " has a private address"); } return; } KUID nodeId = node.getNodeID(); if (context.isLocalNodeID(nodeId)) { // This is expected if there's a Node ID collision assert (message instanceof PingResponse) : "Expected a PingResponse but got a " + message.getClass() + " from " + message.getContact(); if (LOG.isInfoEnabled()) { LOG.info("Looks like our NodeID collides with " + node); } return; } if (StoreSettings.STORE_FORWARD_ENABLED.getValue()) { // Only do store forward if it is a new node in our routing table // (we are (re)connecting to the network) or a node that is reconnecting Contact existing = routeTable.get(nodeId); if (existing == null || existing.isDead() || existing.getInstanceID() != node.getInstanceID()) { // Store forward only if we're bootstrapped if (context.isBootstrapped()) { int k = KademliaSettings.REPLICATION_PARAMETER.getValue(); // we select the 2*k closest nodes in order to also check those values // where the local node is part of the k closest to the value but not part // of the k closest to the new joining node. Collection<Contact> nodes = routeTable.select(nodeId, 2 * k, SelectMode.ALL); // Are we one of the K nearest Nodes to the contact? if (containsNodeID(nodes, context.getLocalNodeID())) { if (LOG.isTraceEnabled()) { LOG.trace( "Node " + node + " is new or has changed his instanceID, will check for store forward!"); } forwardOrRemoveValues(node, existing, message); } } } } // Add the Node to our RouteTable or if it's // already there update its timeStamp and whatsoever routeTable.add(node); }
/** * Determines whether to remove, forward or to do nothing with the value that is associated with * the given valueId. */ private Operation getOperation(Contact node, Contact existing, KUID valueId) { // To avoid redundant STORE forward, a node only transfers a value // if it is the closest to the key or if its ID is closer than any // other ID (except the new closest one of course) // TODO: maybe relax this a little bit: what if we're not the closest // and the closest is stale? int k = KademliaSettings.REPLICATION_PARAMETER.getValue(); RouteTable routeTable = context.getRouteTable(); List<Contact> nodes = org.limewire.collection.CollectionUtils.toList( routeTable.select(valueId, k, SelectMode.ALL)); Contact closest = nodes.get(0); Contact furthest = nodes.get(nodes.size() - 1); if (LOG.isDebugEnabled()) { LOG.debug( MessageFormat.format( "node: {0}, existing: {1}, close nodes: {2}", node, existing, nodes)); } // StringBuilder sb = new StringBuilder(); // sb.append("ME: "+context.getLocalNode()+"\n"); // sb.append("Them: "+node).append("\n"); // sb.append("RT nearest: " + closest).append("\n"); // sb.append("RT furthest: " + furthest).append("\n"); // sb.append(CollectionUtils.toString(nodes)).append("\n"); // We store forward if: // #1 We're the nearest Node of the k-closest Nodes to // the given valueId // // #2 We're the second nearest of the k-closest Nodes to // the given valueId AND the other Node is the nearest. // In other words it changed its instance ID 'cause it // was offline for a short period of time or whatsoever. // (see also pre-condition(s) from where we're calling // this method) // // The first condition applies if the Node is new // and we're the closest Node. The second condition // applies if the Node has changed it's instanceId. // That means we're the second closest and since // the other Node has changed its instanceId we must // re-send the values if (context.isLocalNode(closest) || (node.equals(closest) && nodes.size() > 1 && context.isLocalNode(nodes.get(1)))) { KUID nodeId = node.getNodeID(); KUID furthestId = furthest.getNodeID(); // #3 The other Node must be equal to the furthest Node // or better if (nodeId.equals(furthestId) || nodeId.isNearerTo(valueId, furthestId)) { // sb.append("CONDITION B (FORWARD)").append("\n"); // sb.append("Local (from): " + context.getLocalNode()).append("\n"); // sb.append("Remote (to): " + node).append("\n"); // sb.append(CollectionUtils.toString(nodes)).append("\n"); // System.out.println(sb.toString()); if (LOG.isTraceEnabled()) { LOG.trace( "Node " + node + " is now close enough to a value and we are responsible for xfer"); } return Operation.FORWARD; } // We remove a value if: // #1 The value is stored at k Nodes // (i.e. the total number of Nodes in the DHT // is equal or greater than k. If the DHT has // less than k Nodes then there's no reason to // remove a value) // // #2 This Node is the furthest of the k-closest Nodes // // #3 The new Node isn't in our RouteTable yet. That means // adding it will push this Node out of the club of the // k-closest Nodes and makes it the (k+1)-closest Node. // // #4 The new Node is nearer to the given valueId then // the furthest away Node (we). } else if (nodes.size() >= k && context.isLocalNode(furthest) && (existing == null || existing.isDead())) { KUID nodeId = node.getNodeID(); KUID furthestId = furthest.getNodeID(); if (nodeId.isNearerTo(valueId, furthestId)) { // sb.append("CONDITION C").append("\n"); // sb.append("ME:").append(context.getLocalNode()).append("\n"); // sb.append("VALUE:").append(valueId).append("\n"); // sb.append("NODE:").append(node).append("\n"); // sb.append(CollectionUtils.toString(nodes)).append("\n"); // System.out.println(sb.toString()); return Operation.DELETE; } } return Operation.NOTHING; }
/** Returns a sub-view of the given Collection that will only return the first K elements */ public static <T> Collection<T> getCollection(Collection<T> c) { return getCollection(c, KademliaSettings.REPLICATION_PARAMETER.getValue()); }