package kademlia.routing; import java.util.ArrayList; import java.util.List; import java.util.TreeSet; import kademlia.KadConfiguration; import kademlia.node.KeyComparator; import kademlia.node.Node; import kademlia.node.KademliaId; /** * Implementation of a Kademlia routing table * * @author Joshua Kissoon * @created 20140215 */ public class JKademliaRoutingTable implements KademliaRoutingTable { private final Node localNode; // The current node private transient KademliaBucket[] buckets; private transient KadConfiguration config; public JKademliaRoutingTable(Node localNode, KadConfiguration config) { this.localNode = localNode; this.config = config; /* Initialize all of the buckets to a specific depth */ this.initialize(); /* Insert the local node */ this.insert(localNode); } /** * Initialize the JKademliaRoutingTable to it's default state */ @Override public final void initialize() { this.buckets = new KademliaBucket[KademliaId.ID_LENGTH]; for (int i = 0; i < KademliaId.ID_LENGTH; i++) { buckets[i] = new JKademliaBucket(i, this.config); } } @Override public void setConfiguration(KadConfiguration config) { this.config = config; } /** * Adds a contact to the routing table based on how far it is from the LocalNode. * * @param c The contact to add */ @Override public synchronized final void insert(Contact c) { this.buckets[this.getBucketId(c.getNode().getNodeId())].insert(c); } /** * Adds a node to the routing table based on how far it is from the LocalNode. * * @param n The node to add */ @Override public synchronized final void insert(Node n) { this.buckets[this.getBucketId(n.getNodeId())].insert(n); } /** * Compute the bucket ID in which a given node should be placed; the bucketId is computed based on how far the node is away from the Local Node. * * @param nid The NodeId for which we want to find which bucket it belong to * * @return Integer The bucket ID in which the given node should be placed. */ @Override public final int getBucketId(KademliaId nid) { int bId = this.localNode.getNodeId().getDistance(nid) - 1; /* If we are trying to insert a node into it's own routing table, then the bucket ID will be -1, so let's just keep it in bucket 0 */ return bId < 0 ? 0 : bId; } /** * Find the closest set of contacts to a given NodeId * * @param target The NodeId to find contacts close to * @param numNodesRequired The number of contacts to find * * @return List A List of contacts closest to target */ @Override public synchronized final List<Node> findClosest(KademliaId target, int numNodesRequired) { TreeSet<Node> sortedSet = new TreeSet<>(new KeyComparator(target)); sortedSet.addAll(this.getAllNodes()); List<Node> closest = new ArrayList<>(numNodesRequired); /* Now we have the sorted set, lets get the top numRequired */ int count = 0; for (Node n : sortedSet) { closest.add(n); if (++count == numNodesRequired) { break; } } return closest; } /** * @return List A List of all Nodes in this JKademliaRoutingTable */ @Override public synchronized final List<Node> getAllNodes() { List<Node> nodes = new ArrayList<>(); for (KademliaBucket b : this.buckets) { for (Contact c : b.getContacts()) { nodes.add(c.getNode()); } } return nodes; } /** * @return List A List of all Nodes in this JKademliaRoutingTable */ @Override public final List<Contact> getAllContacts() { List<Contact> contacts = new ArrayList<>(); for (KademliaBucket b : this.buckets) { contacts.addAll(b.getContacts()); } return contacts; } /** * @return Bucket[] The buckets in this Kad Instance */ @Override public final KademliaBucket[] getBuckets() { return this.buckets; } /** * Set the KadBuckets of this routing table, mainly used when retrieving saved state * * @param buckets */ public final void setBuckets(KademliaBucket[] buckets) { this.buckets = buckets; } /** * Method used by operations to notify the routing table of any contacts that have been unresponsive. * * @param contacts The set of unresponsive contacts */ @Override public void setUnresponsiveContacts(List<Node> contacts) { if (contacts.isEmpty()) { return; } for (Node n : contacts) { this.setUnresponsiveContact(n); } } /** * Method used by operations to notify the routing table of any contacts that have been unresponsive. * * @param n */ @Override public synchronized void setUnresponsiveContact(Node n) { int bucketId = this.getBucketId(n.getNodeId()); /* Remove the contact from the bucket */ this.buckets[bucketId].removeNode(n); } @Override public synchronized final String toString() { StringBuilder sb = new StringBuilder("\nPrinting Routing Table Started ***************** \n"); int totalContacts = 0; for (KademliaBucket b : this.buckets) { if (b.numContacts() > 0) { totalContacts += b.numContacts(); sb.append("# nodes in Bucket with depth "); sb.append(b.getDepth()); sb.append(": "); sb.append(b.numContacts()); sb.append("\n"); sb.append(b.toString()); sb.append("\n"); } } sb.append("\nTotal Contacts: "); sb.append(totalContacts); sb.append("\n\n"); sb.append("Printing Routing Table Ended ******************** "); return sb.toString(); } }