cancel()
+ * method inherited from TimerTask
. In this case the caller is
+ * responsible for removing the task from the tasks
map.
+ * */
+ class TimeoutTask extends TimerTask
+ {
+
+ private final int comm;
+ private final Receiver recv;
+
+ public TimeoutTask(int comm, Receiver recv)
+ {
+ this.comm = comm;
+ this.recv = recv;
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ unregister(comm);
+ recv.timeout(comm);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+
+}
diff --git a/src/kademlia/core/Kademlia.java b/src/kademlia/core/Kademlia.java
new file mode 100644
index 0000000..34da90b
--- /dev/null
+++ b/src/kademlia/core/Kademlia.java
@@ -0,0 +1,105 @@
+/**
+ * @author Joshua Kissoon
+ * @created 20140215
+ * @desc The main Kademlia network management class
+ */
+package kademlia.core;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.Timer;
+import java.util.TimerTask;
+import kademlia.exceptions.RoutingException;
+import kademlia.message.MessageFactory;
+import kademlia.node.Node;
+import kademlia.node.NodeId;
+import kademlia.operation.ConnectOperation;
+import kademlia.operation.Operation;
+
+public class Kademlia
+{
+
+ /* Kademlia Attributes */
+ private final String name;
+
+ /* Objects to be used */
+ private final Node localNode;
+ private final KadServer server;
+ private final Timer timer;
+
+ /* Factories */
+ private final MessageFactory messageFactory;
+
+ /**
+ * Creates a Kademlia DistributedMap using the specified name as filename base.
+ * If the id cannot be read from disk the specified defaultId is used.
+ * The instance is bootstraped to an existing network by specifying the
+ * address of a bootstrap node in the network.
+ *
+ * @param name The Name of this node used for storage
+ * @param defaultId Default id for the node
+ * @param udpPort The UDP port to use for routing messages
+ *
+ * @throws IOException If an error occurred while reading id or local map
+ * from disk or a network error occurred while
+ * attempting to connect to the network
+ * */
+ public Kademlia(String name, NodeId defaultId, int udpPort) throws IOException
+ {
+ this.name = name;
+ this.localNode = new Node(defaultId, InetAddress.getLocalHost(), udpPort);
+ this.messageFactory = new MessageFactory(localNode);
+ this.server = new KadServer(udpPort, this.messageFactory);
+ this.timer = new Timer(true);
+
+ /* Schedule Recurring RestoreOperation */
+ timer.schedule(
+ new TimerTask()
+ {
+ @Override
+ public void run()
+ {
+ /**
+ * @todo Create Operation that
+ * Refreshes all buckets and sends HashMessages to all nodes that are
+ * among the K closest to mappings stored at this node. Also deletes any
+ * mappings that this node is no longer among the K closest to.
+ * */
+ }
+ },
+ // Delay // Interval
+ Configuration.RESTORE_INTERVAL, Configuration.RESTORE_INTERVAL
+ );
+ }
+
+ /**
+ * @return Node The local node for this system
+ */
+ public Node getNode()
+ {
+ return this.localNode;
+ }
+
+ /**
+ * @return The KadServer used to send/receive messages
+ */
+ public KadServer getServer()
+ {
+ return this.server;
+ }
+
+ /**
+ * Connect to an existing peer-to-peer network.
+ *
+ * @param n The known node in the peer-to-peer network
+ *
+ * @throws RoutingException If the bootstrap node could not be contacted
+ * @throws IOException If a network error occurred
+ * @throws IllegalStateException If this object is closed
+ * */
+ public final void connect(Node n) throws IOException, RoutingException
+ {
+ Operation op = new ConnectOperation(this.server, this.localNode, n);
+ op.execute();
+ }
+}
diff --git a/src/kademlia/exceptions/RoutingException.java b/src/kademlia/exceptions/RoutingException.java
new file mode 100644
index 0000000..c28f671
--- /dev/null
+++ b/src/kademlia/exceptions/RoutingException.java
@@ -0,0 +1,22 @@
+/**
+ * @author Joshua Kissoon
+ * @created 20140219
+ * @desc An exception to be thrown whenever there is a routing problem
+ */
+package kademlia.exceptions;
+
+import java.io.IOException;
+
+public class RoutingException extends IOException
+{
+
+ public RoutingException()
+ {
+ super();
+ }
+
+ public RoutingException(String message)
+ {
+ super(message);
+ }
+}
diff --git a/src/kademlia/message/AcknowledgeMessage.java b/src/kademlia/message/AcknowledgeMessage.java
new file mode 100644
index 0000000..c6ef459
--- /dev/null
+++ b/src/kademlia/message/AcknowledgeMessage.java
@@ -0,0 +1,57 @@
+/**
+ * @author Joshua Kissoon
+ * @created 20140218
+ * @desc A message used to acknowledge a request from a node
+ */
+package kademlia.message;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import kademlia.node.Node;
+
+public class AcknowledgeMessage implements Message
+{
+
+ private Node origin;
+ public static final byte CODE = 0x10;
+
+ public AcknowledgeMessage(Node origin)
+ {
+ this.origin = origin;
+ }
+
+ public AcknowledgeMessage(DataInput in) throws IOException
+ {
+ this.fromStream(in);
+ }
+
+ @Override
+ public final void fromStream(DataInput in) throws IOException
+ {
+ this.origin = new Node(in);
+ }
+
+ @Override
+ public void toStream(DataOutput out) throws IOException
+ {
+ origin.toStream(out);
+ }
+
+ public Node getOrigin()
+ {
+ return this.origin;
+ }
+
+ @Override
+ public byte code()
+ {
+ return CODE;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "AcknowledgeMessage[origin=" + origin.getNodeId() + "]";
+ }
+}
diff --git a/src/kademlia/message/ConnectMessage.java b/src/kademlia/message/ConnectMessage.java
new file mode 100644
index 0000000..7f885ae
--- /dev/null
+++ b/src/kademlia/message/ConnectMessage.java
@@ -0,0 +1,57 @@
+/**
+ * @author Joshua Kissoon
+ * @created 20140218
+ * @desc A message used to connect nodes
+ */
+package kademlia.message;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import kademlia.node.Node;
+
+public class ConnectMessage implements Message
+{
+
+ private Node origin;
+ public static final byte CODE = 0x11;
+
+ public ConnectMessage(Node origin)
+ {
+ this.origin = origin;
+ }
+
+ public ConnectMessage(DataInput in) throws IOException
+ {
+ this.fromStream(in);
+ }
+
+ @Override
+ public final void fromStream(DataInput in) throws IOException
+ {
+ this.origin = new Node(in);
+ }
+
+ @Override
+ public void toStream(DataOutput out) throws IOException
+ {
+ origin.toStream(out);
+ }
+
+ public Node getOrigin()
+ {
+ return this.origin;
+ }
+
+ @Override
+ public byte code()
+ {
+ return CODE;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "ConnectMessage[origin NodeId=" + origin.getNodeId() + "]";
+ }
+}
diff --git a/src/kademlia/message/ConnectReceiver.java b/src/kademlia/message/ConnectReceiver.java
new file mode 100644
index 0000000..dbef95a
--- /dev/null
+++ b/src/kademlia/message/ConnectReceiver.java
@@ -0,0 +1,58 @@
+/**
+ * @author Joshua Kissoon
+ * @created 20140219
+ * @desc Receives a ConnectMessage and sends an AcknowledgeMessage as reply
+ */
+package kademlia.message;
+
+import java.io.IOException;
+import kademlia.core.KadServer;
+import kademlia.node.Node;
+import kademlia.operation.Receiver;
+
+public class ConnectReceiver implements Receiver
+{
+
+ private final KadServer server;
+ private final Node localNode;
+
+ public ConnectReceiver(KadServer server, Node local)
+ {
+ this.server = server;
+ this.localNode = local;
+ }
+
+ /**
+ * Handle receiving a ConnectMessage
+ *
+ * @param comm
+ *
+ * @throws java.io.IOException
+ */
+ @Override
+ public void receive(Message incoming, int comm) throws IOException
+ {
+ ConnectMessage mess = (ConnectMessage) incoming;
+
+ /* Update the local space by inserting the origin node. */
+ this.localNode.getRoutingTable().insert(mess.getOrigin());
+
+ /* Respond to the connect request */
+ AcknowledgeMessage msg = new AcknowledgeMessage(this.localNode);
+
+ /* Reply to the connect message with an Acknowledgement */
+ this.server.reply(mess.getOrigin(), msg, comm);
+ }
+
+ /**
+ * We don't need to do anything here
+ *
+ * @param comm
+ *
+ * @throws java.io.IOException
+ */
+ @Override
+ public void timeout(int comm) throws IOException
+ {
+ }
+}
diff --git a/src/kademlia/message/Message.java b/src/kademlia/message/Message.java
new file mode 100644
index 0000000..890c4c7
--- /dev/null
+++ b/src/kademlia/message/Message.java
@@ -0,0 +1,10 @@
+package kademlia.message;
+
+public interface Message extends Streamable {
+ /**
+ * The unique code for the message type, used to differentiate all messages
+ * from each other. Since this is of byte
type there can
+ * be at most 256 different message types.
+ **/
+ public byte code();
+}
diff --git a/src/kademlia/message/MessageFactory.java b/src/kademlia/message/MessageFactory.java
new file mode 100644
index 0000000..8542717
--- /dev/null
+++ b/src/kademlia/message/MessageFactory.java
@@ -0,0 +1,49 @@
+/**
+ * @author Joshua
+ * @created
+ * @desc
+ */
+package kademlia.message;
+
+import java.io.DataInput;
+import java.io.IOException;
+import kademlia.core.KadServer;
+import kademlia.node.Node;
+import kademlia.operation.Receiver;
+
+public class MessageFactory
+{
+
+ private final Node localNode;
+
+ public MessageFactory(Node local)
+ {
+ this.localNode = local;
+ }
+
+ public Message createMessage(byte code, DataInput in) throws IOException
+ {
+ switch (code)
+ {
+ default:
+ case SimpleMessage.CODE:
+ return new SimpleMessage(in);
+ case ConnectMessage.CODE:
+ return new ConnectMessage(in);
+ case AcknowledgeMessage.CODE:
+ return new AcknowledgeMessage(in);
+ }
+ }
+
+ public Receiver createReceiver(byte code, KadServer server)
+ {
+ switch (code)
+ {
+ default:
+ case SimpleMessage.CODE:
+ return new SimpleReceiver();
+ case ConnectMessage.CODE:
+ return new ConnectReceiver(server, this.localNode);
+ }
+ }
+}
diff --git a/src/kademlia/message/SimpleMessage.java b/src/kademlia/message/SimpleMessage.java
new file mode 100644
index 0000000..afdc972
--- /dev/null
+++ b/src/kademlia/message/SimpleMessage.java
@@ -0,0 +1,72 @@
+/**
+ * @author Joshua Kissoon
+ * @created 20140217
+ * @desc A simple message used for testing the system
+ */
+package kademlia.message;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class SimpleMessage implements Message
+{
+
+ /* Message constants */
+ public static final byte CODE = 0x01;
+
+ private String content;
+
+ public SimpleMessage(String message)
+ {
+ this.content = message;
+ }
+
+ public SimpleMessage(DataInput in)
+ {
+ System.out.println("Creating message from input stream.");
+ this.fromStream(in);
+ }
+
+ @Override
+ public byte code()
+ {
+ return CODE;
+ }
+
+ @Override
+ public void toStream(DataOutput out)
+ {
+ try
+ {
+ out.writeInt(this.content.length());
+ out.writeBytes(this.content);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public final void fromStream(DataInput in)
+ {
+ try
+ {
+ byte[] buff = new byte[in.readInt()];
+ in.readFully(buff);
+
+ this.content = new String(buff);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.content;
+ }
+}
diff --git a/src/kademlia/message/SimpleReceiver.java b/src/kademlia/message/SimpleReceiver.java
new file mode 100644
index 0000000..7a41496
--- /dev/null
+++ b/src/kademlia/message/SimpleReceiver.java
@@ -0,0 +1,26 @@
+/**
+ * @author Joshua
+ * @created
+ * @desc
+ */
+package kademlia.message;
+
+import java.io.IOException;
+import kademlia.message.Message;
+import kademlia.operation.Receiver;
+
+public class SimpleReceiver implements Receiver
+{
+
+ @Override
+ public void receive(Message incoming, int conversationId)
+ {
+ System.out.println("Received message: " + incoming);
+ }
+
+ @Override
+ public void timeout(int conversationId) throws IOException
+ {
+ System.out.println("");
+ }
+}
diff --git a/src/kademlia/message/Streamable.java b/src/kademlia/message/Streamable.java
new file mode 100644
index 0000000..593aa4e
--- /dev/null
+++ b/src/kademlia/message/Streamable.java
@@ -0,0 +1,32 @@
+package kademlia.message;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * A Streamable object is able to write it's state to an output stream and
+ * a class implementing Streamable must be able to recreate an instance of
+ * the class from an input stream. No information about class name is written
+ * to the output stream so it must be known what class type is expected when
+ * reading objects back in from an input stream. This gives a space
+ * advantage over Serializable.
+ * + * Since the exact class must be known anyway prior to reading, it is incouraged + * that classes implementing Streamble also provide a constructor of the form: + *
+ * Streamable(DataInput in) throws IOException;
+ **/
+public interface Streamable {
+ /**
+ * Writes the internal state of the Streamable object to the output stream
+ * in a format that can later be read by the same Streamble class using
+ * the {@link #fromStream} method.
+ **/
+ public void toStream(DataOutput out) throws IOException;
+
+ /**
+ * Reads the internal state of the Streamable object from the input stream.
+ **/
+ public void fromStream(DataInput out) throws IOException;
+}
diff --git a/src/kademlia/node/Node.java b/src/kademlia/node/Node.java
new file mode 100644
index 0000000..b85fe03
--- /dev/null
+++ b/src/kademlia/node/Node.java
@@ -0,0 +1,118 @@
+/**
+ * @author Joshua
+ * @created
+ * @desc
+ */
+package kademlia.node;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import kademlia.message.Streamable;
+import kademlia.routing.RoutingTable;
+
+public class Node implements Streamable
+{
+
+ private NodeId nodeId;
+ private InetAddress inetAddress;
+ private int port;
+
+ private final RoutingTable routingTable;
+
+
+ {
+ this.routingTable = new RoutingTable(this);
+ }
+
+ public Node(NodeId nid, InetAddress ip, int port)
+ {
+ this.nodeId = nid;
+ this.inetAddress = ip;
+ this.port = port;
+ }
+
+ /**
+ * Load the Node's data from a DataInput stream
+ *
+ * @param in
+ *
+ * @throws IOException
+ */
+ public Node(DataInput in) throws IOException
+ {
+ this.fromStream(in);
+ }
+
+ /**
+ * Set the InetAddress of this node
+ *
+ * @param addr The new InetAddress of this node
+ */
+ public void setInetAddress(InetAddress addr)
+ {
+ this.inetAddress = addr;
+ }
+
+ /**
+ * @return The NodeId object of this node
+ */
+ public NodeId getNodeId()
+ {
+ return this.nodeId;
+ }
+
+ /**
+ * Creates a SocketAddress for this node
+ *
+ * @return
+ */
+ public SocketAddress getSocketAddress()
+ {
+ return new InetSocketAddress(this.inetAddress, this.port);
+ }
+
+ @Override
+ public void toStream(DataOutput out) throws IOException
+ {
+ /* Add the NodeId to the stream */
+ this.nodeId.toStream(out);
+
+ /* Add the Node's IP address to the stream */
+ byte[] a = inetAddress.getAddress();
+ if (a.length != 4)
+ {
+ throw new RuntimeException("Expected InetAddress of 4 bytes, got " + a.length);
+ }
+ out.write(a);
+
+ /* Add the port to the stream */
+ out.writeInt(port);
+ }
+
+ @Override
+ public final void fromStream(DataInput in) throws IOException
+ {
+ /* Load the NodeId */
+ this.nodeId = new NodeId(in);
+
+ /* Load the IP Address */
+ byte[] ip = new byte[4];
+ in.readFully(ip);
+ this.inetAddress = InetAddress.getByAddress(ip);
+
+ /* Read in the port */
+ this.port = in.readInt();
+ }
+
+ /**
+ * @return The RoutingTable of this Node
+ */
+ public RoutingTable getRoutingTable()
+ {
+ return this.routingTable;
+ }
+}
diff --git a/src/kademlia/node/NodeId.java b/src/kademlia/node/NodeId.java
new file mode 100644
index 0000000..5711608
--- /dev/null
+++ b/src/kademlia/node/NodeId.java
@@ -0,0 +1,165 @@
+/**
+ * @author Joshua Kissoon
+ * @created 20140215
+ * @desc Represents a Kademlia Node ID
+ */
+package kademlia.node;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Random;
+import kademlia.message.Streamable;
+
+public class NodeId implements Streamable
+{
+
+ public final static int ID_LENGTH = 160;
+ private byte[] keyBytes;
+
+ /**
+ * Construct the NodeId from some string
+ *
+ * @param data The user generated key string
+ *
+ * @todo Throw an exception if the key is too short or too long
+ */
+ public NodeId(String data)
+ {
+ keyBytes = data.getBytes();
+ }
+
+ /**
+ * Generate a random key
+ */
+ public NodeId()
+ {
+ keyBytes = new byte[ID_LENGTH];
+ new Random().nextBytes(keyBytes);
+ }
+
+ public NodeId(byte[] bytes)
+ {
+ this.keyBytes = bytes;
+ }
+
+ /**
+ * Load the NodeId from a DataInput stream
+ *
+ * @param in The stream from which to load the NodeId
+ *
+ * @throws IOException
+ */
+ public NodeId(DataInput in) throws IOException
+ {
+ this.fromStream(in);
+ }
+
+ public byte[] getBytes()
+ {
+ return this.keyBytes;
+ }
+
+ /**
+ * Compares a NodeId to this NodeId
+ *
+ * @param nid The NodeId to compare to this NodeId
+ *
+ * @return boolean Whether the 2 NodeIds are equal
+ */
+ public boolean equals(NodeId nid)
+ {
+ return Arrays.equals(keyBytes, nid.getBytes());
+ }
+
+ /**
+ * Checks if a given NodeId is less than this NodeId
+ *
+ * @param nid The NodeId to compare to this NodeId
+ *
+ * @return boolean Whether the given NodeId is less than this NodeId
+ */
+ public boolean lessThan(NodeId nid)
+ {
+ byte[] nidBytes = nid.getBytes();
+ for (int i = 0; i < ID_LENGTH; i++)
+ {
+ if (this.keyBytes[i] != nidBytes[i])
+ {
+ return this.keyBytes[i] < nidBytes[i];
+ }
+ }
+
+ /* We got here means they're equal */
+ return false;
+ }
+
+ /**
+ * Checks the distance between this and another NodeId
+ *
+ * @param nid
+ *
+ * @return The distance of this NodeId from the given NodeId
+ */
+ public NodeId xor(NodeId nid)
+ {
+ byte[] result = new byte[ID_LENGTH];
+ byte[] nidBytes = nid.getBytes();
+ for (int i = 0; i < ID_LENGTH / 8; i++)
+ {
+ result[i] = (byte) (this.keyBytes[i] ^ nidBytes[i]);
+ }
+
+ return new NodeId(result);
+ }
+
+ /**
+ * Checks the number of leading 0's in this NodeId
+ *
+ * @return int The number of leading 0's
+ */
+ public int prefixLength()
+ {
+ int prefixLength = 0;
+
+ for (byte b : this.keyBytes)
+ {
+ if (b == 0)
+ {
+ prefixLength++;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return prefixLength;
+ }
+
+ @Override
+ public void toStream(DataOutput out) throws IOException
+ {
+ /* Add the NodeId to the stream */
+ out.write(this.getBytes());
+ }
+
+ @Override
+ public void fromStream(DataInput in) throws IOException
+ {
+ byte[] input = new byte[ID_LENGTH / 8];
+ in.readFully(input);
+ this.keyBytes = input;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder("NodeId: ");
+ sb.append(new String(this.keyBytes));
+
+ return sb.toString();
+ }
+
+}
diff --git a/src/kademlia/operation/ConnectOperation.java b/src/kademlia/operation/ConnectOperation.java
new file mode 100644
index 0000000..ae24b84
--- /dev/null
+++ b/src/kademlia/operation/ConnectOperation.java
@@ -0,0 +1,122 @@
+/**
+ * @author Joshua Kissoon
+ * @created 20140218
+ * @desc Operation that handles connecting to an existing Kademlia network using a bootstrap node
+ */
+package kademlia.operation;
+
+import java.io.IOException;
+import kademlia.core.Configuration;
+import kademlia.core.KadServer;
+import kademlia.exceptions.RoutingException;
+import kademlia.message.AcknowledgeMessage;
+import kademlia.message.ConnectMessage;
+import kademlia.message.Message;
+import kademlia.node.Node;
+
+public class ConnectOperation implements Operation, Receiver
+{
+
+ public static final int MAX_CONNECT_ATTEMPTS = 5; // Try 5 times to connect to a node
+
+ private final KadServer server;
+ private final Node localNode;
+ private final Node bootstrapNode;
+
+ private boolean error;
+ private int attempts;
+
+ /**
+ * @param server The message server used to send/receive messages
+ * @param local The local node
+ * @param bootstrap Node to use to bootstrap the local node onto the network
+ */
+ public ConnectOperation(KadServer server, Node local, Node bootstrap)
+ {
+ this.server = server;
+ this.localNode = local;
+ this.bootstrapNode = bootstrap;
+ }
+
+ /**
+ * @return null
+ */
+ @Override
+ public synchronized Object execute()
+ {
+ try
+ {
+ /* Contact the bootstrap node */
+ this.error = true;
+ this.attempts = 0;
+ Message m = new ConnectMessage(this.localNode);
+
+ /* Send a connect message to the bootstrap node */
+ server.sendMessage(this.bootstrapNode, m, this);
+
+ /* Wait for a while */
+ wait(Configuration.OPERATION_TIMEOUT);
+
+ if (error)
+ {
+ /* Means the contact failed */
+ throw new RoutingException("Bootstrap node did not respond: " + bootstrapNode);
+ }
+
+ /* @todo Perform lookup for our own ID to get nodes close to us */
+ /* @todo Refresh buckets to get a good routing table */
+ return null;
+
+ }
+ catch (IOException | InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ /**
+ * Receives an AcknowledgeMessage from the bootstrap node.
+ *
+ * @param comm
+ */
+ @Override
+ public synchronized void receive(Message incoming, int comm)
+ {
+ /* The incoming message will be an acknowledgement message */
+ AcknowledgeMessage msg = (AcknowledgeMessage) incoming;
+ System.out.println("ConnectOperation now handling Acknowledgement Message: " + msg);
+
+ /* The bootstrap node has responded, insert it into our space */
+ this.localNode.getRoutingTable().insert(this.bootstrapNode);
+
+ /* We got a response, so the error is false */
+ error = false;
+
+ /* Wake up any waiting thread */
+ notify();
+ }
+
+ /**
+ * Resends a ConnectMessage to the boot strap node a maximum of MAX_ATTEMPTS
+ * times.
+ *
+ * @param comm
+ *
+ * @throws java.io.IOException
+ */
+ @Override
+ public synchronized void timeout(int comm) throws IOException
+ {
+ if (++this.attempts < MAX_CONNECT_ATTEMPTS)
+ {
+ this.server.sendMessage(this.bootstrapNode, new ConnectMessage(this.localNode), this);
+ }
+ else
+ {
+ /* We just exit, so notify all other threads that are possibly waiting */
+ notify();
+ }
+ }
+}
diff --git a/src/kademlia/operation/Operation.java b/src/kademlia/operation/Operation.java
new file mode 100644
index 0000000..3b8bbaf
--- /dev/null
+++ b/src/kademlia/operation/Operation.java
@@ -0,0 +1,17 @@
+/**
+ * @author Joshua Kissoon
+ * @created 20140218
+ * @desc Interface for different Kademlia operations
+ */
+package kademlia.operation;
+
+public interface Operation
+{
+
+ /**
+ * Starts an operation and returns when the operation is finished
+ *
+ * @return The return value can differ per operation
+ */
+ public Object execute();
+}
diff --git a/src/kademlia/operation/PingOperation.java b/src/kademlia/operation/PingOperation.java
new file mode 100644
index 0000000..ee9e870
--- /dev/null
+++ b/src/kademlia/operation/PingOperation.java
@@ -0,0 +1,36 @@
+///**
+// * @author Joshua Kissoon
+// * @created 20140218
+// * @desc Implementation of the Kademlia Ping operation
+// */
+//package kademlia.operation;
+//
+//import kademlia.core.KadServer;
+//import kademlia.node.Node;
+//
+//public class PingOperation implements Operation
+//{
+//
+// private final KadServer server;
+// private final Node localNode;
+// private final Node toPing;
+//
+// /**
+// * @param server The Kademlia server used to send & receive messages
+// * @param local The local node
+// * @param toPing The node to send the ping message to
+// */
+// public PingOperation(KadServer server, Node local, Node toPing)
+// {
+// this.server = server;
+// this.localNode = local;
+// this.toPing = toPing;
+// }
+//
+// @Override
+// public Object execute()
+// {
+// /* @todo Create a pingmessage and send this message to the toPing node,
+// then handle the reply from this node using a reciever */
+// }
+//}
diff --git a/src/kademlia/operation/Receiver.java b/src/kademlia/operation/Receiver.java
new file mode 100644
index 0000000..f63596b
--- /dev/null
+++ b/src/kademlia/operation/Receiver.java
@@ -0,0 +1,31 @@
+/**
+ * @author Joshua Kissoon
+ * @created 20140218
+ * @desc A receiver waits for incoming messages and perform some action when the message is received
+ */
+package kademlia.operation;
+
+import java.io.IOException;
+import kademlia.message.Message;
+
+public interface Receiver
+{
+
+ /**
+ * Message is received, now handle it
+ *
+ * @param conversationId The ID of this conversation, used for further conversations
+ * @param incoming The incoming
+ */
+ public void receive(Message incoming, int conversationId) throws IOException;
+
+ /**
+ * If no reply is received in MessageServer.TIMEOUT
seconds for the
+ * message with communication id comm
, the MessageServer calls this method
+ *
+ * @param conversationId The conversation ID of this communication
+ *
+ * @throws IOException if an I/O error occurs
+ * */
+ public void timeout(int conversationId) throws IOException;
+}
diff --git a/src/kademlia/routing/Bucket.java b/src/kademlia/routing/Bucket.java
new file mode 100644
index 0000000..b27dd87
--- /dev/null
+++ b/src/kademlia/routing/Bucket.java
@@ -0,0 +1,27 @@
+/**
+ * @author Joshua Kissoon
+ * @created 20140215
+ * @desc A bucket for the DHT Protocol
+ */
+package kademlia.routing;
+
+import kademlia.node.Node;
+
+public interface Bucket
+{
+
+ /**
+ * Adds a new node to the bucket
+ *
+ * @param n the new node
+ */
+ public void insert(Node n);
+
+ /**
+ * Marks a node as dead: the dead node will be replace if
+ * insert was invoked
+ *
+ * @param n the dead node
+ */
+ public void markDead(Node n);
+}
diff --git a/src/kademlia/routing/KadBucket.java b/src/kademlia/routing/KadBucket.java
new file mode 100644
index 0000000..27fb0b8
--- /dev/null
+++ b/src/kademlia/routing/KadBucket.java
@@ -0,0 +1,82 @@
+/**
+ * @author Joshua Kissoon
+ * @created 20140215
+ * @desc A bucket in the Kademlia routing table
+ */
+package kademlia.routing;
+
+import java.util.ArrayList;
+import kademlia.node.Node;
+
+public class KadBucket implements Bucket
+{
+
+ private final int depth;
+ private final ArrayList