().read(in);
+ }
+ catch (ClassNotFoundException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public Node getOrigin()
+ {
+ return this.origin;
+ }
+
+ public JKademliaStorageEntry getContent()
+ {
+ return this.content;
+ }
+
+ @Override
+ public byte code()
+ {
+ return CODE;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "StoreContentMessage[origin=" + origin + ",content=" + content + "]";
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/message/StoreContentReceiver.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/message/StoreContentReceiver.java
new file mode 100644
index 0000000..e85b728
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/message/StoreContentReceiver.java
@@ -0,0 +1,57 @@
+package com.github.joshuakissoon.kademlia.message;
+
+import java.io.IOException;
+import com.github.joshuakissoon.kademlia.KadServer;
+import com.github.joshuakissoon.kademlia.KademliaNode;
+import com.github.joshuakissoon.kademlia.dht.KademliaDHT;
+
+/**
+ * Receiver for incoming StoreContentMessage
+ *
+ * @author Joshua Kissoon
+ * @since 20140225
+ */
+public class StoreContentReceiver implements Receiver
+{
+
+ private final KadServer server;
+ private final KademliaNode localNode;
+ private final KademliaDHT dht;
+
+ public StoreContentReceiver(KadServer server, KademliaNode localNode, KademliaDHT dht)
+ {
+ this.server = server;
+ this.localNode = localNode;
+ this.dht = dht;
+ }
+
+ @Override
+ public void receive(Message incoming, int comm)
+ {
+ /* It's a StoreContentMessage we're receiving */
+ StoreContentMessage msg = (StoreContentMessage) incoming;
+
+ /* Insert the message sender into this node's routing table */
+ this.localNode.getRoutingTable().insert(msg.getOrigin());
+
+ try
+ {
+ /* Store this Content into the DHT */
+ this.dht.store(msg.getContent());
+ }
+ catch (IOException e)
+ {
+ System.err.println("Unable to store received content; Message: " + e.getMessage());
+ }
+
+ }
+
+ @Override
+ public void timeout(int comm)
+ {
+ /**
+ * This receiver only handles Receiving content when we've received the message,
+ * so no timeout will happen with this receiver.
+ */
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/message/Streamable.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/message/Streamable.java
new file mode 100644
index 0000000..0f3573d
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/message/Streamable.java
@@ -0,0 +1,42 @@
+package com.github.joshuakissoon.kademlia.message;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+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.
+ *
+ * @param out
+ *
+ * @throws java.io.IOException
+ */
+ public void toStream(DataOutputStream out) throws IOException;
+
+ /**
+ * Reads the internal state of the Streamable object from the input stream.
+ *
+ * @param out
+ *
+ * @throws java.io.IOException
+ */
+ public void fromStream(DataInputStream out) throws IOException;
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/node/KademliaId.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/node/KademliaId.java
new file mode 100644
index 0000000..3eb4a5d
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/node/KademliaId.java
@@ -0,0 +1,263 @@
+/**
+ * @author Joshua Kissoon
+ * @created 20140215
+ * @desc Represents a Kademlia Node ID
+ */
+package com.github.joshuakissoon.kademlia.node;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.Serializable;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Random;
+import com.github.joshuakissoon.kademlia.message.Streamable;
+
+public class KademliaId implements Streamable, Serializable
+{
+
+ public final transient static int ID_LENGTH = 160;
+ private byte[] keyBytes;
+
+ /**
+ * Construct the NodeId from some string
+ *
+ * @param data The user generated key string
+ */
+ public KademliaId(String data)
+ {
+ keyBytes = data.getBytes();
+ if (keyBytes.length != ID_LENGTH / 8)
+ {
+ throw new IllegalArgumentException("Specified Data need to be " + (ID_LENGTH / 8) + " characters long.");
+ }
+ }
+
+ /**
+ * Generate a random key
+ */
+ public KademliaId()
+ {
+ keyBytes = new byte[ID_LENGTH / 8];
+ new Random().nextBytes(keyBytes);
+ }
+
+ /**
+ * Generate the NodeId from a given byte[]
+ *
+ * @param bytes
+ */
+ public KademliaId(byte[] bytes)
+ {
+ if (bytes.length != ID_LENGTH / 8)
+ {
+ throw new IllegalArgumentException("Specified Data need to be " + (ID_LENGTH / 8) + " characters long. Data Given: '" + new String(bytes) + "'");
+ }
+ this.keyBytes = bytes;
+ }
+
+ /**
+ * Load the NodeId from a DataInput stream
+ *
+ * @param in The stream from which to load the NodeId
+ *
+ * @throws IOException
+ */
+ public KademliaId(DataInputStream in) throws IOException
+ {
+ this.fromStream(in);
+ }
+
+ public byte[] getBytes()
+ {
+ return this.keyBytes;
+ }
+
+ /**
+ * @return The BigInteger representation of the key
+ */
+ public BigInteger getInt()
+ {
+ return new BigInteger(1, this.getBytes());
+ }
+
+ /**
+ * Compares a NodeId to this NodeId
+ *
+ * @param o The NodeId to compare to this NodeId
+ *
+ * @return boolean Whether the 2 NodeIds are equal
+ */
+ @Override
+ public boolean equals(Object o)
+ {
+ if (o instanceof KademliaId)
+ {
+ KademliaId nid = (KademliaId) o;
+ return this.hashCode() == nid.hashCode();
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int hash = 7;
+ hash = 83 * hash + Arrays.hashCode(this.keyBytes);
+ return hash;
+ }
+
+ /**
+ * Checks the distance between this and another NodeId
+ *
+ * @param nid
+ *
+ * @return The distance of this NodeId from the given NodeId
+ */
+ public KademliaId xor(KademliaId nid)
+ {
+ byte[] result = new byte[ID_LENGTH / 8];
+ byte[] nidBytes = nid.getBytes();
+
+ for (int i = 0; i < ID_LENGTH / 8; i++)
+ {
+ result[i] = (byte) (this.keyBytes[i] ^ nidBytes[i]);
+ }
+
+ KademliaId resNid = new KademliaId(result);
+
+ return resNid;
+ }
+
+ /**
+ * Generates a NodeId that is some distance away from this NodeId
+ *
+ * @param distance in number of bits
+ *
+ * @return NodeId The newly generated NodeId
+ */
+ public KademliaId generateNodeIdByDistance(int distance)
+ {
+ byte[] result = new byte[ID_LENGTH / 8];
+
+ /* Since distance = ID_LENGTH - prefixLength, we need to fill that amount with 0's */
+ int numByteZeroes = (ID_LENGTH - distance) / 8;
+ int numBitZeroes = 8 - (distance % 8);
+
+ /* Filling byte zeroes */
+ for (int i = 0; i < numByteZeroes; i++)
+ {
+ result[i] = 0;
+ }
+
+ /* Filling bit zeroes */
+ BitSet bits = new BitSet(8);
+ bits.set(0, 8);
+
+ for (int i = 0; i < numBitZeroes; i++)
+ {
+ /* Shift 1 zero into the start of the value */
+ bits.clear(i);
+ }
+ bits.flip(0, 8); // Flip the bits since they're in reverse order
+ result[numByteZeroes] = (byte) bits.toByteArray()[0];
+
+ /* Set the remaining bytes to Maximum value */
+ for (int i = numByteZeroes + 1; i < result.length; i++)
+ {
+ result[i] = Byte.MAX_VALUE;
+ }
+
+ return this.xor(new KademliaId(result));
+ }
+
+ /**
+ * Counts the number of leading 0's in this NodeId
+ *
+ * @return Integer The number of leading 0's
+ */
+ public int getFirstSetBitIndex()
+ {
+ int prefixLength = 0;
+
+ for (byte b : this.keyBytes)
+ {
+ if (b == 0)
+ {
+ prefixLength += 8;
+ }
+ else
+ {
+ /* If the byte is not 0, we need to count how many MSBs are 0 */
+ int count = 0;
+ for (int i = 7; i >= 0; i--)
+ {
+ boolean a = (b & (1 << i)) == 0;
+ if (a)
+ {
+ count++;
+ }
+ else
+ {
+ break; // Reset the count if we encounter a non-zero number
+ }
+ }
+
+ /* Add the count of MSB 0s to the prefix length */
+ prefixLength += count;
+
+ /* Break here since we've now covered the MSB 0s */
+ break;
+ }
+ }
+ return prefixLength;
+ }
+
+ /**
+ * Gets the distance from this NodeId to another NodeId
+ *
+ * @param to
+ *
+ * @return Integer The distance
+ */
+ public int getDistance(KademliaId to)
+ {
+ /**
+ * Compute the xor of this and to
+ * Get the index i of the first set bit of the xor returned NodeId
+ * The distance between them is ID_LENGTH - i
+ */
+ return ID_LENGTH - this.xor(to).getFirstSetBitIndex();
+ }
+
+ @Override
+ public void toStream(DataOutputStream out) throws IOException
+ {
+ /* Add the NodeId to the stream */
+ out.write(this.getBytes());
+ }
+
+ @Override
+ public final void fromStream(DataInputStream in) throws IOException
+ {
+ byte[] input = new byte[ID_LENGTH / 8];
+ in.readFully(input);
+ this.keyBytes = input;
+ }
+
+ public String hexRepresentation()
+ {
+ /* Returns the hex format of this NodeId */
+ BigInteger bi = new BigInteger(1, this.keyBytes);
+ return String.format("%0" + (this.keyBytes.length << 1) + "X", bi);
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.hexRepresentation();
+ }
+
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/node/KeyComparator.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/node/KeyComparator.java
new file mode 100644
index 0000000..99c2454
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/node/KeyComparator.java
@@ -0,0 +1,44 @@
+package com.github.joshuakissoon.kademlia.node;
+
+import java.math.BigInteger;
+import java.util.Comparator;
+
+/**
+ * A Comparator to compare 2 keys to a given key
+ *
+ * @author Joshua Kissoon
+ * @since 20140322
+ */
+public class KeyComparator implements Comparator
+{
+
+ private final BigInteger key;
+
+ /**
+ * @param key The NodeId relative to which the distance should be measured.
+ */
+ public KeyComparator(KademliaId key)
+ {
+ this.key = key.getInt();
+ }
+
+ /**
+ * Compare two objects which must both be of type Node
+ * and determine which is closest to the identifier specified in the
+ * constructor.
+ *
+ * @param n1 Node 1 to compare distance from the key
+ * @param n2 Node 2 to compare distance from the key
+ */
+ @Override
+ public int compare(Node n1, Node n2)
+ {
+ BigInteger b1 = n1.getNodeId().getInt();
+ BigInteger b2 = n2.getNodeId().getInt();
+
+ b1 = b1.xor(key);
+ b2 = b2.xor(key);
+
+ return b1.abs().compareTo(b2.abs());
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/node/Node.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/node/Node.java
new file mode 100644
index 0000000..7b2b91b
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/node/Node.java
@@ -0,0 +1,134 @@
+package com.github.joshuakissoon.kademlia.node;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import com.github.joshuakissoon.kademlia.message.Streamable;
+
+/**
+ * A Node in the Kademlia network - Contains basic node network information.
+ *
+ * @author Joshua Kissoon
+ * @since 20140202
+ * @version 0.1
+ */
+public class Node implements Streamable, Serializable
+{
+
+ private KademliaId nodeId;
+ private InetAddress inetAddress;
+ private int port;
+ private final String strRep;
+
+ public Node(KademliaId nid, InetAddress ip, int port)
+ {
+ this.nodeId = nid;
+ this.inetAddress = ip;
+ this.port = port;
+ this.strRep = this.nodeId.toString();
+ }
+
+ /**
+ * Load the Node's data from a DataInput stream
+ *
+ * @param in
+ *
+ * @throws IOException
+ */
+ public Node(DataInputStream in) throws IOException
+ {
+ this.fromStream(in);
+ this.strRep = this.nodeId.toString();
+ }
+
+ /**
+ * 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 KademliaId getNodeId()
+ {
+ return this.nodeId;
+ }
+
+ /**
+ * Creates a SocketAddress for this node
+ *
+ * @return
+ */
+ public InetSocketAddress getSocketAddress()
+ {
+ return new InetSocketAddress(this.inetAddress, this.port);
+ }
+
+ @Override
+ public void toStream(DataOutputStream 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(DataInputStream in) throws IOException
+ {
+ /* Load the NodeId */
+ this.nodeId = new KademliaId(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();
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (o instanceof Node)
+ {
+ Node n = (Node) o;
+ if (n == this)
+ {
+ return true;
+ }
+ return this.getNodeId().equals(n.getNodeId());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return this.getNodeId().hashCode();
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.getNodeId().toString();
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/BucketRefreshOperation.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/BucketRefreshOperation.java
new file mode 100644
index 0000000..03a7327
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/BucketRefreshOperation.java
@@ -0,0 +1,66 @@
+package com.github.joshuakissoon.kademlia.operation;
+
+import java.io.IOException;
+import com.github.joshuakissoon.kademlia.KadConfiguration;
+import com.github.joshuakissoon.kademlia.KadServer;
+import com.github.joshuakissoon.kademlia.KademliaNode;
+import com.github.joshuakissoon.kademlia.node.KademliaId;
+
+/**
+ * At each time interval t, nodes need to refresh their K-Buckets
+ * This operation takes care of refreshing this node's K-Buckets
+ *
+ * @author Joshua Kissoon
+ * @created 20140224
+ */
+public class BucketRefreshOperation implements Operation
+{
+
+ private final KadServer server;
+ private final KademliaNode localNode;
+ private final KadConfiguration config;
+
+ public BucketRefreshOperation(KadServer server, KademliaNode localNode, KadConfiguration config)
+ {
+ this.server = server;
+ this.localNode = localNode;
+ this.config = config;
+ }
+
+ /**
+ * Each bucket need to be refreshed at every time interval t.
+ * Find an identifier in each bucket's range, use it to look for nodes closest to this identifier
+ * allowing the bucket to be refreshed.
+ *
+ * Then Do a NodeLookupOperation for each of the generated NodeIds,
+ * This will find the K-Closest nodes to that ID, and update the necessary K-Bucket
+ *
+ * @throws java.io.IOException
+ */
+ @Override
+ public synchronized void execute() throws IOException
+ {
+ for (int i = 1; i < KademliaId.ID_LENGTH; i++)
+ {
+ /* Construct a NodeId that is i bits away from the current node Id */
+ final KademliaId current = this.localNode.getNode().getNodeId().generateNodeIdByDistance(i);
+
+ /* Run the Node Lookup Operation, each in a different thread to speed up things */
+ new Thread()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ new NodeLookupOperation(server, localNode, current, BucketRefreshOperation.this.config).execute();
+ }
+ catch (IOException e)
+ {
+ //System.err.println("Bucket Refresh Operation Failed. Msg: " + e.getMessage());
+ }
+ }
+ }.start();
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/ConnectOperation.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/ConnectOperation.java
new file mode 100644
index 0000000..ba15621
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/ConnectOperation.java
@@ -0,0 +1,140 @@
+/**
+ * @author Joshua Kissoon
+ * @created 20140218
+ * @desc Operation that handles connecting to an existing Kademlia network using a bootstrap node
+ */
+package com.github.joshuakissoon.kademlia.operation;
+
+import com.github.joshuakissoon.kademlia.message.Receiver;
+import java.io.IOException;
+import com.github.joshuakissoon.kademlia.JKademliaNode;
+import com.github.joshuakissoon.kademlia.KadConfiguration;
+import com.github.joshuakissoon.kademlia.KadServer;
+import com.github.joshuakissoon.kademlia.KademliaNode;
+import com.github.joshuakissoon.kademlia.exceptions.RoutingException;
+import com.github.joshuakissoon.kademlia.message.AcknowledgeMessage;
+import com.github.joshuakissoon.kademlia.message.ConnectMessage;
+import com.github.joshuakissoon.kademlia.message.Message;
+import com.github.joshuakissoon.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 KademliaNode localNode;
+ private final Node bootstrapNode;
+ private final KadConfiguration config;
+
+ 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
+ * @param config
+ */
+ public ConnectOperation(KadServer server, KademliaNode local, Node bootstrap, KadConfiguration config)
+ {
+ this.server = server;
+ this.localNode = local;
+ this.bootstrapNode = bootstrap;
+ this.config = config;
+ }
+
+ @Override
+ public synchronized void execute() throws IOException
+ {
+ try
+ {
+ /* Contact the bootstrap node */
+ this.error = true;
+ this.attempts = 0;
+ Message m = new ConnectMessage(this.localNode.getNode());
+
+ /* Send a connect message to the bootstrap node */
+ server.sendMessage(this.bootstrapNode, m, this);
+
+ /* If we haven't finished as yet, wait for a maximum of config.operationTimeout() time */
+ int totalTimeWaited = 0;
+ int timeInterval = 50; // We re-check every 300 milliseconds
+ while (totalTimeWaited < this.config.operationTimeout())
+ {
+ if (error)
+ {
+ wait(timeInterval);
+ totalTimeWaited += timeInterval;
+ }
+ else
+ {
+ break;
+ }
+ }
+ if (error)
+ {
+ /* If we still haven't received any responses by then, do a routing timeout */
+ throw new RoutingException("ConnectOperation: Bootstrap node did not respond: " + bootstrapNode);
+ }
+
+ /* Perform lookup for our own ID to get nodes close to us */
+ Operation lookup = new NodeLookupOperation(this.server, this.localNode, this.localNode.getNode().getNodeId(), this.config);
+ lookup.execute();
+
+ /**
+ * Refresh buckets to get a good routing table
+ * After the above lookup operation, K nodes will be in our routing table,
+ * Now we try to populate all of our buckets.
+ */
+ new BucketRefreshOperation(this.server, this.localNode, this.config).execute();
+ }
+ catch (InterruptedException e)
+ {
+ System.err.println("Connect operation was interrupted. ");
+ }
+ }
+
+ /**
+ * 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;
+
+ /* 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.getNode()), this);
+ }
+ else
+ {
+ /* We just exit, so notify all other threads that are possibly waiting */
+ notify();
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/ContentLookupOperation.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/ContentLookupOperation.java
new file mode 100644
index 0000000..0058c3b
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/ContentLookupOperation.java
@@ -0,0 +1,342 @@
+package com.github.joshuakissoon.kademlia.operation;
+
+import com.github.joshuakissoon.kademlia.message.Receiver;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import com.github.joshuakissoon.kademlia.JKademliaNode;
+import com.github.joshuakissoon.kademlia.dht.GetParameter;
+import com.github.joshuakissoon.kademlia.KadConfiguration;
+import com.github.joshuakissoon.kademlia.KadServer;
+import com.github.joshuakissoon.kademlia.dht.JKademliaStorageEntry;
+import com.github.joshuakissoon.kademlia.exceptions.ContentNotFoundException;
+import com.github.joshuakissoon.kademlia.exceptions.RoutingException;
+import com.github.joshuakissoon.kademlia.exceptions.UnknownMessageException;
+import com.github.joshuakissoon.kademlia.message.ContentLookupMessage;
+import com.github.joshuakissoon.kademlia.message.ContentMessage;
+import com.github.joshuakissoon.kademlia.message.Message;
+import com.github.joshuakissoon.kademlia.message.NodeReplyMessage;
+import com.github.joshuakissoon.kademlia.node.KeyComparator;
+import com.github.joshuakissoon.kademlia.node.Node;
+import com.github.joshuakissoon.kademlia.util.RouteLengthChecker;
+
+/**
+ * Looks up a specified identifier and returns the value associated with it
+ *
+ * @author Joshua Kissoon
+ * @since 20140226
+ */
+public class ContentLookupOperation implements Operation, Receiver
+{
+
+ /* Constants */
+ private static final Byte UNASKED = (byte) 0x00;
+ private static final Byte AWAITING = (byte) 0x01;
+ private static final Byte ASKED = (byte) 0x02;
+ private static final Byte FAILED = (byte) 0x03;
+
+ private final KadServer server;
+ private final JKademliaNode localNode;
+ private JKademliaStorageEntry contentFound = null;
+ private final KadConfiguration config;
+
+ private final ContentLookupMessage lookupMessage;
+
+ private boolean isContentFound;
+ private final SortedMap nodes;
+
+ /* Tracks messages in transit and awaiting reply */
+ private final Map messagesTransiting;
+
+ /* Used to sort nodes */
+ private final Comparator comparator;
+
+ /* Statistical information */
+ private final RouteLengthChecker routeLengthChecker;
+
+
+ {
+ messagesTransiting = new HashMap<>();
+ isContentFound = false;
+ routeLengthChecker = new RouteLengthChecker();
+ }
+
+ /**
+ * @param server
+ * @param localNode
+ * @param params The parameters to search for the content which we need to find
+ * @param config
+ */
+ public ContentLookupOperation(KadServer server, JKademliaNode localNode, GetParameter params, KadConfiguration config)
+ {
+ /* Construct our lookup message */
+ this.lookupMessage = new ContentLookupMessage(localNode.getNode(), params);
+
+ this.server = server;
+ this.localNode = localNode;
+ this.config = config;
+
+ /**
+ * We initialize a TreeMap to store nodes.
+ * This map will be sorted by which nodes are closest to the lookupId
+ */
+ this.comparator = new KeyComparator(params.getKey());
+ this.nodes = new TreeMap(this.comparator);
+ }
+
+ /**
+ * @throws java.io.IOException
+ * @throws com.github.joshuakissoon.kademlia.exceptions.RoutingException
+ */
+ @Override
+ public synchronized void execute() throws IOException, RoutingException
+ {
+ try
+ {
+ /* Set the local node as already asked */
+ nodes.put(this.localNode.getNode(), ASKED);
+
+ /**
+ * We add all nodes here instead of the K-Closest because there may be the case that the K-Closest are offline
+ * - The operation takes care of looking at the K-Closest.
+ */
+ List allNodes = this.localNode.getRoutingTable().getAllNodes();
+ this.addNodes(allNodes);
+
+ /* Also add the initial set of nodes to the routeLengthChecker */
+ this.routeLengthChecker.addInitialNodes(allNodes);
+
+ /**
+ * If we haven't found the requested amount of content as yet,
+ * keey trying until config.operationTimeout() time has expired
+ */
+ int totalTimeWaited = 0;
+ int timeInterval = 10; // We re-check every n milliseconds
+ while (totalTimeWaited < this.config.operationTimeout())
+ {
+ if (!this.askNodesorFinish() && !isContentFound)
+ {
+ wait(timeInterval);
+ totalTimeWaited += timeInterval;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ catch (InterruptedException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Add nodes from this list to the set of nodes to lookup
+ *
+ * @param list The list from which to add nodes
+ */
+ public void addNodes(List list)
+ {
+ for (Node o : list)
+ {
+ /* If this node is not in the list, add the node */
+ if (!nodes.containsKey(o))
+ {
+ nodes.put(o, UNASKED);
+ }
+ }
+ }
+
+ /**
+ * Asks some of the K closest nodes seen but not yet queried.
+ * Assures that no more than DefaultConfiguration.CONCURRENCY messages are in transit at a time
+ *
+ * This method should be called every time a reply is received or a timeout occurs.
+ *
+ * If all K closest nodes have been asked and there are no messages in transit,
+ * the algorithm is finished.
+ *
+ * @return true
if finished OR false
otherwise
+ */
+ private boolean askNodesorFinish() throws IOException
+ {
+ /* If >= CONCURRENCY nodes are in transit, don't do anything */
+ if (this.config.maxConcurrentMessagesTransiting() <= this.messagesTransiting.size())
+ {
+ return false;
+ }
+
+ /* Get unqueried nodes among the K closest seen that have not FAILED */
+ List unasked = this.closestNodesNotFailed(UNASKED);
+
+ if (unasked.isEmpty() && this.messagesTransiting.isEmpty())
+ {
+ /* We have no unasked nodes nor any messages in transit, we're finished! */
+ return true;
+ }
+
+ /* Sort nodes according to criteria */
+ Collections.sort(unasked, this.comparator);
+
+ /**
+ * Send messages to nodes in the list;
+ * making sure than no more than CONCURRENCY messsages are in transit
+ */
+ for (int i = 0; (this.messagesTransiting.size() < this.config.maxConcurrentMessagesTransiting()) && (i < unasked.size()); i++)
+ {
+ Node n = (Node) unasked.get(i);
+
+ int comm = server.sendMessage(n, lookupMessage, this);
+
+ this.nodes.put(n, AWAITING);
+ this.messagesTransiting.put(comm, n);
+ }
+
+ /* We're not finished as yet, return false */
+ return false;
+ }
+
+ /**
+ * Find The K closest nodes to the target lookupId given that have not FAILED.
+ * From those K, get those that have the specified status
+ *
+ * @param status The status of the nodes to return
+ *
+ * @return A List of the closest nodes
+ */
+ private List closestNodesNotFailed(Byte status)
+ {
+ List closestNodes = new ArrayList<>(this.config.k());
+ int remainingSpaces = this.config.k();
+
+ for (Map.Entry e : this.nodes.entrySet())
+ {
+ if (!FAILED.equals(e.getValue()))
+ {
+ if (status.equals(e.getValue()))
+ {
+ /* We got one with the required status, now add it */
+ closestNodes.add((Node) e.getKey());
+ }
+
+ if (--remainingSpaces == 0)
+ {
+ break;
+ }
+ }
+ }
+
+ return closestNodes;
+ }
+
+ @Override
+ public synchronized void receive(Message incoming, int comm) throws IOException, RoutingException
+ {
+ if (this.isContentFound)
+ {
+ return;
+ }
+
+ if (incoming instanceof ContentMessage)
+ {
+ /* The reply received is a content message with the required content, take it in */
+ ContentMessage msg = (ContentMessage) incoming;
+
+ /* Add the origin node to our routing table */
+ this.localNode.getRoutingTable().insert(msg.getOrigin());
+
+ /* Get the Content and check if it satisfies the required parameters */
+ JKademliaStorageEntry content = msg.getContent();
+ this.contentFound = content;
+ this.isContentFound = true;
+ }
+ else
+ {
+ /* The reply received is a NodeReplyMessage with nodes closest to the content needed */
+ NodeReplyMessage msg = (NodeReplyMessage) incoming;
+
+ /* Add the origin node to our routing table */
+ Node origin = msg.getOrigin();
+ this.localNode.getRoutingTable().insert(origin);
+
+ /* Set that we've completed ASKing the origin node */
+ this.nodes.put(origin, ASKED);
+
+ /* Remove this msg from messagesTransiting since it's completed now */
+ this.messagesTransiting.remove(comm);
+
+ /* Add the received nodes to the routeLengthChecker */
+ this.routeLengthChecker.addNodes(msg.getNodes(), origin);
+
+ /* Add the received nodes to our nodes list to query */
+ this.addNodes(msg.getNodes());
+ this.askNodesorFinish();
+ }
+ }
+
+ /**
+ * A node does not respond or a packet was lost, we set this node as failed
+ *
+ * @param comm
+ *
+ * @throws java.io.IOException
+ */
+ @Override
+ public synchronized void timeout(int comm) throws IOException
+ {
+ /* Get the node associated with this communication */
+ Node n = this.messagesTransiting.get(new Integer(comm));
+
+ if (n == null)
+ {
+ throw new UnknownMessageException("Unknown comm: " + comm);
+ }
+
+ /* Mark this node as failed and inform the routing table that it's unresponsive */
+ this.nodes.put(n, FAILED);
+ this.localNode.getRoutingTable().setUnresponsiveContact(n);
+ this.messagesTransiting.remove(comm);
+
+ this.askNodesorFinish();
+ }
+
+ /**
+ * @return Whether the content was found or not.
+ */
+ public boolean isContentFound()
+ {
+ return this.isContentFound;
+ }
+
+ /**
+ * @return The list of all content found during the lookup operation
+ *
+ * @throws com.github.joshuakissoon.kademlia.exceptions.ContentNotFoundException
+ */
+ public JKademliaStorageEntry getContentFound() throws ContentNotFoundException
+ {
+ if (this.isContentFound)
+ {
+ return this.contentFound;
+ }
+ else
+ {
+ throw new ContentNotFoundException("No Value was found for the given key.");
+ }
+ }
+
+ /**
+ * @return How many hops it took in order to get to the content.
+ */
+ public int routeLength()
+ {
+ return this.routeLengthChecker.getRouteLength();
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/ContentRefreshOperation.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/ContentRefreshOperation.java
new file mode 100644
index 0000000..f9df8e0
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/ContentRefreshOperation.java
@@ -0,0 +1,99 @@
+package com.github.joshuakissoon.kademlia.operation;
+
+import java.io.IOException;
+import java.util.List;
+import com.github.joshuakissoon.kademlia.KadConfiguration;
+import com.github.joshuakissoon.kademlia.KadServer;
+import com.github.joshuakissoon.kademlia.KademliaNode;
+import com.github.joshuakissoon.kademlia.dht.KademliaDHT;
+import com.github.joshuakissoon.kademlia.dht.KademliaStorageEntryMetadata;
+import com.github.joshuakissoon.kademlia.exceptions.ContentNotFoundException;
+import com.github.joshuakissoon.kademlia.message.Message;
+import com.github.joshuakissoon.kademlia.message.StoreContentMessage;
+import com.github.joshuakissoon.kademlia.node.Node;
+
+/**
+ * Refresh/Restore the data on this node by sending the data to the K-Closest nodes to the data
+ *
+ * @author Joshua Kissoon
+ * @since 20140306
+ */
+public class ContentRefreshOperation implements Operation
+{
+
+ private final KadServer server;
+ private final KademliaNode localNode;
+ private final KademliaDHT dht;
+ private final KadConfiguration config;
+
+ public ContentRefreshOperation(KadServer server, KademliaNode localNode, KademliaDHT dht, KadConfiguration config)
+ {
+ this.server = server;
+ this.localNode = localNode;
+ this.dht = dht;
+ this.config = config;
+ }
+
+ /**
+ * For each content stored on this DHT, distribute it to the K closest nodes
+ Also delete the content if this node is no longer one of the K closest nodes
+
+ We assume that our JKademliaRoutingTable is updated, and we can get the K closest nodes from that table
+ *
+ * @throws java.io.IOException
+ */
+ @Override
+ public void execute() throws IOException
+ {
+ /* Get a list of all storage entries for content */
+ List entries = this.dht.getStorageEntries();
+
+ /* If a content was last republished before this time, then we need to republish it */
+ final long minRepublishTime = (System.currentTimeMillis() / 1000L) - this.config.restoreInterval();
+
+ /* For each storage entry, distribute it */
+ for (KademliaStorageEntryMetadata e : entries)
+ {
+ /* Check last update time of this entry and only distribute it if it has been last updated > 1 hour ago */
+ if (e.lastRepublished() > minRepublishTime)
+ {
+ continue;
+ }
+
+ /* Set that this content is now republished */
+ e.updateLastRepublished();
+
+ /* Get the K closest nodes to this entries */
+ List closestNodes = this.localNode.getRoutingTable().findClosest(e.getKey(), this.config.k());
+
+ /* Create the message */
+ Message msg = new StoreContentMessage(this.localNode.getNode(), dht.get(e));
+
+ /*Store the message on all of the K-Nodes*/
+ for (Node n : closestNodes)
+ {
+ /*We don't need to again store the content locally, it's already here*/
+ if (!n.equals(this.localNode.getNode()))
+ {
+ /* Send a contentstore operation to the K-Closest nodes */
+ this.server.sendMessage(n, msg, null);
+ }
+ }
+
+ /* Delete any content on this node that this node is not one of the K-Closest nodes to */
+ try
+ {
+ if (!closestNodes.contains(this.localNode.getNode()))
+ {
+ this.dht.remove(e);
+ }
+ }
+ catch (ContentNotFoundException cnfe)
+ {
+ /* It would be weird if the content is not found here */
+ System.err.println("ContentRefreshOperation: Removing content from local node, content not found... Message: " + cnfe.getMessage());
+ }
+ }
+
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/KadRefreshOperation.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/KadRefreshOperation.java
new file mode 100644
index 0000000..664dd89
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/KadRefreshOperation.java
@@ -0,0 +1,40 @@
+package com.github.joshuakissoon.kademlia.operation;
+
+import java.io.IOException;
+import com.github.joshuakissoon.kademlia.KadConfiguration;
+import com.github.joshuakissoon.kademlia.KadServer;
+import com.github.joshuakissoon.kademlia.KademliaNode;
+import com.github.joshuakissoon.kademlia.dht.KademliaDHT;
+
+/**
+ * An operation that handles refreshing the entire Kademlia Systems including buckets and content
+ *
+ * @author Joshua Kissoon
+ * @since 20140306
+ */
+public class KadRefreshOperation implements Operation
+{
+
+ private final KadServer server;
+ private final KademliaNode localNode;
+ private final KademliaDHT dht;
+ private final KadConfiguration config;
+
+ public KadRefreshOperation(KadServer server, KademliaNode localNode, KademliaDHT dht, KadConfiguration config)
+ {
+ this.server = server;
+ this.localNode = localNode;
+ this.dht = dht;
+ this.config = config;
+ }
+
+ @Override
+ public void execute() throws IOException
+ {
+ /* Run our BucketRefreshOperation to refresh buckets */
+ new BucketRefreshOperation(this.server, this.localNode, this.config).execute();
+
+ /* After buckets have been refreshed, we refresh content */
+ new ContentRefreshOperation(this.server, this.localNode, this.dht, this.config).execute();
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/NodeLookupOperation.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/NodeLookupOperation.java
new file mode 100644
index 0000000..f0e3621
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/NodeLookupOperation.java
@@ -0,0 +1,323 @@
+package com.github.joshuakissoon.kademlia.operation;
+
+import com.github.joshuakissoon.kademlia.message.Receiver;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import com.github.joshuakissoon.kademlia.KadConfiguration;
+import com.github.joshuakissoon.kademlia.KadServer;
+import com.github.joshuakissoon.kademlia.KademliaNode;
+import com.github.joshuakissoon.kademlia.exceptions.RoutingException;
+import com.github.joshuakissoon.kademlia.message.Message;
+import com.github.joshuakissoon.kademlia.message.NodeLookupMessage;
+import com.github.joshuakissoon.kademlia.message.NodeReplyMessage;
+import com.github.joshuakissoon.kademlia.node.KeyComparator;
+import com.github.joshuakissoon.kademlia.node.Node;
+import com.github.joshuakissoon.kademlia.node.KademliaId;
+
+/**
+ * Finds the K closest nodes to a specified identifier
+ * The algorithm terminates when it has gotten responses from the K closest nodes it has seen.
+ * Nodes that fail to respond are removed from consideration
+ *
+ * @author Joshua Kissoon
+ * @created 20140219
+ */
+public class NodeLookupOperation implements Operation, Receiver
+{
+
+ /* Constants */
+ private static final String UNASKED = "UnAsked";
+ private static final String AWAITING = "Awaiting";
+ private static final String ASKED = "Asked";
+ private static final String FAILED = "Failed";
+
+ private final KadServer server;
+ private final KademliaNode localNode;
+ private final KadConfiguration config;
+
+ private final Message lookupMessage; // Message sent to each peer
+ private final Map nodes;
+
+ /* Tracks messages in transit and awaiting reply */
+ private final Map messagesTransiting;
+
+ /* Used to sort nodes */
+ private final Comparator comparator;
+
+
+ {
+ messagesTransiting = new HashMap<>();
+ }
+
+ /**
+ * @param server KadServer used for communication
+ * @param localNode The local node making the communication
+ * @param lookupId The ID for which to find nodes close to
+ * @param config
+ */
+ public NodeLookupOperation(KadServer server, KademliaNode localNode, KademliaId lookupId, KadConfiguration config)
+ {
+ this.server = server;
+ this.localNode = localNode;
+ this.config = config;
+
+ this.lookupMessage = new NodeLookupMessage(localNode.getNode(), lookupId);
+
+ /**
+ * We initialize a TreeMap to store nodes.
+ * This map will be sorted by which nodes are closest to the lookupId
+ */
+ this.comparator = new KeyComparator(lookupId);
+ this.nodes = new TreeMap(this.comparator);
+ }
+
+ /**
+ * @throws java.io.IOException
+ * @throws com.github.joshuakissoon.kademlia.exceptions.RoutingException
+ */
+ @Override
+ public synchronized void execute() throws IOException, RoutingException
+ {
+ try
+ {
+ /* Set the local node as already asked */
+ nodes.put(this.localNode.getNode(), ASKED);
+
+ /**
+ * We add all nodes here instead of the K-Closest because there may be the case that the K-Closest are offline
+ * - The operation takes care of looking at the K-Closest.
+ */
+ this.addNodes(this.localNode.getRoutingTable().getAllNodes());
+
+ /* If we haven't finished as yet, wait for a maximum of config.operationTimeout() time */
+ int totalTimeWaited = 0;
+ int timeInterval = 10; // We re-check every n milliseconds
+ while (totalTimeWaited < this.config.operationTimeout())
+ {
+ if (!this.askNodesorFinish())
+ {
+ wait(timeInterval);
+ totalTimeWaited += timeInterval;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ /* Now after we've finished, we would have an idea of offline nodes, lets update our routing table */
+ this.localNode.getRoutingTable().setUnresponsiveContacts(this.getFailedNodes());
+
+ }
+ catch (InterruptedException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public List getClosestNodes()
+ {
+ return this.closestNodes(ASKED);
+ }
+
+ /**
+ * Add nodes from this list to the set of nodes to lookup
+ *
+ * @param list The list from which to add nodes
+ */
+ public void addNodes(List list)
+ {
+ for (Node o : list)
+ {
+ /* If this node is not in the list, add the node */
+ if (!nodes.containsKey(o))
+ {
+ nodes.put(o, UNASKED);
+ }
+ }
+ }
+
+ /**
+ * Asks some of the K closest nodes seen but not yet queried.
+ * Assures that no more than DefaultConfiguration.CONCURRENCY messages are in transit at a time
+ *
+ * This method should be called every time a reply is received or a timeout occurs.
+ *
+ * If all K closest nodes have been asked and there are no messages in transit,
+ * the algorithm is finished.
+ *
+ * @return true
if finished OR false
otherwise
+ */
+ private boolean askNodesorFinish() throws IOException
+ {
+ /* If >= CONCURRENCY nodes are in transit, don't do anything */
+ if (this.config.maxConcurrentMessagesTransiting() <= this.messagesTransiting.size())
+ {
+ return false;
+ }
+
+ /* Get unqueried nodes among the K closest seen that have not FAILED */
+ List unasked = this.closestNodesNotFailed(UNASKED);
+
+ if (unasked.isEmpty() && this.messagesTransiting.isEmpty())
+ {
+ /* We have no unasked nodes nor any messages in transit, we're finished! */
+ return true;
+ }
+
+ /**
+ * Send messages to nodes in the list;
+ * making sure than no more than CONCURRENCY messsages are in transit
+ */
+ for (int i = 0; (this.messagesTransiting.size() < this.config.maxConcurrentMessagesTransiting()) && (i < unasked.size()); i++)
+ {
+ Node n = (Node) unasked.get(i);
+
+ int comm = server.sendMessage(n, lookupMessage, this);
+
+ this.nodes.put(n, AWAITING);
+ this.messagesTransiting.put(comm, n);
+ }
+
+ /* We're not finished as yet, return false */
+ return false;
+ }
+
+ /**
+ * @param status The status of the nodes to return
+ *
+ * @return The K closest nodes to the target lookupId given that have the specified status
+ */
+ private List closestNodes(String status)
+ {
+ List closestNodes = new ArrayList<>(this.config.k());
+ int remainingSpaces = this.config.k();
+
+ for (Map.Entry e : this.nodes.entrySet())
+ {
+ if (status.equals(e.getValue()))
+ {
+ /* We got one with the required status, now add it */
+ closestNodes.add((Node) e.getKey());
+ if (--remainingSpaces == 0)
+ {
+ break;
+ }
+ }
+ }
+
+ return closestNodes;
+ }
+
+ /**
+ * Find The K closest nodes to the target lookupId given that have not FAILED.
+ * From those K, get those that have the specified status
+ *
+ * @param status The status of the nodes to return
+ *
+ * @return A List of the closest nodes
+ */
+ private List closestNodesNotFailed(String status)
+ {
+ List closestNodes = new ArrayList<>(this.config.k());
+ int remainingSpaces = this.config.k();
+
+ for (Map.Entry e : this.nodes.entrySet())
+ {
+ if (!FAILED.equals(e.getValue()))
+ {
+ if (status.equals(e.getValue()))
+ {
+ /* We got one with the required status, now add it */
+ closestNodes.add(e.getKey());
+ }
+
+ if (--remainingSpaces == 0)
+ {
+ break;
+ }
+ }
+ }
+
+ return closestNodes;
+ }
+
+ /**
+ * Receive and handle the incoming NodeReplyMessage
+ *
+ * @param comm
+ *
+ * @throws java.io.IOException
+ */
+ @Override
+ public synchronized void receive(Message incoming, int comm) throws IOException
+ {
+ if (!(incoming instanceof NodeReplyMessage))
+ {
+ /* Not sure why we get a message of a different type here... @todo Figure it out. */
+ return;
+ }
+ /* We receive a NodeReplyMessage with a set of nodes, read this message */
+ NodeReplyMessage msg = (NodeReplyMessage) incoming;
+
+ /* Add the origin node to our routing table */
+ Node origin = msg.getOrigin();
+ this.localNode.getRoutingTable().insert(origin);
+
+ /* Set that we've completed ASKing the origin node */
+ this.nodes.put(origin, ASKED);
+
+ /* Remove this msg from messagesTransiting since it's completed now */
+ this.messagesTransiting.remove(comm);
+
+ /* Add the received nodes to our nodes list to query */
+ this.addNodes(msg.getNodes());
+ this.askNodesorFinish();
+ }
+
+ /**
+ * A node does not respond or a packet was lost, we set this node as failed
+ *
+ * @param comm
+ *
+ * @throws java.io.IOException
+ */
+ @Override
+ public synchronized void timeout(int comm) throws IOException
+ {
+ /* Get the node associated with this communication */
+ Node n = this.messagesTransiting.get(comm);
+
+ if (n == null)
+ {
+ return;
+ }
+
+ /* Mark this node as failed and inform the routing table that it is unresponsive */
+ this.nodes.put(n, FAILED);
+ this.localNode.getRoutingTable().setUnresponsiveContact(n);
+ this.messagesTransiting.remove(comm);
+
+ this.askNodesorFinish();
+ }
+
+ public List getFailedNodes()
+ {
+ List failedNodes = new ArrayList<>();
+
+ for (Map.Entry e : this.nodes.entrySet())
+ {
+ if (e.getValue().equals(FAILED))
+ {
+ failedNodes.add(e.getKey());
+ }
+ }
+
+ return failedNodes;
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/Operation.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/Operation.java
new file mode 100644
index 0000000..e491870
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/Operation.java
@@ -0,0 +1,21 @@
+package com.github.joshuakissoon.kademlia.operation;
+
+import java.io.IOException;
+import com.github.joshuakissoon.kademlia.exceptions.RoutingException;
+
+/**
+ * An operation in the Kademlia routing protocol
+ *
+ * @author Joshua Kissoon
+ * @created 20140218
+ */
+public interface Operation
+{
+
+ /**
+ * Starts an operation and returns when the operation is finished
+ *
+ * @throws com.github.joshuakissoon.kademlia.exceptions.RoutingException
+ */
+ public void execute() throws IOException, RoutingException;
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/PingOperation.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/PingOperation.java
new file mode 100644
index 0000000..33daad4
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/PingOperation.java
@@ -0,0 +1,39 @@
+/**
+ * Implementation of the Kademlia Ping operation,
+ * This is on hold at the moment since I'm not sure if we'll use ping given the improvements mentioned in the paper.
+ *
+ * @author Joshua Kissoon
+ * @since 20140218
+ */
+package com.github.joshuakissoon.kademlia.operation;
+
+import java.io.IOException;
+import com.github.joshuakissoon.kademlia.KadServer;
+import com.github.joshuakissoon.kademlia.exceptions.RoutingException;
+import com.github.joshuakissoon.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 void execute() throws IOException, RoutingException
+ {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/StoreOperation.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/StoreOperation.java
new file mode 100644
index 0000000..852e2aa
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/operation/StoreOperation.java
@@ -0,0 +1,83 @@
+package com.github.joshuakissoon.kademlia.operation;
+
+import java.io.IOException;
+import java.util.List;
+import com.github.joshuakissoon.kademlia.KadConfiguration;
+import com.github.joshuakissoon.kademlia.KadServer;
+import com.github.joshuakissoon.kademlia.KademliaNode;
+import com.github.joshuakissoon.kademlia.dht.JKademliaStorageEntry;
+import com.github.joshuakissoon.kademlia.dht.KademliaDHT;
+import com.github.joshuakissoon.kademlia.message.Message;
+import com.github.joshuakissoon.kademlia.message.StoreContentMessage;
+import com.github.joshuakissoon.kademlia.node.Node;
+
+/**
+ * Operation that stores a DHT Content onto the K closest nodes to the content Key
+ *
+ * @author Joshua Kissoon
+ * @since 20140224
+ */
+public class StoreOperation implements Operation
+{
+
+ private final KadServer server;
+ private final KademliaNode localNode;
+ private final JKademliaStorageEntry storageEntry;
+ private final KademliaDHT localDht;
+ private final KadConfiguration config;
+
+ /**
+ * @param server
+ * @param localNode
+ * @param storageEntry The content to be stored on the DHT
+ * @param localDht The local DHT
+ * @param config
+ */
+ public StoreOperation(KadServer server, KademliaNode localNode, JKademliaStorageEntry storageEntry, KademliaDHT localDht, KadConfiguration config)
+ {
+ this.server = server;
+ this.localNode = localNode;
+ this.storageEntry = storageEntry;
+ this.localDht = localDht;
+ this.config = config;
+ }
+
+ @Override
+ public synchronized void execute() throws IOException
+ {
+ /* Get the nodes on which we need to store the content */
+ NodeLookupOperation ndlo = new NodeLookupOperation(this.server, this.localNode, this.storageEntry.getContentMetadata().getKey(), this.config);
+ ndlo.execute();
+ List nodes = ndlo.getClosestNodes();
+
+ /* Create the message */
+ Message msg = new StoreContentMessage(this.localNode.getNode(), this.storageEntry);
+
+ /*Store the message on all of the K-Nodes*/
+ for (Node n : nodes)
+ {
+ if (n.equals(this.localNode.getNode()))
+ {
+ /* Store the content locally */
+ this.localDht.store(this.storageEntry);
+ }
+ else
+ {
+ /**
+ * @todo Create a receiver that receives a store acknowledgement message to count how many nodes a content have been stored at
+ */
+ this.server.sendMessage(n, msg, null);
+ }
+ }
+ }
+
+ /**
+ * @return The number of nodes that have stored this content
+ *
+ * @todo Implement this method
+ */
+ public int numNodesStoredAt()
+ {
+ return 1;
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/routing/Contact.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/routing/Contact.java
new file mode 100644
index 0000000..2526c82
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/routing/Contact.java
@@ -0,0 +1,118 @@
+package com.github.joshuakissoon.kademlia.routing;
+
+import com.github.joshuakissoon.kademlia.node.Node;
+
+/**
+ * Keeps information about contacts of the Node; Contacts are stored in the Buckets in the Routing Table.
+ *
+ * Contacts are used instead of nodes because more information is needed than just the node information.
+ * - Information such as
+ * -- Last seen time
+ *
+ * @author Joshua Kissoon
+ * @since 20140425
+ * @updated 20140426
+ */
+public class Contact implements Comparable
+{
+
+ private final Node n;
+ private long lastSeen;
+
+ /**
+ * Stale as described by Kademlia paper page 64
+ * When a contact fails to respond, if the replacement cache is empty and there is no replacement for the contact,
+ * just mark it as stale.
+ *
+ * Now when a new contact is added, if the contact is stale, it is removed.
+ */
+ private int staleCount;
+
+ /**
+ * Create a contact object
+ *
+ * @param n The node associated with this contact
+ */
+ public Contact(Node n)
+ {
+ this.n = n;
+ this.lastSeen = System.currentTimeMillis() / 1000L;
+ }
+
+ public Node getNode()
+ {
+ return this.n;
+ }
+
+ /**
+ * When a Node sees a contact a gain, the Node will want to update that it's seen recently,
+ * this method updates the last seen timestamp for this contact.
+ */
+ public void setSeenNow()
+ {
+ this.lastSeen = System.currentTimeMillis() / 1000L;
+ }
+
+ /**
+ * When last was this contact seen?
+ *
+ * @return long The last time this contact was seen.
+ */
+ public long lastSeen()
+ {
+ return this.lastSeen;
+ }
+
+ @Override
+ public boolean equals(Object c)
+ {
+ if (c instanceof Contact)
+ {
+ return ((Contact) c).getNode().equals(this.getNode());
+ }
+
+ return false;
+ }
+
+ /**
+ * Increments the amount of times this count has failed to respond to a request.
+ */
+ public void incrementStaleCount()
+ {
+ staleCount++;
+ }
+
+ /**
+ * @return Integer Stale count
+ */
+ public int staleCount()
+ {
+ return this.staleCount;
+ }
+
+ /**
+ * Reset the stale count of the contact if it's recently seen
+ */
+ public void resetStaleCount()
+ {
+ this.staleCount = 0;
+ }
+
+ @Override
+ public int compareTo(Contact o)
+ {
+ if (this.getNode().equals(o.getNode()))
+ {
+ return 0;
+ }
+
+ return (this.lastSeen() > o.lastSeen()) ? 1 : -1;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return this.getNode().hashCode();
+ }
+
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/routing/ContactLastSeenComparator.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/routing/ContactLastSeenComparator.java
new file mode 100644
index 0000000..1f00194
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/routing/ContactLastSeenComparator.java
@@ -0,0 +1,34 @@
+package com.github.joshuakissoon.kademlia.routing;
+
+import java.util.Comparator;
+
+/**
+ * A Comparator to compare 2 contacts by their last seen time
+ *
+ * @author Joshua Kissoon
+ * @since 20140426
+ */
+public class ContactLastSeenComparator implements Comparator
+{
+
+ /**
+ * Compare two contacts to determine their order in the Bucket,
+ * Contacts are ordered by their last seen timestamp.
+ *
+ * @param c1 Contact 1
+ * @param c2 Contact 2
+ */
+ @Override
+ public int compare(Contact c1, Contact c2)
+ {
+ if (c1.getNode().equals(c2.getNode()))
+ {
+ return 0;
+ }
+ else
+ {
+ /* We may have 2 different contacts with same last seen values so we can't return 0 here */
+ return c1.lastSeen() > c2.lastSeen() ? 1 : -1;
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/routing/JKademliaBucket.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/routing/JKademliaBucket.java
new file mode 100644
index 0000000..8492346
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/routing/JKademliaBucket.java
@@ -0,0 +1,275 @@
+package com.github.joshuakissoon.kademlia.routing;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.TreeSet;
+import com.github.joshuakissoon.kademlia.KadConfiguration;
+import com.github.joshuakissoon.kademlia.node.Node;
+
+/**
+ * A bucket in the Kademlia routing table
+ *
+ * @author Joshua Kissoon
+ * @created 20140215
+ */
+public class JKademliaBucket implements KademliaBucket
+{
+
+ /* How deep is this bucket in the Routing Table */
+ private final int depth;
+
+ /* Contacts stored in this routing table */
+ private final TreeSet contacts;
+
+ /* A set of last seen contacts that can replace any current contact that is unresponsive */
+ private final TreeSet replacementCache;
+
+ private final KadConfiguration config;
+
+
+ {
+ contacts = new TreeSet<>();
+ replacementCache = new TreeSet<>();
+ }
+
+ /**
+ * @param depth How deep in the routing tree is this bucket
+ * @param config
+ */
+ public JKademliaBucket(int depth, KadConfiguration config)
+ {
+ this.depth = depth;
+ this.config = config;
+ }
+
+ @Override
+ public synchronized void insert(Contact c)
+ {
+ if (this.contacts.contains(c))
+ {
+ /**
+ * If the contact is already in the bucket, lets update that we've seen it
+ * We need to remove and re-add the contact to get the Sorted Set to update sort order
+ */
+ Contact tmp = this.removeFromContacts(c.getNode());
+ tmp.setSeenNow();
+ tmp.resetStaleCount();
+ this.contacts.add(tmp);
+ }
+ else
+ {
+ /* If the bucket is filled, so put the contacts in the replacement cache */
+ if (contacts.size() >= this.config.k())
+ {
+ /* If the cache is empty, we check if any contacts are stale and replace the stalest one */
+ Contact stalest = null;
+ for (Contact tmp : this.contacts)
+ {
+ if (tmp.staleCount() >= this.config.stale())
+ {
+ /* Contact is stale */
+ if (stalest == null)
+ {
+ stalest = tmp;
+ }
+ else if (tmp.staleCount() > stalest.staleCount())
+ {
+ stalest = tmp;
+ }
+ }
+ }
+
+ /* If we have a stale contact, remove it and add the new contact to the bucket */
+ if (stalest != null)
+ {
+ this.contacts.remove(stalest);
+ this.contacts.add(c);
+ }
+ else
+ {
+ /* No stale contact, lets insert this into replacement cache */
+ this.insertIntoReplacementCache(c);
+ }
+ }
+ else
+ {
+ this.contacts.add(c);
+ }
+ }
+ }
+
+ @Override
+ public synchronized void insert(Node n)
+ {
+ this.insert(new Contact(n));
+ }
+
+ @Override
+ public synchronized boolean containsContact(Contact c)
+ {
+ return this.contacts.contains(c);
+ }
+
+ @Override
+ public synchronized boolean containsNode(Node n)
+ {
+ return this.containsContact(new Contact(n));
+ }
+
+ @Override
+ public synchronized boolean removeContact(Contact c)
+ {
+ /* If the contact does not exist, then we failed to remove it */
+ if (!this.contacts.contains(c))
+ {
+ return false;
+ }
+
+ /* Contact exist, lets remove it only if our replacement cache has a replacement */
+ if (!this.replacementCache.isEmpty())
+ {
+ /* Replace the contact with one from the replacement cache */
+ this.contacts.remove(c);
+ Contact replacement = this.replacementCache.first();
+ this.contacts.add(replacement);
+ this.replacementCache.remove(replacement);
+ }
+ else
+ {
+ /* There is no replacement, just increment the contact's stale count */
+ this.getFromContacts(c.getNode()).incrementStaleCount();
+ }
+
+ return true;
+ }
+
+ private synchronized Contact getFromContacts(Node n)
+ {
+ for (Contact c : this.contacts)
+ {
+ if (c.getNode().equals(n))
+ {
+ return c;
+ }
+ }
+
+ /* This contact does not exist */
+ throw new NoSuchElementException("The contact does not exist in the contacts list.");
+ }
+
+ private synchronized Contact removeFromContacts(Node n)
+ {
+ for (Contact c : this.contacts)
+ {
+ if (c.getNode().equals(n))
+ {
+ this.contacts.remove(c);
+ return c;
+ }
+ }
+
+ /* We got here means this element does not exist */
+ throw new NoSuchElementException("Node does not exist in the replacement cache. ");
+ }
+
+ @Override
+ public synchronized boolean removeNode(Node n)
+ {
+ return this.removeContact(new Contact(n));
+ }
+
+ @Override
+ public synchronized int numContacts()
+ {
+ return this.contacts.size();
+ }
+
+ @Override
+ public synchronized int getDepth()
+ {
+ return this.depth;
+ }
+
+ @Override
+ public synchronized List getContacts()
+ {
+ final ArrayList ret = new ArrayList<>();
+
+ /* If we have no contacts, return the blank arraylist */
+ if (this.contacts.isEmpty())
+ {
+ return ret;
+ }
+
+ /* We have contacts, lets copy put them into the arraylist and return */
+ for (Contact c : this.contacts)
+ {
+ ret.add(c);
+ }
+
+ return ret;
+ }
+
+ /**
+ * When the bucket is filled, we keep extra contacts in the replacement cache.
+ */
+ private synchronized void insertIntoReplacementCache(Contact c)
+ {
+ /* Just return if this contact is already in our replacement cache */
+ if (this.replacementCache.contains(c))
+ {
+ /**
+ * If the contact is already in the bucket, lets update that we've seen it
+ * We need to remove and re-add the contact to get the Sorted Set to update sort order
+ */
+ Contact tmp = this.removeFromReplacementCache(c.getNode());
+ tmp.setSeenNow();
+ this.replacementCache.add(tmp);
+ }
+ else if (this.replacementCache.size() > this.config.k())
+ {
+ /* if our cache is filled, we remove the least recently seen contact */
+ this.replacementCache.remove(this.replacementCache.last());
+ this.replacementCache.add(c);
+ }
+ else
+ {
+ this.replacementCache.add(c);
+ }
+ }
+
+ private synchronized Contact removeFromReplacementCache(Node n)
+ {
+ for (Contact c : this.replacementCache)
+ {
+ if (c.getNode().equals(n))
+ {
+ this.replacementCache.remove(c);
+ return c;
+ }
+ }
+
+ /* We got here means this element does not exist */
+ throw new NoSuchElementException("Node does not exist in the replacement cache. ");
+ }
+
+ @Override
+ public synchronized String toString()
+ {
+ StringBuilder sb = new StringBuilder("Bucket at depth: ");
+ sb.append(this.depth);
+ sb.append("\n Nodes: \n");
+ for (Contact n : this.contacts)
+ {
+ sb.append("Node: ");
+ sb.append(n.getNode().getNodeId().toString());
+ sb.append(" (stale: ");
+ sb.append(n.staleCount());
+ sb.append(")");
+ sb.append("\n");
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/routing/JKademliaRoutingTable.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/routing/JKademliaRoutingTable.java
new file mode 100644
index 0000000..e24bb4f
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/routing/JKademliaRoutingTable.java
@@ -0,0 +1,238 @@
+package com.github.joshuakissoon.kademlia.routing;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.TreeSet;
+import com.github.joshuakissoon.kademlia.KadConfiguration;
+import com.github.joshuakissoon.kademlia.node.KeyComparator;
+import com.github.joshuakissoon.kademlia.node.Node;
+import com.github.joshuakissoon.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 findClosest(KademliaId target, int numNodesRequired)
+ {
+ TreeSet sortedSet = new TreeSet<>(new KeyComparator(target));
+ sortedSet.addAll(this.getAllNodes());
+
+ List 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 getAllNodes()
+ {
+ List 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 getAllContacts()
+ {
+ List 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 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();
+ }
+
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/routing/KademliaBucket.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/routing/KademliaBucket.java
new file mode 100644
index 0000000..af5012f
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/routing/KademliaBucket.java
@@ -0,0 +1,87 @@
+package com.github.joshuakissoon.kademlia.routing;
+
+import java.util.List;
+import com.github.joshuakissoon.kademlia.node.Node;
+
+/**
+ * A bucket used to store Contacts in the routing table.
+ *
+ * @author Joshua Kissoon
+ * @created 20140215
+ */
+public interface KademliaBucket
+{
+
+ /**
+ * Adds a contact to the bucket
+ *
+ * @param c the new contact
+ */
+ public void insert(Contact c);
+
+ /**
+ * Create a new contact and insert it into the bucket.
+ *
+ * @param n The node to create the contact from
+ */
+ public void insert(Node n);
+
+ /**
+ * Checks if this bucket contain a contact
+ *
+ * @param c The contact to check for
+ *
+ * @return boolean
+ */
+ public boolean containsContact(Contact c);
+
+ /**
+ * Checks if this bucket contain a node
+ *
+ * @param n The node to check for
+ *
+ * @return boolean
+ */
+ public boolean containsNode(Node n);
+
+ /**
+ * Remove a contact from this bucket.
+ *
+ * If there are replacement contacts in the replacement cache,
+ * select the last seen one and put it into the bucket while removing the required contact.
+ *
+ * If there are no contacts in the replacement cache, then we just mark the contact requested to be removed as stale.
+ * Marking as stale would actually be incrementing the stale count of the contact.
+ *
+ * @param c The contact to remove
+ *
+ * @return Boolean whether the removal was successful.
+ */
+ public boolean removeContact(Contact c);
+
+ /**
+ * Remove the contact object related to a node from this bucket
+ *
+ * @param n The node of the contact to remove
+ *
+ * @return Boolean whether the removal was successful.
+ */
+ public boolean removeNode(Node n);
+
+ /**
+ * Counts the number of contacts in this bucket.
+ *
+ * @return Integer The number of contacts in this bucket
+ */
+ public int numContacts();
+
+ /**
+ * @return Integer The depth of this bucket in the RoutingTable
+ */
+ public int getDepth();
+
+ /**
+ * @return An Iterable structure with all contacts in this bucket
+ */
+ public List getContacts();
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/routing/KademliaRoutingTable.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/routing/KademliaRoutingTable.java
new file mode 100644
index 0000000..7129421
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/routing/KademliaRoutingTable.java
@@ -0,0 +1,91 @@
+package com.github.joshuakissoon.kademlia.routing;
+
+import java.util.List;
+import com.github.joshuakissoon.kademlia.KadConfiguration;
+import com.github.joshuakissoon.kademlia.node.Node;
+import com.github.joshuakissoon.kademlia.node.KademliaId;
+
+/**
+ * Specification for Kademlia's Routing Table
+ *
+ * @author Joshua Kissoon
+ * @since 20140501
+ */
+public interface KademliaRoutingTable
+{
+
+ /**
+ * Initialize the RoutingTable to it's default state
+ */
+ public void initialize();
+
+ /**
+ * Sets the configuration file for this routing table
+ *
+ * @param config
+ */
+ public void setConfiguration(KadConfiguration config);
+
+ /**
+ * Adds a contact to the routing table based on how far it is from the LocalNode.
+ *
+ * @param c The contact to add
+ */
+ public void insert(Contact c);
+
+ /**
+ * Adds a node to the routing table based on how far it is from the LocalNode.
+ *
+ * @param n The node to add
+ */
+ public void insert(Node 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.
+ */
+ public int getBucketId(KademliaId nid);
+
+ /**
+ * 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
+ */
+ public List findClosest(KademliaId target, int numNodesRequired);
+
+ /**
+ * @return List A List of all Nodes in this RoutingTable
+ */
+ public List getAllNodes();
+
+ /**
+ * @return List A List of all Nodes in this RoutingTable
+ */
+ public List getAllContacts();
+
+ /**
+ * @return Bucket[] The buckets in this Kad Instance
+ */
+ public KademliaBucket[] getBuckets();
+
+ /**
+ * Method used by operations to notify the routing table of any contacts that have been unresponsive.
+ *
+ * @param contacts The set of unresponsive contacts
+ */
+ public void setUnresponsiveContacts(List contacts);
+
+ /**
+ * Method used by operations to notify the routing table of any contacts that have been unresponsive.
+ *
+ * @param n
+ */
+ public void setUnresponsiveContact(Node n);
+
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/AutoRefreshOperation.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/AutoRefreshOperation.java
new file mode 100644
index 0000000..c9b7a51
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/AutoRefreshOperation.java
@@ -0,0 +1,83 @@
+package com.github.joshuakissoon.kademlia.simulations;
+
+import java.util.Timer;
+import java.util.TimerTask;
+import com.github.joshuakissoon.kademlia.DefaultConfiguration;
+import com.github.joshuakissoon.kademlia.JKademliaNode;
+import com.github.joshuakissoon.kademlia.KadConfiguration;
+import com.github.joshuakissoon.kademlia.node.KademliaId;
+
+/**
+ * Testing the Kademlia Auto Content and Node table refresh operations
+ *
+ * @author Joshua Kissoon
+ * @since 20140309
+ */
+public class AutoRefreshOperation implements Simulation
+{
+
+ @Override
+ public void runSimulation()
+ {
+ try
+ {
+ /* Setting up 2 Kad networks */
+ final JKademliaNode kad1 = new JKademliaNode("JoshuaK", new KademliaId("ASF456789djem45674DH"), 12049);
+ final JKademliaNode kad2 = new JKademliaNode("Crystal", new KademliaId("AJDHR678947584567464"), 4585);
+ final JKademliaNode kad3 = new JKademliaNode("Shameer", new KademliaId("AS84k6789KRNS45KFJ8W"), 8104);
+ final JKademliaNode kad4 = new JKademliaNode("Lokesh.", new KademliaId("ASF45678947A845674GG"), 8335);
+ final JKademliaNode kad5 = new JKademliaNode("Chandu.", new KademliaId("AS84kUD894758456dyrj"), 13345);
+
+ /* Connecting nodes */
+ System.out.println("Connecting Nodes");
+ kad2.bootstrap(kad1.getNode());
+ kad3.bootstrap(kad2.getNode());
+ kad4.bootstrap(kad2.getNode());
+ kad5.bootstrap(kad4.getNode());
+
+ DHTContentImpl c = new DHTContentImpl(new KademliaId("AS84k678947584567465"), kad1.getOwnerId());
+ c.setData("Setting the data");
+
+ System.out.println("\n Content ID: " + c.getKey());
+ System.out.println(kad1.getNode() + " Distance from content: " + kad1.getNode().getNodeId().getDistance(c.getKey()));
+ System.out.println(kad2.getNode() + " Distance from content: " + kad2.getNode().getNodeId().getDistance(c.getKey()));
+ System.out.println(kad3.getNode() + " Distance from content: " + kad3.getNode().getNodeId().getDistance(c.getKey()));
+ System.out.println(kad4.getNode() + " Distance from content: " + kad4.getNode().getNodeId().getDistance(c.getKey()));
+ System.out.println(kad5.getNode() + " Distance from content: " + kad5.getNode().getNodeId().getDistance(c.getKey()));
+ System.out.println("\nSTORING CONTENT 1 locally on " + kad1.getOwnerId() + "\n\n\n\n");
+
+ kad1.putLocally(c);
+
+ System.out.println(kad1);
+ System.out.println(kad2);
+ System.out.println(kad3);
+ System.out.println(kad4);
+ System.out.println(kad5);
+
+ /* Print the node states every few minutes */
+ KadConfiguration config = new DefaultConfiguration();
+ Timer timer = new Timer(true);
+ timer.schedule(
+ new TimerTask()
+ {
+ @Override
+ public void run()
+ {
+ System.out.println(kad1);
+ System.out.println(kad2);
+ System.out.println(kad3);
+ System.out.println(kad4);
+ System.out.println(kad5);
+ }
+ },
+ // Delay // Interval
+ config.restoreInterval(), config.restoreInterval()
+ );
+ }
+
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/AutoRefreshOperation2.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/AutoRefreshOperation2.java
new file mode 100644
index 0000000..6e8ae6d
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/AutoRefreshOperation2.java
@@ -0,0 +1,72 @@
+package com.github.joshuakissoon.kademlia.simulations;
+
+import java.util.Timer;
+import java.util.TimerTask;
+import com.github.joshuakissoon.kademlia.DefaultConfiguration;
+import com.github.joshuakissoon.kademlia.JKademliaNode;
+import com.github.joshuakissoon.kademlia.KadConfiguration;
+import com.github.joshuakissoon.kademlia.node.KademliaId;
+
+/**
+ * Testing the Kademlia Auto Content and Node table refresh operations
+ *
+ * @author Joshua Kissoon
+ * @since 20140309
+ */
+public class AutoRefreshOperation2 implements Simulation
+{
+
+ @Override
+ public void runSimulation()
+ {
+ try
+ {
+ /* Setting up 2 Kad networks */
+ final JKademliaNode kad1 = new JKademliaNode("JoshuaK", new KademliaId("ASF456789djem4567463"), 12049);
+ final JKademliaNode kad2 = new JKademliaNode("Crystal", new KademliaId("AS84k678DJRW84567465"), 4585);
+ final JKademliaNode kad3 = new JKademliaNode("Shameer", new KademliaId("AS84k67894758456746A"), 8104);
+
+ /* Connecting nodes */
+ System.out.println("Connecting Nodes");
+ kad2.bootstrap(kad1.getNode());
+ kad3.bootstrap(kad2.getNode());
+
+ DHTContentImpl c = new DHTContentImpl(new KademliaId("AS84k678947584567465"), kad1.getOwnerId());
+ c.setData("Setting the data");
+ kad1.putLocally(c);
+
+ System.out.println("\n Content ID: " + c.getKey());
+ System.out.println(kad1.getNode() + " Distance from content: " + kad1.getNode().getNodeId().getDistance(c.getKey()));
+ System.out.println(kad2.getNode() + " Distance from content: " + kad2.getNode().getNodeId().getDistance(c.getKey()));
+ System.out.println(kad3.getNode() + " Distance from content: " + kad3.getNode().getNodeId().getDistance(c.getKey()));
+ System.out.println("\nSTORING CONTENT 1 locally on " + kad1.getOwnerId() + "\n\n\n\n");
+
+ System.out.println(kad1);
+ System.out.println(kad2);
+ System.out.println(kad3);
+
+ /* Print the node states every few minutes */
+ KadConfiguration config = new DefaultConfiguration();
+ Timer timer = new Timer(true);
+ timer.schedule(
+ new TimerTask()
+ {
+ @Override
+ public void run()
+ {
+ System.out.println(kad1);
+ System.out.println(kad2);
+ System.out.println(kad3);
+ }
+ },
+ // Delay // Interval
+ config.restoreInterval(), config.restoreInterval()
+ );
+ }
+
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/ContentSendingTest.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/ContentSendingTest.java
new file mode 100644
index 0000000..3e67afb
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/ContentSendingTest.java
@@ -0,0 +1,60 @@
+package com.github.joshuakissoon.kademlia.simulations;
+
+import java.io.IOException;
+import java.util.UUID;
+import com.github.joshuakissoon.kademlia.dht.GetParameter;
+import com.github.joshuakissoon.kademlia.JKademliaNode;
+import com.github.joshuakissoon.kademlia.dht.KademliaStorageEntry;
+import com.github.joshuakissoon.kademlia.exceptions.ContentNotFoundException;
+import com.github.joshuakissoon.kademlia.node.KademliaId;
+
+/**
+ * Testing sending and receiving content between 2 Nodes on a network
+ *
+ * @author Joshua Kissoon
+ * @since 20140224
+ */
+public class ContentSendingTest
+{
+
+ public static void main(String[] args)
+ {
+ try
+ {
+ /* Setting up 2 Kad networks */
+ JKademliaNode kad1 = new JKademliaNode("JoshuaK", new KademliaId("ASF45678947584567467"), 7574);
+ System.out.println("Created Node Kad 1: " + kad1.getNode().getNodeId());
+ JKademliaNode kad2 = new JKademliaNode("Crystal", new KademliaId("ASERTKJDHGVHERJHGFLK"), 7572);
+ System.out.println("Created Node Kad 2: " + kad2.getNode().getNodeId());
+ kad2.bootstrap(kad1.getNode());
+
+ /**
+ * Lets create the content and share it
+ */
+ String data = "";
+ for (int i = 0; i < 500; i++)
+ {
+ data += UUID.randomUUID();
+ }
+ System.out.println(data);
+ DHTContentImpl c = new DHTContentImpl(kad2.getOwnerId(), data);
+ kad2.put(c);
+
+ /**
+ * Lets retrieve the content
+ */
+ System.out.println("Retrieving Content");
+ GetParameter gp = new GetParameter(c.getKey(), DHTContentImpl.TYPE);
+ gp.setOwnerId(c.getOwnerId());
+ System.out.println("Get Parameter: " + gp);
+ KademliaStorageEntry conte = kad2.get(gp);
+ System.out.println("Content Found: " + new DHTContentImpl().fromSerializedForm(conte.getContent()));
+ System.out.println("Content Metadata: " + conte.getContentMetadata());
+
+ }
+ catch (IOException | ContentNotFoundException e)
+ {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/ContentUpdatingTest.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/ContentUpdatingTest.java
new file mode 100644
index 0000000..048d3ce
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/ContentUpdatingTest.java
@@ -0,0 +1,59 @@
+package com.github.joshuakissoon.kademlia.simulations;
+
+import java.io.IOException;
+import com.github.joshuakissoon.kademlia.dht.GetParameter;
+import com.github.joshuakissoon.kademlia.JKademliaNode;
+import com.github.joshuakissoon.kademlia.dht.KademliaStorageEntry;
+import com.github.joshuakissoon.kademlia.exceptions.ContentNotFoundException;
+import com.github.joshuakissoon.kademlia.node.KademliaId;
+
+/**
+ * Testing sending and receiving content between 2 Nodes on a network
+ *
+ * @author Joshua Kissoon
+ * @since 20140224
+ */
+public class ContentUpdatingTest
+{
+
+ public static void main(String[] args)
+ {
+ try
+ {
+ /* Setting up 2 Kad networks */
+ JKademliaNode kad1 = new JKademliaNode("JoshuaK", new KademliaId("ASF45678947584567467"), 7574);
+ System.out.println("Created Node Kad 1: " + kad1.getNode().getNodeId());
+ JKademliaNode kad2 = new JKademliaNode("Crystal", new KademliaId("ASERTKJDHGVHERJHGFLK"), 7572);
+ System.out.println("Created Node Kad 2: " + kad2.getNode().getNodeId());
+ kad2.bootstrap(kad1.getNode());
+
+ /* Lets create the content and share it */
+ DHTContentImpl c = new DHTContentImpl(kad2.getOwnerId(), "Some Data");
+ kad2.put(c);
+
+ /* Lets retrieve the content */
+ System.out.println("Retrieving Content");
+ GetParameter gp = new GetParameter(c.getKey(), DHTContentImpl.TYPE, c.getOwnerId());
+
+ System.out.println("Get Parameter: " + gp);
+ KademliaStorageEntry conte = kad2.get(gp);
+ System.out.println("Content Found: " + new DHTContentImpl().fromSerializedForm(conte.getContent()));
+ System.out.println("Content Metadata: " + conte.getContentMetadata());
+
+ /* Lets update the content and put it again */
+ c.setData("Some New Data");
+ kad2.put(c);
+
+ /* Lets retrieve the content */
+ System.out.println("Retrieving Content Again");
+ conte = kad2.get(gp);
+ System.out.println("Content Found: " + new DHTContentImpl().fromSerializedForm(conte.getContent()));
+ System.out.println("Content Metadata: " + conte.getContentMetadata());
+
+ }
+ catch (IOException | ContentNotFoundException e)
+ {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/DHTContentImpl.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/DHTContentImpl.java
new file mode 100644
index 0000000..2df4e79
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/DHTContentImpl.java
@@ -0,0 +1,111 @@
+package com.github.joshuakissoon.kademlia.simulations;
+
+import com.google.gson.Gson;
+import com.github.joshuakissoon.kademlia.dht.KadContent;
+import com.github.joshuakissoon.kademlia.node.KademliaId;
+
+/**
+ * A simple DHT Content object to test DHT storage
+ *
+ * @author Joshua Kissoon
+ * @since 20140224
+ */
+public class DHTContentImpl implements KadContent
+{
+
+ public static final transient String TYPE = "DHTContentImpl";
+
+ private KademliaId key;
+ private String data;
+ private String ownerId;
+ private final long createTs;
+ private long updateTs;
+
+
+ {
+ this.createTs = this.updateTs = System.currentTimeMillis() / 1000L;
+ }
+
+ public DHTContentImpl()
+ {
+
+ }
+
+ public DHTContentImpl(String ownerId, String data)
+ {
+ this.ownerId = ownerId;
+ this.data = data;
+ this.key = new KademliaId();
+ }
+
+ public DHTContentImpl(KademliaId key, String ownerId)
+ {
+ this.key = key;
+ this.ownerId = ownerId;
+ }
+
+ public void setData(String newData)
+ {
+ this.data = newData;
+ this.setUpdated();
+ }
+
+ @Override
+ public KademliaId getKey()
+ {
+ return this.key;
+ }
+
+ @Override
+ public String getType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ public String getOwnerId()
+ {
+ return this.ownerId;
+ }
+
+ /**
+ * Set the content as updated
+ */
+ public void setUpdated()
+ {
+ this.updateTs = System.currentTimeMillis() / 1000L;
+ }
+
+ @Override
+ public long getCreatedTimestamp()
+ {
+ return this.createTs;
+ }
+
+ @Override
+ public long getLastUpdatedTimestamp()
+ {
+ return this.updateTs;
+ }
+
+ @Override
+ public byte[] toSerializedForm()
+ {
+ Gson gson = new Gson();
+ return gson.toJson(this).getBytes();
+ }
+
+ @Override
+ public DHTContentImpl fromSerializedForm(byte[] data)
+ {
+ Gson gson = new Gson();
+ DHTContentImpl val = gson.fromJson(new String(data), DHTContentImpl.class);
+ return val;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "DHTContentImpl[{data=" + this.data + "{ {key:" + this.key + "}]";
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/NodeConnectionTest.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/NodeConnectionTest.java
new file mode 100644
index 0000000..97727c0
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/NodeConnectionTest.java
@@ -0,0 +1,70 @@
+package com.github.joshuakissoon.kademlia.simulations;
+
+import java.io.IOException;
+import com.github.joshuakissoon.kademlia.JKademliaNode;
+import com.github.joshuakissoon.kademlia.node.KademliaId;
+
+/**
+ * Testing connecting 2 nodes to each other
+ *
+ * @author Joshua Kissoon
+ * @created 20140219
+ */
+public class NodeConnectionTest
+{
+
+ public static void main(String[] args)
+ {
+ try
+ {
+ /* Setting up 2 Kad networks */
+ JKademliaNode kad1 = new JKademliaNode("JoshuaK", new KademliaId("ASF45678947584567467"), 7574);
+ System.out.println("Created Node Kad 1: " + kad1.getNode().getNodeId());
+
+ JKademliaNode kad2 = new JKademliaNode("Crystal", new KademliaId("ASERTKJDHGVHERJHGFLK"), 7572);
+ //NodeId diff12 = kad1.getNode().getNodeId().xor(kad2.getNode().getNodeId());
+ System.out.println("Created Node Kad 2: " + kad2.getNode().getNodeId());
+// System.out.println(kad1.getNode().getNodeId() + " ^ " + kad2.getNode().getNodeId() + " = " + diff12);
+// System.out.println("Kad 1 - Kad 2 distance: " + diff12.getFirstSetBitIndex());
+
+ /* Connecting 2 to 1 */
+ System.out.println("Connecting Kad 1 and Kad 2");
+ kad1.bootstrap(kad2.getNode());
+
+// System.out.println("Kad 1: ");
+// System.out.println(kad1.getNode().getRoutingTable());
+// System.out.println("Kad 2: ");
+// System.out.println(kad2.getNode().getRoutingTable());
+
+ /* Creating a new node 3 and connecting it to 1, hoping it'll get onto 2 also */
+ JKademliaNode kad3 = new JKademliaNode("Jessica", new KademliaId("ASERTKJDOLKMNBVFR45G"), 7783);
+ System.out.println("\n\n\n\n\n\nCreated Node Kad 3: " + kad3.getNode().getNodeId());
+
+ System.out.println("Connecting Kad 3 and Kad 2");
+ kad3.bootstrap(kad2.getNode());
+
+// NodeId diff32 = kad3.getNode().getNodeId().xor(kad2.getNode().getNodeId());
+// NodeId diff31 = kad1.getNode().getNodeId().xor(kad3.getNode().getNodeId());
+// System.out.println("Kad 3 - Kad 1 distance: " + diff31.getFirstSetBitIndex());
+// System.out.println("Kad 3 - Kad 2 distance: " + diff32.getFirstSetBitIndex());
+ JKademliaNode kad4 = new JKademliaNode("Sandy", new KademliaId("ASERTK85OLKMN85FR4SS"), 7789);
+ System.out.println("\n\n\n\n\n\nCreated Node Kad 4: " + kad4.getNode().getNodeId());
+
+ System.out.println("Connecting Kad 4 and Kad 2");
+ kad4.bootstrap(kad2.getNode());
+
+ System.out.println("\n\nKad 1: " + kad1.getNode().getNodeId() + " Routing Table: ");
+ System.out.println(kad1.getRoutingTable());
+ System.out.println("\n\nKad 2: " + kad2.getNode().getNodeId() + " Routing Table: ");
+ System.out.println(kad2.getRoutingTable());
+ System.out.println("\n\nKad 3: " + kad3.getNode().getNodeId() + " Routing Table: ");
+ System.out.println(kad3.getRoutingTable());
+ System.out.println("\n\nKad 4: " + kad4.getNode().getNodeId() + " Routing Table: ");
+ System.out.println(kad4.getRoutingTable());
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/RefreshOperationTest.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/RefreshOperationTest.java
new file mode 100644
index 0000000..3c97c47
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/RefreshOperationTest.java
@@ -0,0 +1,45 @@
+package com.github.joshuakissoon.kademlia.simulations;
+
+import java.io.IOException;
+import com.github.joshuakissoon.kademlia.dht.GetParameter;
+import com.github.joshuakissoon.kademlia.JKademliaNode;
+import com.github.joshuakissoon.kademlia.dht.KademliaStorageEntry;
+import com.github.joshuakissoon.kademlia.exceptions.ContentNotFoundException;
+import com.github.joshuakissoon.kademlia.node.KademliaId;
+
+/**
+ * Testing sending and receiving content between 2 Nodes on a network
+ *
+ * @author Joshua Kissoon
+ * @since 20140224
+ */
+public class RefreshOperationTest
+{
+
+ public static void main(String[] args)
+ {
+ try
+ {
+ /* Setting up 2 Kad networks */
+ JKademliaNode kad1 = new JKademliaNode("JoshuaK", new KademliaId("ASF45678947584567467"), 7574);
+ JKademliaNode kad2 = new JKademliaNode("Crystal", new KademliaId("ASERTKJDHGVHERJHGFLK"), 7572);
+ kad2.bootstrap(kad1.getNode());
+
+ /* Lets create the content and share it */
+ DHTContentImpl c = new DHTContentImpl(kad2.getOwnerId(), "Some Data");
+ kad2.put(c);
+
+ /* Lets retrieve the content */
+ GetParameter gp = new GetParameter(c.getKey(), DHTContentImpl.TYPE);
+ gp.setType(DHTContentImpl.TYPE);
+ gp.setOwnerId(c.getOwnerId());
+ KademliaStorageEntry conte = kad2.get(gp);
+
+ kad2.refresh();
+ }
+ catch (IOException | ContentNotFoundException e)
+ {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/RoutingTableSimulation.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/RoutingTableSimulation.java
new file mode 100644
index 0000000..9170e9e
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/RoutingTableSimulation.java
@@ -0,0 +1,57 @@
+package com.github.joshuakissoon.kademlia.simulations;
+
+import com.github.joshuakissoon.kademlia.JKademliaNode;
+import com.github.joshuakissoon.kademlia.node.KademliaId;
+import com.github.joshuakissoon.kademlia.routing.KademliaRoutingTable;
+
+/**
+ * Testing how the routing table works and checking if everything works properly
+ *
+ * @author Joshua Kissoon
+ * @since 20140426
+ */
+public class RoutingTableSimulation
+{
+
+ public RoutingTableSimulation()
+ {
+ try
+ {
+ /* Setting up 2 Kad networks */
+ JKademliaNode kad1 = new JKademliaNode("JoshuaK", new KademliaId("ASF45678947584567463"), 12049);
+ JKademliaNode kad2 = new JKademliaNode("Crystal", new KademliaId("ASF45678947584567464"), 4585);
+ JKademliaNode kad3 = new JKademliaNode("Shameer", new KademliaId("ASF45678947584567465"), 8104);
+ JKademliaNode kad4 = new JKademliaNode("Lokesh", new KademliaId("ASF45678947584567466"), 8335);
+ JKademliaNode kad5 = new JKademliaNode("Chandu", new KademliaId("ASF45678947584567467"), 13345);
+
+ KademliaRoutingTable rt = kad1.getRoutingTable();
+
+ rt.insert(kad2.getNode());
+ rt.insert(kad3.getNode());
+ rt.insert(kad4.getNode());
+ System.out.println(rt);
+
+ rt.insert(kad5.getNode());
+ System.out.println(rt);
+
+ rt.insert(kad3.getNode());
+ System.out.println(rt);
+
+
+ /* Lets shut down a node and then try putting a content on the network. We'll then see how the un-responsive contacts work */
+ }
+ catch (IllegalStateException e)
+ {
+
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public static void main(String[] args)
+ {
+ new RoutingTableSimulation();
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/RoutingTableStateTesting.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/RoutingTableStateTesting.java
new file mode 100644
index 0000000..d5b290b
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/RoutingTableStateTesting.java
@@ -0,0 +1,149 @@
+package com.github.joshuakissoon.kademlia.simulations;
+
+import java.io.IOException;
+import java.util.Scanner;
+import com.github.joshuakissoon.kademlia.JKademliaNode;
+import com.github.joshuakissoon.kademlia.dht.KadContent;
+import com.github.joshuakissoon.kademlia.node.KademliaId;
+
+/**
+ * Testing how the routing table works and it's state after different operations
+ *
+ * @author Joshua Kissoon
+ * @since 20140426
+ */
+public class RoutingTableStateTesting
+{
+
+ JKademliaNode[] kads;
+
+ public int numKads = 10;
+
+ public RoutingTableStateTesting()
+ {
+ try
+ {
+ /* Setting up Kad networks */
+ kads = new JKademliaNode[numKads];
+
+ kads[0] = new JKademliaNode("user0", new KademliaId("HRF456789SD584567460"), 1334);
+ kads[1] = new JKademliaNode("user1", new KademliaId("ASF456789475DS567461"), 1209);
+ kads[2] = new JKademliaNode("user2", new KademliaId("AFG45678947584567462"), 4585);
+ kads[3] = new JKademliaNode("user3", new KademliaId("FSF45J38947584567463"), 8104);
+ kads[4] = new JKademliaNode("user4", new KademliaId("ASF45678947584567464"), 8335);
+ kads[5] = new JKademliaNode("user5", new KademliaId("GHF4567894DR84567465"), 13345);
+ kads[6] = new JKademliaNode("user6", new KademliaId("ASF45678947584567466"), 12049);
+ kads[7] = new JKademliaNode("user7", new KademliaId("AE345678947584567467"), 14585);
+ kads[8] = new JKademliaNode("user8", new KademliaId("ASAA5678947584567468"), 18104);
+ kads[9] = new JKademliaNode("user9", new KademliaId("ASF456789475845674U9"), 18335);
+
+ for (int i = 1; i < numKads; i++)
+ {
+ kads[i].bootstrap(kads[0].getNode());
+ }
+
+ /* Lets shut down a node and then try putting a content on the network. We'll then see how the un-responsive contacts work */
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public KadContent putContent(String content, JKademliaNode owner)
+ {
+ DHTContentImpl c = null;
+ try
+ {
+ c = new DHTContentImpl(owner.getOwnerId(), "Some Data");
+ owner.put(c);
+ return c;
+ }
+ catch (IOException e)
+ {
+ System.err.println("Error whiles putting content " + content + " from owner: " + owner.getOwnerId());
+ }
+
+ return c;
+ }
+
+ public void shutdownKad(JKademliaNode kad)
+ {
+ try
+ {
+ kad.shutdown(false);
+ }
+ catch (IOException ex)
+ {
+ System.err.println("Error whiles shutting down node with owner: " + kad.getOwnerId());
+ }
+ }
+
+ public void printRoutingTable(int kadId)
+ {
+ System.out.println(kads[kadId].getRoutingTable());
+ }
+
+ public void printRoutingTables()
+ {
+ for (int i = 0; i < numKads; i++)
+ {
+ this.printRoutingTable(i);
+ }
+ }
+
+ public void printStorage(int kadId)
+ {
+ System.out.println(kads[kadId].getDHT());
+ }
+
+ public void printStorage()
+ {
+ for (int i = 0; i < numKads; i++)
+ {
+ this.printStorage(i);
+ }
+ }
+
+ public static void main(String[] args)
+ {
+
+ RoutingTableStateTesting rtss = new RoutingTableStateTesting();
+
+ try
+ {
+ rtss.printRoutingTables();
+
+ /* Lets shut down a node to test the node removal operation */
+ rtss.shutdownKad(rtss.kads[3]);
+
+ rtss.putContent("Content owned by kad0", rtss.kads[0]);
+ rtss.printStorage();
+
+ Thread.sleep(1000);
+
+ /* kad3 should be removed from their routing tables by now. */
+ rtss.printRoutingTables();
+ }
+ catch (InterruptedException ex)
+ {
+
+ }
+
+ Scanner sc = new Scanner(System.in);
+ while (true)
+ {
+ System.out.println("\n\n ************************* Options **************************** \n");
+ System.out.println("1 i - Print routing table of node i");
+ int val1 = sc.nextInt();
+ int val2 = sc.nextInt();
+
+ switch (val1)
+ {
+ case 1:
+ rtss.printRoutingTable(val2);
+ break;
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/SaveStateTest.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/SaveStateTest.java
new file mode 100644
index 0000000..db3ac56
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/SaveStateTest.java
@@ -0,0 +1,96 @@
+package com.github.joshuakissoon.kademlia.simulations;
+
+import com.github.joshuakissoon.kademlia.JKademliaNode;
+import com.github.joshuakissoon.kademlia.node.KademliaId;
+
+/**
+ * Testing the save and retrieve state operations
+ *
+ * @author Joshua Kissoon
+ * @since 20140309
+ */
+public class SaveStateTest
+{
+
+ public SaveStateTest()
+ {
+ try
+ {
+ /* Setting up 2 Kad networks */
+ JKademliaNode kad1 = new JKademliaNode("JoshuaK", new KademliaId("ASF45678947584567463"), 12049);
+ JKademliaNode kad2 = new JKademliaNode("Crystal", new KademliaId("ASF45678947584567464"), 4585);
+ JKademliaNode kad3 = new JKademliaNode("Shameer", new KademliaId("ASF45678947584567465"), 8104);
+ JKademliaNode kad4 = new JKademliaNode("Lokesh", new KademliaId("ASF45678947584567466"), 8335);
+ JKademliaNode kad5 = new JKademliaNode("Chandu", new KademliaId("ASF45678947584567467"), 13345);
+
+ /* Connecting 2 to 1 */
+ System.out.println("Connecting Nodes 1 & 2");
+ kad2.bootstrap(kad1.getNode());
+ System.out.println(kad1);
+ System.out.println(kad2);
+
+ kad3.bootstrap(kad2.getNode());
+ System.out.println(kad1);
+ System.out.println(kad2);
+ System.out.println(kad3);
+
+ kad4.bootstrap(kad2.getNode());
+ System.out.println(kad1);
+ System.out.println(kad2);
+ System.out.println(kad3);
+ System.out.println(kad4);
+
+ kad5.bootstrap(kad4.getNode());
+
+ System.out.println(kad1);
+ System.out.println(kad2);
+ System.out.println(kad3);
+ System.out.println(kad4);
+ System.out.println(kad5);
+
+ synchronized (this)
+ {
+ System.out.println("\n\n\n\nSTORING CONTENT 1\n\n\n\n");
+ DHTContentImpl c = new DHTContentImpl(kad2.getOwnerId(), "Some Data");
+ System.out.println(c);
+ kad2.put(c);
+ }
+
+ synchronized (this)
+ {
+ System.out.println("\n\n\n\nSTORING CONTENT 2\n\n\n\n");
+ DHTContentImpl c2 = new DHTContentImpl(kad2.getOwnerId(), "Some other Data");
+ System.out.println(c2);
+ kad4.put(c2);
+ }
+
+ System.out.println(kad1);
+ System.out.println(kad2);
+ System.out.println(kad3);
+ System.out.println(kad4);
+ System.out.println(kad5);
+
+ /* Shutting down kad1 and restarting it */
+ System.out.println("\n\n\nShutting down Kad instance");
+ System.out.println(kad2);
+ kad1.shutdown(true);
+
+ System.out.println("\n\n\nReloading Kad instance from file");
+ JKademliaNode kadR2 = JKademliaNode.loadFromFile("JoshuaK");
+ System.out.println(kadR2);
+ }
+ catch (IllegalStateException e)
+ {
+
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public static void main(String[] args)
+ {
+ new SaveStateTest();
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/SaveStateTest2.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/SaveStateTest2.java
new file mode 100644
index 0000000..287fdcd
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/SaveStateTest2.java
@@ -0,0 +1,70 @@
+package com.github.joshuakissoon.kademlia.simulations;
+
+import com.github.joshuakissoon.kademlia.JKademliaNode;
+import com.github.joshuakissoon.kademlia.dht.GetParameter;
+import com.github.joshuakissoon.kademlia.dht.KademliaStorageEntry;
+import com.github.joshuakissoon.kademlia.node.KademliaId;
+
+/**
+ * Testing the save and retrieve state operations.
+ * Here we also try to look for content on a restored node
+ *
+ * @author Joshua Kissoon
+ * @since 20140309
+ */
+public class SaveStateTest2
+{
+
+ public SaveStateTest2()
+ {
+ try
+ {
+ /* Setting up 2 Kad networks */
+ JKademliaNode kad1 = new JKademliaNode("JoshuaK", new KademliaId("ASF45678947584567463"), 12049);
+ JKademliaNode kad2 = new JKademliaNode("Crystal", new KademliaId("ASF45678947584567464"), 4585);
+
+ /* Connecting 2 to 1 */
+ System.out.println("Connecting Nodes 1 & 2");
+ kad2.bootstrap(kad1.getNode());
+ System.out.println(kad1);
+ System.out.println(kad2);
+
+ DHTContentImpl c;
+ synchronized (this)
+ {
+ System.out.println("\n\n\n\nSTORING CONTENT 1\n\n\n\n");
+ c = new DHTContentImpl(kad2.getOwnerId(), "Some Data");
+ System.out.println(c);
+ kad1.putLocally(c);
+ }
+
+ System.out.println(kad1);
+ System.out.println(kad2);
+
+ /* Shutting down kad1 and restarting it */
+ System.out.println("\n\n\nShutting down Kad 1 instance");
+ kad1.shutdown(true);
+
+ System.out.println("\n\n\nReloading Kad instance from file");
+ kad1 = JKademliaNode.loadFromFile("JoshuaK");
+ kad1.bootstrap(kad2.getNode());
+ System.out.println(kad2);
+
+ /* Trying to get a content stored on the restored node */
+ GetParameter gp = new GetParameter(c.getKey(), kad2.getOwnerId(), c.getType());
+ KademliaStorageEntry content = kad2.get(gp);
+ DHTContentImpl cc = new DHTContentImpl().fromSerializedForm(content.getContent());
+ System.out.println("Content received: " + cc);
+ }
+
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public static void main(String[] args)
+ {
+ new SaveStateTest2();
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/SimpleMessageTest.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/SimpleMessageTest.java
new file mode 100644
index 0000000..f98035e
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/SimpleMessageTest.java
@@ -0,0 +1,32 @@
+package com.github.joshuakissoon.kademlia.simulations;
+
+import java.io.IOException;
+import com.github.joshuakissoon.kademlia.JKademliaNode;
+import com.github.joshuakissoon.kademlia.message.SimpleMessage;
+import com.github.joshuakissoon.kademlia.node.KademliaId;
+import com.github.joshuakissoon.kademlia.message.SimpleReceiver;
+
+/**
+ * Test 1: Try sending a simple message between nodes
+ *
+ * @author Joshua Kissoon
+ * @created 20140218
+ */
+public class SimpleMessageTest
+{
+
+ public static void main(String[] args)
+ {
+ try
+ {
+ JKademliaNode kad1 = new JKademliaNode("Joshua", new KademliaId("12345678901234567890"), 7574);
+ JKademliaNode kad2 = new JKademliaNode("Crystal", new KademliaId("12345678901234567891"), 7572);
+
+ kad1.getServer().sendMessage(kad2.getNode(), new SimpleMessage("Some Message"), new SimpleReceiver());
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/Simulation.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/Simulation.java
new file mode 100644
index 0000000..fa4fbdf
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/simulations/Simulation.java
@@ -0,0 +1,16 @@
+package com.github.joshuakissoon.kademlia.simulations;
+
+/**
+ * A class that specifies the structure for simulations.
+ *
+ * @author Joshua Kissoon
+ * @since
+ */
+public interface Simulation
+{
+
+ /**
+ * Calling this method runs the simulation
+ */
+ public void runSimulation();
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/util/HashCalculator.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/util/HashCalculator.java
new file mode 100644
index 0000000..f866811
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/util/HashCalculator.java
@@ -0,0 +1,100 @@
+package com.github.joshuakissoon.kademlia.util;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * A class that is used to calculate the hash of strings.
+ *
+ * @author Joshua Kissoon
+ * @since 20140405
+ */
+public class HashCalculator
+{
+
+ /**
+ * Computes the SHA-1 Hash.
+ *
+ * @param toHash The string to hash
+ *
+ * @return byte[20] The hashed string
+ *
+ * @throws java.security.NoSuchAlgorithmException
+ */
+ public static byte[] sha1Hash(String toHash) throws NoSuchAlgorithmException
+ {
+ /* Create a MessageDigest */
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
+
+ /* Add password bytes to digest */
+ md.update(toHash.getBytes());
+
+ /* Get the hashed bytes */
+ return md.digest();
+ }
+
+ /**
+ * Computes the SHA-1 Hash using a Salt.
+ *
+ * @param toHash The string to hash
+ * @param salt A salt used to blind the hash
+ *
+ * @return byte[20] The hashed string
+ *
+ * @throws java.security.NoSuchAlgorithmException
+ */
+ public static byte[] sha1Hash(String toHash, String salt) throws NoSuchAlgorithmException
+ {
+ /* Create a MessageDigest */
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
+
+ /* Add password bytes to digest */
+ md.update(toHash.getBytes());
+
+ /* Get the hashed bytes */
+ return md.digest(salt.getBytes());
+ }
+
+ /**
+ * Computes the MD5 Hash.
+ *
+ * @param toHash The string to hash
+ *
+ * @return byte[16] The hashed string
+ *
+ * @throws java.security.NoSuchAlgorithmException
+ */
+ public static byte[] md5Hash(String toHash) throws NoSuchAlgorithmException
+ {
+ /* Create a MessageDigest */
+ MessageDigest md = MessageDigest.getInstance("MD5");
+
+ /* Add password bytes to digest */
+ md.update(toHash.getBytes());
+
+ /* Get the hashed bytes */
+ return md.digest();
+ }
+
+ /**
+ * Computes the MD5 Hash using a salt.
+ *
+ * @param toHash The string to hash
+ * @param salt A salt used to blind the hash
+ *
+ * @return byte[16] The hashed string
+ *
+ * @throws java.security.NoSuchAlgorithmException
+ */
+ public static byte[] md5Hash(String toHash, String salt) throws NoSuchAlgorithmException
+ {
+ /* Create a MessageDigest */
+ MessageDigest md = MessageDigest.getInstance("MD5");
+
+ /* Add password bytes to digest */
+ md.update(toHash.getBytes());
+
+ /* Get the hashed bytes */
+ return md.digest(salt.getBytes());
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/util/RouteLengthChecker.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/util/RouteLengthChecker.java
new file mode 100644
index 0000000..b121da8
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/util/RouteLengthChecker.java
@@ -0,0 +1,92 @@
+package com.github.joshuakissoon.kademlia.util;
+
+import java.util.Collection;
+import java.util.HashMap;
+import com.github.joshuakissoon.kademlia.node.Node;
+
+/**
+ * Class that helps compute the route length taken to complete an operation.
+ *
+ * Only used for routing operations - mainly the NodeLookup and ContentLookup Operations.
+ *
+ * Idea:
+ * - Add the original set of nodes with route length 0;
+ * - When we get a node reply with a set of nodes, we add those nodes and set the route length to their sender route length + 1
+ *
+ * @author Joshua Kissoon
+ * @since 20140510
+ */
+public class RouteLengthChecker
+{
+
+ /* Store the nodes and their route length (RL) */
+ private final HashMap nodes;
+
+ /* Lets cache the max route length instead of having to go and search for it later */
+ private int maxRouteLength;
+
+
+ {
+ this.nodes = new HashMap<>();
+ this.maxRouteLength = 1;
+ }
+
+ /**
+ * Add the initial nodes in the routing operation
+ *
+ * @param initialNodes The set of initial nodes
+ */
+ public void addInitialNodes(Collection initialNodes)
+ {
+ for (Node n : initialNodes)
+ {
+ this.nodes.put(n, 1);
+ }
+ }
+
+ /**
+ * Add any nodes that we get from a node reply.
+ *
+ * The route length of these nodes will be their sender + 1;
+ *
+ * @param inputSet The set of nodes we receive
+ * @param sender The node who send the set
+ */
+ public void addNodes(Collection inputSet, Node sender)
+ {
+ if (!this.nodes.containsKey(sender))
+ {
+ return;
+ }
+
+ /* Get the route length of the input set - sender RL + 1 */
+ int inputSetRL = this.nodes.get(sender) + 1;
+
+ if (inputSetRL > this.maxRouteLength)
+ {
+ this.maxRouteLength = inputSetRL;
+ }
+
+ /* Add the nodes to our set */
+ for (Node n : inputSet)
+ {
+ /* We only add if the node is not already there... */
+ if (!this.nodes.containsKey(n))
+ {
+ this.nodes.put(n, inputSetRL);
+ }
+ }
+ }
+
+ /**
+ * Get the route length of the operation!
+ *
+ * It will be the max route length of all the nodes here.
+ *
+ * @return The route length
+ */
+ public int getRouteLength()
+ {
+ return this.maxRouteLength;
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/util/serializer/JsonDHTSerializer.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/util/serializer/JsonDHTSerializer.java
new file mode 100644
index 0000000..939dd0d
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/util/serializer/JsonDHTSerializer.java
@@ -0,0 +1,95 @@
+package com.github.joshuakissoon.kademlia.util.serializer;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.lang.reflect.Type;
+import java.util.List;
+import com.github.joshuakissoon.kademlia.dht.DHT;
+import com.github.joshuakissoon.kademlia.dht.KademliaDHT;
+import com.github.joshuakissoon.kademlia.dht.KademliaStorageEntryMetadata;
+
+/**
+ * A KadSerializer that serializes DHT to JSON format
+ * The generic serializer is not working for DHT
+ *
+ * Why a DHT specific serializer?
+ * The DHT structure:
+ * - DHT
+ * -- StorageEntriesManager
+ * --- Map>
+ * ---- NodeId:KeyBytes
+ * ---- List
+ * ----- StorageEntry: Key, OwnerId, Type, Hash
+ *
+ * The above structure seems to be causing some problem for Gson, especially at the Map part.
+ *
+ * Solution
+ * - Make the StorageEntriesManager transient
+ * - Simply store all StorageEntry in the serialized object
+ * - When reloading, re-add all StorageEntry to the DHT
+ *
+ * @author Joshua Kissoon
+ *
+ * @since 20140310
+ */
+public class JsonDHTSerializer implements KadSerializer
+{
+
+ private final Gson gson;
+ private final Type storageEntriesCollectionType;
+
+
+ {
+ gson = new Gson();
+
+ storageEntriesCollectionType = new TypeToken>()
+ {
+ }.getType();
+ }
+
+ @Override
+ public void write(KademliaDHT data, DataOutputStream out) throws IOException
+ {
+ try (JsonWriter writer = new JsonWriter(new OutputStreamWriter(out)))
+ {
+ writer.beginArray();
+
+ /* Write the basic DHT */
+ gson.toJson(data, DHT.class, writer);
+
+ /* Now Store the Entries */
+ gson.toJson(data.getStorageEntries(), this.storageEntriesCollectionType, writer);
+
+ writer.endArray();
+ }
+
+ }
+
+ @Override
+ public KademliaDHT read(DataInputStream in) throws IOException, ClassNotFoundException
+ {
+ try (DataInputStream din = new DataInputStream(in);
+ JsonReader reader = new JsonReader(new InputStreamReader(in)))
+ {
+ reader.beginArray();
+
+ /* Read the basic DHT */
+ DHT dht = gson.fromJson(reader, DHT.class);
+ dht.initialize();
+
+ /* Now get the entries and add them back to the DHT */
+ List entries = gson.fromJson(reader, this.storageEntriesCollectionType);
+ dht.putStorageEntries(entries);
+
+ reader.endArray();
+ return dht;
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/util/serializer/JsonRoutingTableSerializer.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/util/serializer/JsonRoutingTableSerializer.java
new file mode 100644
index 0000000..8d01b81
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/util/serializer/JsonRoutingTableSerializer.java
@@ -0,0 +1,112 @@
+package com.github.joshuakissoon.kademlia.util.serializer;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import com.github.joshuakissoon.kademlia.routing.JKademliaRoutingTable;
+import java.lang.reflect.Type;
+import java.util.List;
+import com.github.joshuakissoon.kademlia.KadConfiguration;
+import com.github.joshuakissoon.kademlia.routing.Contact;
+import com.github.joshuakissoon.kademlia.routing.KademliaRoutingTable;
+
+/**
+ * A KadSerializer that serializes routing tables to JSON format
+ The generic serializer is not working for routing tables
+
+ Why a JKademliaRoutingTable specific serializer?
+ The routing table structure:
+ - JKademliaRoutingTable
+ -- Buckets[]
+ --- Map
+ * ---- NodeId:KeyBytes
+ * ---- Node: NodeId, InetAddress, Port
+ *
+ * The above structure seems to be causing some problem for Gson,
+ * especially at the Map part.
+ *
+ * Solution
+ - Make the Buckets[] transient
+ - Simply store all Nodes in the serialized object
+ - When reloading, re-add all nodes to the JKademliaRoutingTable
+ *
+ * @author Joshua Kissoon
+ *
+ * @since 20140310
+ */
+public class JsonRoutingTableSerializer implements KadSerializer
+{
+
+ private final Gson gson;
+
+ Type contactCollectionType = new TypeToken>()
+ {
+ }.getType();
+
+ private final KadConfiguration config;
+
+
+ {
+ gson = new Gson();
+ }
+
+ /**
+ * Initialize the class
+ *
+ * @param config
+ */
+ public JsonRoutingTableSerializer(KadConfiguration config)
+ {
+ this.config = config;
+ }
+
+ @Override
+ public void write(KademliaRoutingTable data, DataOutputStream out) throws IOException
+ {
+ try (JsonWriter writer = new JsonWriter(new OutputStreamWriter(out)))
+ {
+ writer.beginArray();
+
+ /* Write the basic JKademliaRoutingTable */
+ gson.toJson(data, JKademliaRoutingTable.class, writer);
+
+ /* Now Store the Contacts */
+ gson.toJson(data.getAllContacts(), contactCollectionType, writer);
+
+ writer.endArray();
+ }
+ }
+
+ @Override
+ public KademliaRoutingTable read(DataInputStream in) throws IOException, ClassNotFoundException
+ {
+ try (DataInputStream din = new DataInputStream(in);
+ JsonReader reader = new JsonReader(new InputStreamReader(in)))
+ {
+ reader.beginArray();
+
+ /* Read the basic JKademliaRoutingTable */
+ KademliaRoutingTable tbl = gson.fromJson(reader, KademliaRoutingTable.class);
+ tbl.setConfiguration(config);
+
+ /* Now get the Contacts and add them back to the JKademliaRoutingTable */
+ List contacts = gson.fromJson(reader, contactCollectionType);
+ tbl.initialize();
+
+ for (Contact c : contacts)
+ {
+ tbl.insert(c);
+ }
+
+ reader.endArray();
+ /* Read and return the Content*/
+ return tbl;
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/util/serializer/JsonSerializer.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/util/serializer/JsonSerializer.java
new file mode 100644
index 0000000..d8ac462
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/util/serializer/JsonSerializer.java
@@ -0,0 +1,67 @@
+package com.github.joshuakissoon.kademlia.util.serializer;
+
+import com.google.gson.Gson;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+
+/**
+ * A KadSerializer that serializes content to JSON format
+ *
+ * @param The type of content to serialize
+ *
+ * @author Joshua Kissoon
+ *
+ * @since 20140225
+ */
+public class JsonSerializer implements KadSerializer
+{
+
+ private final Gson gson;
+
+
+ {
+ gson = new Gson();
+ }
+
+ @Override
+ public void write(T data, DataOutputStream out) throws IOException
+ {
+ try (JsonWriter writer = new JsonWriter(new OutputStreamWriter(out)))
+ {
+ writer.beginArray();
+
+ /* Store the content type */
+ gson.toJson(data.getClass().getName(), String.class, writer);
+
+ /* Now Store the content */
+ gson.toJson(data, data.getClass(), writer);
+
+ writer.endArray();
+ }
+ }
+
+ @Override
+ public T read(DataInputStream in) throws IOException, ClassNotFoundException
+ {
+ try (DataInputStream din = new DataInputStream(in);
+ JsonReader reader = new JsonReader(new InputStreamReader(in)))
+ {
+ reader.beginArray();
+
+ /* Read the class name */
+ String className = gson.fromJson(reader, String.class);
+
+ /* Read and return the Content*/
+ T ret = gson.fromJson(reader, Class.forName(className));
+
+ reader.endArray();
+
+ return ret;
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/chronosx88/influence/kademlia/util/serializer/KadSerializer.java b/app/src/main/java/io/github/chronosx88/influence/kademlia/util/serializer/KadSerializer.java
new file mode 100644
index 0000000..92ad7d4
--- /dev/null
+++ b/app/src/main/java/io/github/chronosx88/influence/kademlia/util/serializer/KadSerializer.java
@@ -0,0 +1,41 @@
+package com.github.joshuakissoon.kademlia.util.serializer;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * A Serializer is used to transform data to and from a specified form.
+ *
+ * Here we define the structure of any Serializer used in Kademlia
+ *
+ * @author Joshua Kissoon
+ * @param The type of content being serialized
+ *
+ * @since 20140225
+ */
+public interface KadSerializer
+{
+
+ /**
+ * Write a KadContent to a DataOutput stream
+ *
+ * @param data The data to write
+ * @param out The output Stream to write to
+ *
+ * @throws java.io.IOException
+ */
+ public void write(T data, DataOutputStream out) throws IOException;
+
+ /**
+ * Read data of type T from a DataInput Stream
+ *
+ * @param in The InputStream to read the data from
+ *
+ * @return T Data of type T
+ *
+ * @throws java.io.IOException
+ * @throws java.lang.ClassNotFoundException
+ */
+ public T read(DataInputStream in) throws IOException, ClassNotFoundException;
+}