13 | {
14 |
15 | private final BigInteger key;
16 |
17 | /**
18 | * @param key The NodeId relative to which the distance should be measured.
19 | */
20 | public KeyComparator(KademliaId key)
21 | {
22 | this.key = key.getInt();
23 | }
24 |
25 | /**
26 | * Compare two objects which must both be of type Node
27 | * and determine which is closest to the identifier specified in the
28 | * constructor.
29 | *
30 | * @param n1 Node 1 to compare distance from the key
31 | * @param n2 Node 2 to compare distance from the key
32 | */
33 | @Override
34 | public int compare(Node n1, Node n2)
35 | {
36 | BigInteger b1 = n1.getNodeId().getInt();
37 | BigInteger b2 = n2.getNodeId().getInt();
38 |
39 | b1 = b1.xor(key);
40 | b2 = b2.xor(key);
41 |
42 | return b1.abs().compareTo(b2.abs());
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/kademlia/operation/PingOperation.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Implementation of the Kademlia Ping operation,
3 | * This is on hold at the moment since I'm not sure if we'll use ping given the improvements mentioned in the paper.
4 | *
5 | * @author Joshua Kissoon
6 | * @since 20140218
7 | */
8 | package kademlia.operation;
9 |
10 | import java.io.IOException;
11 | import kademlia.KadServer;
12 | import kademlia.exceptions.RoutingException;
13 | import kademlia.node.Node;
14 |
15 | public class PingOperation implements Operation
16 | {
17 |
18 | private final KadServer server;
19 | private final Node localNode;
20 | private final Node toPing;
21 |
22 | /**
23 | * @param server The Kademlia server used to send & receive messages
24 | * @param local The local node
25 | * @param toPing The node to send the ping message to
26 | */
27 | public PingOperation(KadServer server, Node local, Node toPing)
28 | {
29 | this.server = server;
30 | this.localNode = local;
31 | this.toPing = toPing;
32 | }
33 |
34 | @Override
35 | public void execute() throws IOException, RoutingException
36 | {
37 | throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/kademlia/message/ConnectMessage.java:
--------------------------------------------------------------------------------
1 | package kademlia.message;
2 |
3 | import java.io.DataInputStream;
4 | import java.io.DataOutputStream;
5 | import java.io.IOException;
6 | import kademlia.node.Node;
7 |
8 | /**
9 | * A message sent to another node requesting to connect to them.
10 | *
11 | * @author Joshua Kissoon
12 | * @created 20140218
13 | */
14 | public class ConnectMessage implements Message
15 | {
16 |
17 | private Node origin;
18 | public static final byte CODE = 0x02;
19 |
20 | public ConnectMessage(Node origin)
21 | {
22 | this.origin = origin;
23 | }
24 |
25 | public ConnectMessage(DataInputStream in) throws IOException
26 | {
27 | this.fromStream(in);
28 | }
29 |
30 | @Override
31 | public final void fromStream(DataInputStream in) throws IOException
32 | {
33 | this.origin = new Node(in);
34 | }
35 |
36 | @Override
37 | public void toStream(DataOutputStream out) throws IOException
38 | {
39 | origin.toStream(out);
40 | }
41 |
42 | public Node getOrigin()
43 | {
44 | return this.origin;
45 | }
46 |
47 | @Override
48 | public byte code()
49 | {
50 | return CODE;
51 | }
52 |
53 | @Override
54 | public String toString()
55 | {
56 | return "ConnectMessage[origin NodeId=" + origin.getNodeId() + "]";
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/kademlia/operation/KadRefreshOperation.java:
--------------------------------------------------------------------------------
1 | package kademlia.operation;
2 |
3 | import java.io.IOException;
4 | import kademlia.KadConfiguration;
5 | import kademlia.KadServer;
6 | import kademlia.KademliaNode;
7 | import kademlia.dht.KademliaDHT;
8 |
9 | /**
10 | * An operation that handles refreshing the entire Kademlia Systems including buckets and content
11 | *
12 | * @author Joshua Kissoon
13 | * @since 20140306
14 | */
15 | public class KadRefreshOperation implements Operation
16 | {
17 |
18 | private final KadServer server;
19 | private final KademliaNode localNode;
20 | private final KademliaDHT dht;
21 | private final KadConfiguration config;
22 |
23 | public KadRefreshOperation(KadServer server, KademliaNode localNode, KademliaDHT dht, KadConfiguration config)
24 | {
25 | this.server = server;
26 | this.localNode = localNode;
27 | this.dht = dht;
28 | this.config = config;
29 | }
30 |
31 | @Override
32 | public void execute() throws IOException
33 | {
34 | /* Run our BucketRefreshOperation to refresh buckets */
35 | new BucketRefreshOperation(this.server, this.localNode, this.config).execute();
36 |
37 | /* After buckets have been refreshed, we refresh content */
38 | new ContentRefreshOperation(this.server, this.localNode, this.dht, this.config).execute();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/kademlia/message/AcknowledgeMessage.java:
--------------------------------------------------------------------------------
1 | package kademlia.message;
2 |
3 | import java.io.DataInputStream;
4 | import java.io.DataOutputStream;
5 | import java.io.IOException;
6 | import kademlia.node.Node;
7 |
8 | /**
9 | * A message used to acknowledge a request from a node; can be used in many situations.
10 | * - Mainly used to acknowledge a connect message
11 | *
12 | * @author Joshua Kissoon
13 | * @created 20140218
14 | */
15 | public class AcknowledgeMessage implements Message
16 | {
17 |
18 | private Node origin;
19 | public static final byte CODE = 0x01;
20 |
21 | public AcknowledgeMessage(Node origin)
22 | {
23 | this.origin = origin;
24 | }
25 |
26 | public AcknowledgeMessage(DataInputStream in) throws IOException
27 | {
28 | this.fromStream(in);
29 | }
30 |
31 | @Override
32 | public final void fromStream(DataInputStream in) throws IOException
33 | {
34 | this.origin = new Node(in);
35 | }
36 |
37 | @Override
38 | public void toStream(DataOutputStream out) throws IOException
39 | {
40 | origin.toStream(out);
41 | }
42 |
43 | public Node getOrigin()
44 | {
45 | return this.origin;
46 | }
47 |
48 | @Override
49 | public byte code()
50 | {
51 | return CODE;
52 | }
53 |
54 | @Override
55 | public String toString()
56 | {
57 | return "AcknowledgeMessage[origin=" + origin.getNodeId() + "]";
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/kademlia/message/Streamable.java:
--------------------------------------------------------------------------------
1 | package kademlia.message;
2 |
3 | import java.io.DataInputStream;
4 | import java.io.DataOutputStream;
5 | import java.io.IOException;
6 |
7 | /**
8 | * A Streamable object is able to write it's state to an output stream and
9 | * a class implementing Streamable must be able to recreate an instance of
10 | * the class from an input stream. No information about class name is written
11 | * to the output stream so it must be known what class type is expected when
12 | * reading objects back in from an input stream. This gives a space
13 | * advantage over Serializable.
14 | *
15 | * Since the exact class must be known anyway prior to reading, it is incouraged
16 | * that classes implementing Streamble also provide a constructor of the form:
17 | *
18 | * Streamable(DataInput in) throws IOException;
19 | * */
20 | public interface Streamable
21 | {
22 |
23 | /**
24 | * Writes the internal state of the Streamable object to the output stream
25 | * in a format that can later be read by the same Streamble class using
26 | * the {@link #fromStream} method.
27 | *
28 | * @param out
29 | *
30 | * @throws java.io.IOException
31 | */
32 | public void toStream(DataOutputStream out) throws IOException;
33 |
34 | /**
35 | * Reads the internal state of the Streamable object from the input stream.
36 | *
37 | * @param out
38 | *
39 | * @throws java.io.IOException
40 | */
41 | public void fromStream(DataInputStream out) throws IOException;
42 | }
43 |
--------------------------------------------------------------------------------
/src/kademlia/dht/JKademliaStorageEntry.java:
--------------------------------------------------------------------------------
1 | package kademlia.dht;
2 |
3 | /**
4 | * A JKademliaStorageEntry class that is used to store a content on the DHT
5 | *
6 | * @author Joshua Kissoon
7 | * @since 20140402
8 | */
9 | public class JKademliaStorageEntry implements KademliaStorageEntry
10 | {
11 |
12 | private String content;
13 | private final StorageEntryMetadata metadata;
14 |
15 | public JKademliaStorageEntry(final KadContent content)
16 | {
17 | this(content, new StorageEntryMetadata(content));
18 | }
19 |
20 | public JKademliaStorageEntry(final KadContent content, final StorageEntryMetadata metadata)
21 | {
22 | this.setContent(content.toSerializedForm());
23 | this.metadata = metadata;
24 | }
25 |
26 | @Override
27 | public final void setContent(final byte[] data)
28 | {
29 | this.content = new String(data);
30 | }
31 |
32 | @Override
33 | public final byte[] getContent()
34 | {
35 | return this.content.getBytes();
36 | }
37 |
38 | @Override
39 | public final KademliaStorageEntryMetadata getContentMetadata()
40 | {
41 | return this.metadata;
42 | }
43 |
44 | @Override
45 | public String toString()
46 | {
47 | StringBuilder sb = new StringBuilder("[StorageEntry: ");
48 |
49 | sb.append("[Content: ");
50 | sb.append(this.getContent());
51 | sb.append("]");
52 |
53 | sb.append(this.getContentMetadata());
54 |
55 | sb.append("]");
56 |
57 | return sb.toString();
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/kademlia/simulations/RefreshOperationTest.java:
--------------------------------------------------------------------------------
1 | package kademlia.simulations;
2 |
3 | import java.io.IOException;
4 | import kademlia.dht.GetParameter;
5 | import kademlia.JKademliaNode;
6 | import kademlia.dht.KademliaStorageEntry;
7 | import kademlia.exceptions.ContentNotFoundException;
8 | import kademlia.node.KademliaId;
9 |
10 | /**
11 | * Testing sending and receiving content between 2 Nodes on a network
12 | *
13 | * @author Joshua Kissoon
14 | * @since 20140224
15 | */
16 | public class RefreshOperationTest
17 | {
18 |
19 | public static void main(String[] args)
20 | {
21 | try
22 | {
23 | /* Setting up 2 Kad networks */
24 | JKademliaNode kad1 = new JKademliaNode("JoshuaK", new KademliaId("ASF45678947584567467"), 7574);
25 | JKademliaNode kad2 = new JKademliaNode("Crystal", new KademliaId("ASERTKJDHGVHERJHGFLK"), 7572);
26 | kad2.bootstrap(kad1.getNode());
27 |
28 | /* Lets create the content and share it */
29 | DHTContentImpl c = new DHTContentImpl(kad2.getOwnerId(), "Some Data");
30 | kad2.put(c);
31 |
32 | /* Lets retrieve the content */
33 | GetParameter gp = new GetParameter(c.getKey(), DHTContentImpl.TYPE);
34 | gp.setType(DHTContentImpl.TYPE);
35 | gp.setOwnerId(c.getOwnerId());
36 | KademliaStorageEntry conte = kad2.get(gp);
37 |
38 | kad2.refresh();
39 | }
40 | catch (IOException | ContentNotFoundException e)
41 | {
42 | e.printStackTrace();
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/kademlia/message/ConnectReceiver.java:
--------------------------------------------------------------------------------
1 | package kademlia.message;
2 |
3 | import java.io.IOException;
4 | import kademlia.KadServer;
5 | import kademlia.KademliaNode;
6 |
7 | /**
8 | * Receives a ConnectMessage and sends an AcknowledgeMessage as reply.
9 | *
10 | * @author Joshua Kissoon
11 | * @created 20140219
12 | */
13 | public class ConnectReceiver implements Receiver
14 | {
15 |
16 | private final KadServer server;
17 | private final KademliaNode localNode;
18 |
19 | public ConnectReceiver(KadServer server, KademliaNode local)
20 | {
21 | this.server = server;
22 | this.localNode = local;
23 | }
24 |
25 | /**
26 | * Handle receiving a ConnectMessage
27 | *
28 | * @param comm
29 | *
30 | * @throws java.io.IOException
31 | */
32 | @Override
33 | public void receive(Message incoming, int comm) throws IOException
34 | {
35 | ConnectMessage mess = (ConnectMessage) incoming;
36 |
37 | /* Update the local space by inserting the origin node. */
38 | this.localNode.getRoutingTable().insert(mess.getOrigin());
39 |
40 | /* Respond to the connect request */
41 | AcknowledgeMessage msg = new AcknowledgeMessage(this.localNode.getNode());
42 |
43 | /* Reply to the connect message with an Acknowledgement */
44 | this.server.reply(mess.getOrigin(), msg, comm);
45 | }
46 |
47 | /**
48 | * We don't need to do anything here
49 | *
50 | * @param comm
51 | *
52 | * @throws java.io.IOException
53 | */
54 | @Override
55 | public void timeout(int comm) throws IOException
56 | {
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/kademlia/dht/KademliaStorageEntryMetadata.java:
--------------------------------------------------------------------------------
1 | package kademlia.dht;
2 |
3 | import kademlia.node.KademliaId;
4 |
5 | /**
6 | * Keeps track of data for a Content stored in the DHT
7 | * Used by the StorageEntryManager class
8 | *
9 | * @author Joshua Kissoon
10 | * @since 20140226
11 | */
12 | public interface KademliaStorageEntryMetadata
13 | {
14 |
15 | /**
16 | * @return The Kademlia ID of this content
17 | */
18 | public KademliaId getKey();
19 |
20 | /**
21 | * @return The content's owner ID
22 | */
23 | public String getOwnerId();
24 |
25 | /**
26 | * @return The type of this content
27 | */
28 | public String getType();
29 |
30 | /**
31 | * @return A hash of the content
32 | */
33 | public int getContentHash();
34 |
35 | /**
36 | * @return The last time this content was updated
37 | */
38 | public long getLastUpdatedTimestamp();
39 |
40 | /**
41 | * When a node is looking for content, he sends the search criteria in a GetParameter object
42 | * Here we take this GetParameter object and check if this StorageEntry satisfies the given parameters
43 | *
44 | * @param params
45 | *
46 | * @return boolean Whether this content satisfies the parameters
47 | */
48 | public boolean satisfiesParameters(GetParameter params);
49 |
50 | /**
51 | * @return The timestamp for the last time this content was republished
52 | */
53 | public long lastRepublished();
54 |
55 | /**
56 | * Whenever we republish a content or get this content from the network, we update the last republished time
57 | */
58 | public void updateLastRepublished();
59 | }
60 |
--------------------------------------------------------------------------------
/src/kademlia/KadConfiguration.java:
--------------------------------------------------------------------------------
1 | package kademlia;
2 |
3 | /**
4 | * Interface that defines a KadConfiguration object
5 | *
6 | * @author Joshua Kissoon
7 | * @since 20140329
8 | */
9 | public interface KadConfiguration
10 | {
11 |
12 | /**
13 | * @return Interval in milliseconds between execution of RestoreOperations.
14 | */
15 | public long restoreInterval();
16 |
17 | /**
18 | * If no reply received from a node in this period (in milliseconds)
19 | * consider the node unresponsive.
20 | *
21 | * @return The time it takes to consider a node unresponsive
22 | */
23 | public long responseTimeout();
24 |
25 | /**
26 | * @return Maximum number of milliseconds for performing an operation.
27 | */
28 | public long operationTimeout();
29 |
30 | /**
31 | * @return Maximum number of concurrent messages in transit.
32 | */
33 | public int maxConcurrentMessagesTransiting();
34 |
35 | /**
36 | * @return K-Value used throughout Kademlia
37 | */
38 | public int k();
39 |
40 | /**
41 | * @return Size of replacement cache.
42 | */
43 | public int replacementCacheSize();
44 |
45 | /**
46 | * @return # of times a node can be marked as stale before it is actually removed.
47 | */
48 | public int stale();
49 |
50 | /**
51 | * Creates the folder in which this node data is to be stored.
52 | *
53 | * @param ownerId
54 | *
55 | * @return The folder path
56 | */
57 | public String getNodeDataFolder(String ownerId);
58 |
59 | /**
60 | * @return Whether we're in a testing or production system.
61 | */
62 | public boolean isTesting();
63 | }
64 |
--------------------------------------------------------------------------------
/src/kademlia/message/SimpleMessage.java:
--------------------------------------------------------------------------------
1 | package kademlia.message;
2 |
3 | import java.io.DataInputStream;
4 | import java.io.DataOutputStream;
5 | import java.io.IOException;
6 |
7 | /**
8 | * A simple message used for testing the system; Default message constructed if the message type sent is not available
9 | *
10 | * @author Joshua Kissoon
11 | * @created 20140217
12 | */
13 | public class SimpleMessage implements Message
14 | {
15 |
16 | /* Message constants */
17 | public static final byte CODE = 0x07;
18 |
19 | private String content;
20 |
21 | public SimpleMessage(String message)
22 | {
23 | this.content = message;
24 | }
25 |
26 | public SimpleMessage(DataInputStream in)
27 | {
28 | this.fromStream(in);
29 | }
30 |
31 | @Override
32 | public byte code()
33 | {
34 | return CODE;
35 | }
36 |
37 | @Override
38 | public void toStream(DataOutputStream out)
39 | {
40 | try
41 | {
42 | out.writeInt(this.content.length());
43 | out.writeBytes(this.content);
44 | }
45 | catch (IOException e)
46 | {
47 | e.printStackTrace();
48 | }
49 | }
50 |
51 | @Override
52 | public final void fromStream(DataInputStream in)
53 | {
54 | try
55 | {
56 | byte[] buff = new byte[in.readInt()];
57 | in.readFully(buff);
58 |
59 | this.content = new String(buff);
60 | }
61 | catch (IOException e)
62 | {
63 | e.printStackTrace();
64 | }
65 | }
66 |
67 | @Override
68 | public String toString()
69 | {
70 | return this.content;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/kademlia/message/StoreContentReceiver.java:
--------------------------------------------------------------------------------
1 | package kademlia.message;
2 |
3 | import java.io.IOException;
4 | import kademlia.KadServer;
5 | import kademlia.KademliaNode;
6 | import kademlia.dht.KademliaDHT;
7 |
8 | /**
9 | * Receiver for incoming StoreContentMessage
10 | *
11 | * @author Joshua Kissoon
12 | * @since 20140225
13 | */
14 | public class StoreContentReceiver implements Receiver
15 | {
16 |
17 | private final KadServer server;
18 | private final KademliaNode localNode;
19 | private final KademliaDHT dht;
20 |
21 | public StoreContentReceiver(KadServer server, KademliaNode localNode, KademliaDHT dht)
22 | {
23 | this.server = server;
24 | this.localNode = localNode;
25 | this.dht = dht;
26 | }
27 |
28 | @Override
29 | public void receive(Message incoming, int comm)
30 | {
31 | /* It's a StoreContentMessage we're receiving */
32 | StoreContentMessage msg = (StoreContentMessage) incoming;
33 |
34 | /* Insert the message sender into this node's routing table */
35 | this.localNode.getRoutingTable().insert(msg.getOrigin());
36 |
37 | try
38 | {
39 | /* Store this Content into the DHT */
40 | this.dht.store(msg.getContent());
41 | }
42 | catch (IOException e)
43 | {
44 | System.err.println("Unable to store received content; Message: " + e.getMessage());
45 | }
46 |
47 | }
48 |
49 | @Override
50 | public void timeout(int comm)
51 | {
52 | /**
53 | * This receiver only handles Receiving content when we've received the message,
54 | * so no timeout will happen with this receiver.
55 | */
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/kademlia/dht/KadContent.java:
--------------------------------------------------------------------------------
1 | package kademlia.dht;
2 |
3 | import kademlia.node.KademliaId;
4 |
5 | /**
6 | * Any piece of content that needs to be stored on the DHT
7 | *
8 | * @author Joshua Kissoon
9 | *
10 | * @since 20140224
11 | */
12 | public interface KadContent
13 | {
14 |
15 | /**
16 | * @return NodeId The DHT key for this content
17 | */
18 | public KademliaId getKey();
19 |
20 | /**
21 | * @return String The type of content
22 | */
23 | public String getType();
24 |
25 | /**
26 | * Each content will have an created date
27 | * This allows systems to know when to delete a content form his/her machine
28 | *
29 | * @return long The create date of this content
30 | */
31 | public long getCreatedTimestamp();
32 |
33 | /**
34 | * Each content will have an update timestamp
35 | * This allows the DHT to keep only the latest version of a content
36 | *
37 | * @return long The timestamp of when this content was last updated
38 | */
39 | public long getLastUpdatedTimestamp();
40 |
41 | /**
42 | * @return The ID of the owner of this content
43 | */
44 | public String getOwnerId();
45 |
46 | /**
47 | * Each content needs to be in byte format for transporting and storage,
48 | * this method takes care of that.
49 | *
50 | * Each object is responsible for transforming itself to byte format since the
51 | * structure of methods may differ.
52 | *
53 | * @return The content in byte format
54 | */
55 | public byte[] toSerializedForm();
56 |
57 | /**
58 | * Given the Content in byte format, read it
59 | *
60 | * @param data The object in byte format
61 | *
62 | * @return A new object from the given
63 | */
64 | public KadContent fromSerializedForm(byte[] data);
65 | }
66 |
--------------------------------------------------------------------------------
/src/kademlia/message/NodeLookupMessage.java:
--------------------------------------------------------------------------------
1 | package kademlia.message;
2 |
3 | import java.io.DataInputStream;
4 | import java.io.DataOutputStream;
5 | import java.io.IOException;
6 | import kademlia.node.Node;
7 | import kademlia.node.KademliaId;
8 |
9 | /**
10 | * A message sent to other nodes requesting the K-Closest nodes to a key sent in this message.
11 | *
12 | * @author Joshua Kissoon
13 | * @created 20140218
14 | */
15 | public class NodeLookupMessage implements Message
16 | {
17 |
18 | private Node origin;
19 | private KademliaId lookupId;
20 |
21 | public static final byte CODE = 0x05;
22 |
23 | /**
24 | * A new NodeLookupMessage to find nodes
25 | *
26 | * @param origin The Node from which the message is coming from
27 | * @param lookup The key for which to lookup nodes for
28 | */
29 | public NodeLookupMessage(Node origin, KademliaId lookup)
30 | {
31 | this.origin = origin;
32 | this.lookupId = lookup;
33 | }
34 |
35 | public NodeLookupMessage(DataInputStream in) throws IOException
36 | {
37 | this.fromStream(in);
38 | }
39 |
40 | @Override
41 | public final void fromStream(DataInputStream in) throws IOException
42 | {
43 | this.origin = new Node(in);
44 | this.lookupId = new KademliaId(in);
45 | }
46 |
47 | @Override
48 | public void toStream(DataOutputStream out) throws IOException
49 | {
50 | this.origin.toStream(out);
51 | this.lookupId.toStream(out);
52 | }
53 |
54 | public Node getOrigin()
55 | {
56 | return this.origin;
57 | }
58 |
59 | public KademliaId getLookupId()
60 | {
61 | return this.lookupId;
62 | }
63 |
64 | @Override
65 | public byte code()
66 | {
67 | return CODE;
68 | }
69 |
70 | @Override
71 | public String toString()
72 | {
73 | return "NodeLookupMessage[origin=" + origin + ",lookup=" + lookupId + "]";
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/kademlia/util/serializer/JsonSerializer.java:
--------------------------------------------------------------------------------
1 | package kademlia.util.serializer;
2 |
3 | import com.google.gson.Gson;
4 | import com.google.gson.stream.JsonReader;
5 | import com.google.gson.stream.JsonWriter;
6 | import java.io.DataInputStream;
7 | import java.io.DataOutputStream;
8 | import java.io.IOException;
9 | import java.io.InputStreamReader;
10 | import java.io.OutputStreamWriter;
11 |
12 | /**
13 | * A KadSerializer that serializes content to JSON format
14 | *
15 | * @param The type of content to serialize
16 | *
17 | * @author Joshua Kissoon
18 | *
19 | * @since 20140225
20 | */
21 | public class JsonSerializer implements KadSerializer
22 | {
23 |
24 | private final Gson gson;
25 |
26 |
27 | {
28 | gson = new Gson();
29 | }
30 |
31 | @Override
32 | public void write(T data, DataOutputStream out) throws IOException
33 | {
34 | try (JsonWriter writer = new JsonWriter(new OutputStreamWriter(out)))
35 | {
36 | writer.beginArray();
37 |
38 | /* Store the content type */
39 | gson.toJson(data.getClass().getName(), String.class, writer);
40 |
41 | /* Now Store the content */
42 | gson.toJson(data, data.getClass(), writer);
43 |
44 | writer.endArray();
45 | }
46 | }
47 |
48 | @Override
49 | public T read(DataInputStream in) throws IOException, ClassNotFoundException
50 | {
51 | try (DataInputStream din = new DataInputStream(in);
52 | JsonReader reader = new JsonReader(new InputStreamReader(in)))
53 | {
54 | reader.beginArray();
55 |
56 | /* Read the class name */
57 | String className = gson.fromJson(reader, String.class);
58 |
59 | /* Read and return the Content*/
60 | T ret = gson.fromJson(reader, Class.forName(className));
61 |
62 | reader.endArray();
63 |
64 | return ret;
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/kademlia/simulations/RoutingTableSimulation.java:
--------------------------------------------------------------------------------
1 | package kademlia.simulations;
2 |
3 | import kademlia.JKademliaNode;
4 | import kademlia.node.KademliaId;
5 | import kademlia.routing.KademliaRoutingTable;
6 |
7 | /**
8 | * Testing how the routing table works and checking if everything works properly
9 | *
10 | * @author Joshua Kissoon
11 | * @since 20140426
12 | */
13 | public class RoutingTableSimulation
14 | {
15 |
16 | public RoutingTableSimulation()
17 | {
18 | try
19 | {
20 | /* Setting up 2 Kad networks */
21 | JKademliaNode kad1 = new JKademliaNode("JoshuaK", new KademliaId("ASF45678947584567463"), 12049);
22 | JKademliaNode kad2 = new JKademliaNode("Crystal", new KademliaId("ASF45678947584567464"), 4585);
23 | JKademliaNode kad3 = new JKademliaNode("Shameer", new KademliaId("ASF45678947584567465"), 8104);
24 | JKademliaNode kad4 = new JKademliaNode("Lokesh", new KademliaId("ASF45678947584567466"), 8335);
25 | JKademliaNode kad5 = new JKademliaNode("Chandu", new KademliaId("ASF45678947584567467"), 13345);
26 |
27 | KademliaRoutingTable rt = kad1.getRoutingTable();
28 |
29 | rt.insert(kad2.getNode());
30 | rt.insert(kad3.getNode());
31 | rt.insert(kad4.getNode());
32 | System.out.println(rt);
33 |
34 | rt.insert(kad5.getNode());
35 | System.out.println(rt);
36 |
37 | rt.insert(kad3.getNode());
38 | System.out.println(rt);
39 |
40 |
41 | /* Lets shut down a node and then try putting a content on the network. We'll then see how the un-responsive contacts work */
42 | }
43 | catch (IllegalStateException e)
44 | {
45 |
46 | }
47 | catch (Exception e)
48 | {
49 | e.printStackTrace();
50 | }
51 | }
52 |
53 | public static void main(String[] args)
54 | {
55 | new RoutingTableSimulation();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/kademlia/message/ContentLookupMessage.java:
--------------------------------------------------------------------------------
1 | package kademlia.message;
2 |
3 | import java.io.DataInputStream;
4 | import java.io.DataOutputStream;
5 | import java.io.IOException;
6 | import kademlia.dht.GetParameter;
7 | import kademlia.node.Node;
8 | import kademlia.util.serializer.JsonSerializer;
9 |
10 | /**
11 | * Messages used to send to another node requesting content.
12 | *
13 | * @author Joshua Kissoon
14 | * @since 20140226
15 | */
16 | public class ContentLookupMessage implements Message
17 | {
18 |
19 | public static final byte CODE = 0x03;
20 |
21 | private Node origin;
22 | private GetParameter params;
23 |
24 | /**
25 | * @param origin The node where this lookup came from
26 | * @param params The parameters used to find the content
27 | */
28 | public ContentLookupMessage(Node origin, GetParameter params)
29 | {
30 | this.origin = origin;
31 | this.params = params;
32 | }
33 |
34 | public ContentLookupMessage(DataInputStream in) throws IOException
35 | {
36 | this.fromStream(in);
37 | }
38 |
39 | public GetParameter getParameters()
40 | {
41 | return this.params;
42 | }
43 |
44 | public Node getOrigin()
45 | {
46 | return this.origin;
47 | }
48 |
49 | @Override
50 | public void toStream(DataOutputStream out) throws IOException
51 | {
52 | this.origin.toStream(out);
53 |
54 | /* Write the params to the stream */
55 | new JsonSerializer().write(this.params, out);
56 | }
57 |
58 | @Override
59 | public final void fromStream(DataInputStream in) throws IOException
60 | {
61 | this.origin = new Node(in);
62 |
63 | /* Read the params from the stream */
64 | try
65 | {
66 | this.params = new JsonSerializer().read(in);
67 | }
68 | catch (ClassNotFoundException e)
69 | {
70 | e.printStackTrace();
71 | }
72 | }
73 |
74 | @Override
75 | public byte code()
76 | {
77 | return CODE;
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/src/kademlia/message/NodeLookupReceiver.java:
--------------------------------------------------------------------------------
1 | package kademlia.message;
2 |
3 | import java.io.IOException;
4 | import java.util.List;
5 | import kademlia.KadConfiguration;
6 | import kademlia.KadServer;
7 | import kademlia.KademliaNode;
8 | import kademlia.node.Node;
9 |
10 | /**
11 | * Receives a NodeLookupMessage and sends a NodeReplyMessage as reply with the K-Closest nodes to the ID sent.
12 | *
13 | * @author Joshua Kissoon
14 | * @created 20140219
15 | */
16 | public class NodeLookupReceiver implements Receiver
17 | {
18 |
19 | private final KadServer server;
20 | private final KademliaNode localNode;
21 | private final KadConfiguration config;
22 |
23 | public NodeLookupReceiver(KadServer server, KademliaNode local, KadConfiguration config)
24 | {
25 | this.server = server;
26 | this.localNode = local;
27 | this.config = config;
28 | }
29 |
30 | /**
31 | * Handle receiving a NodeLookupMessage
32 | * Find the set of K nodes closest to the lookup ID and return them
33 | *
34 | * @param comm
35 | *
36 | * @throws java.io.IOException
37 | */
38 | @Override
39 | public void receive(Message incoming, int comm) throws IOException
40 | {
41 | NodeLookupMessage msg = (NodeLookupMessage) incoming;
42 |
43 | Node origin = msg.getOrigin();
44 |
45 | /* Update the local space by inserting the origin node. */
46 | this.localNode.getRoutingTable().insert(origin);
47 |
48 | /* Find nodes closest to the LookupId */
49 | List nodes = this.localNode.getRoutingTable().findClosest(msg.getLookupId(), this.config.k());
50 |
51 | /* Respond to the NodeLookupMessage */
52 | Message reply = new NodeReplyMessage(this.localNode.getNode(), nodes);
53 |
54 | if (this.server.isRunning())
55 | {
56 | /* Let the Server send the reply */
57 | this.server.reply(origin, reply, comm);
58 | }
59 | }
60 |
61 | /**
62 | * We don't need to do anything here
63 | *
64 | * @param comm
65 | *
66 | * @throws java.io.IOException
67 | */
68 | @Override
69 | public void timeout(int comm) throws IOException
70 | {
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/kademlia/simulations/ContentSendingTest.java:
--------------------------------------------------------------------------------
1 | package kademlia.simulations;
2 |
3 | import java.io.IOException;
4 | import java.util.UUID;
5 | import kademlia.dht.GetParameter;
6 | import kademlia.JKademliaNode;
7 | import kademlia.dht.KademliaStorageEntry;
8 | import kademlia.exceptions.ContentNotFoundException;
9 | import kademlia.node.KademliaId;
10 |
11 | /**
12 | * Testing sending and receiving content between 2 Nodes on a network
13 | *
14 | * @author Joshua Kissoon
15 | * @since 20140224
16 | */
17 | public class ContentSendingTest
18 | {
19 |
20 | public static void main(String[] args)
21 | {
22 | try
23 | {
24 | /* Setting up 2 Kad networks */
25 | JKademliaNode kad1 = new JKademliaNode("JoshuaK", new KademliaId("ASF45678947584567467"), 7574);
26 | System.out.println("Created Node Kad 1: " + kad1.getNode().getNodeId());
27 | JKademliaNode kad2 = new JKademliaNode("Crystal", new KademliaId("ASERTKJDHGVHERJHGFLK"), 7572);
28 | System.out.println("Created Node Kad 2: " + kad2.getNode().getNodeId());
29 | kad2.bootstrap(kad1.getNode());
30 |
31 | /**
32 | * Lets create the content and share it
33 | */
34 | String data = "";
35 | for (int i = 0; i < 500; i++)
36 | {
37 | data += UUID.randomUUID();
38 | }
39 | System.out.println(data);
40 | DHTContentImpl c = new DHTContentImpl(kad2.getOwnerId(), data);
41 | kad2.put(c);
42 |
43 | /**
44 | * Lets retrieve the content
45 | */
46 | System.out.println("Retrieving Content");
47 | GetParameter gp = new GetParameter(c.getKey(), DHTContentImpl.TYPE);
48 | gp.setOwnerId(c.getOwnerId());
49 | System.out.println("Get Parameter: " + gp);
50 | KademliaStorageEntry conte = kad2.get(gp);
51 | System.out.println("Content Found: " + new DHTContentImpl().fromSerializedForm(conte.getContent()));
52 | System.out.println("Content Metadata: " + conte.getContentMetadata());
53 |
54 | }
55 | catch (IOException | ContentNotFoundException e)
56 | {
57 | e.printStackTrace();
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/kademlia/message/StoreContentMessage.java:
--------------------------------------------------------------------------------
1 | package kademlia.message;
2 |
3 | import java.io.DataInputStream;
4 | import java.io.DataOutputStream;
5 | import java.io.IOException;
6 | import kademlia.dht.JKademliaStorageEntry;
7 | import kademlia.dht.KademliaStorageEntry;
8 | import kademlia.node.Node;
9 | import kademlia.util.serializer.JsonSerializer;
10 |
11 | /**
12 | * A StoreContentMessage used to send a store message to a node
13 | *
14 | * @author Joshua Kissoon
15 | * @since 20140225
16 | */
17 | public class StoreContentMessage implements Message
18 | {
19 |
20 | public static final byte CODE = 0x08;
21 |
22 | private JKademliaStorageEntry content;
23 | private Node origin;
24 |
25 | /**
26 | * @param origin Where the message came from
27 | * @param content The content to be stored
28 | *
29 | */
30 | public StoreContentMessage(Node origin, JKademliaStorageEntry content)
31 | {
32 | this.content = content;
33 | this.origin = origin;
34 | }
35 |
36 | public StoreContentMessage(DataInputStream in) throws IOException
37 | {
38 | this.fromStream(in);
39 | }
40 |
41 | @Override
42 | public void toStream(DataOutputStream out) throws IOException
43 | {
44 | this.origin.toStream(out);
45 |
46 | /* Serialize the KadContent, then send it to the stream */
47 | new JsonSerializer().write(content, out);
48 | }
49 |
50 | @Override
51 | public final void fromStream(DataInputStream in) throws IOException
52 | {
53 | this.origin = new Node(in);
54 | try
55 | {
56 | this.content = new JsonSerializer().read(in);
57 | }
58 | catch (ClassNotFoundException e)
59 | {
60 | e.printStackTrace();
61 | }
62 | }
63 |
64 | public Node getOrigin()
65 | {
66 | return this.origin;
67 | }
68 |
69 | public JKademliaStorageEntry getContent()
70 | {
71 | return this.content;
72 | }
73 |
74 | @Override
75 | public byte code()
76 | {
77 | return CODE;
78 | }
79 |
80 | @Override
81 | public String toString()
82 | {
83 | return "StoreContentMessage[origin=" + origin + ",content=" + content + "]";
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/kademlia/message/ContentMessage.java:
--------------------------------------------------------------------------------
1 | package kademlia.message;
2 |
3 | import java.io.DataInputStream;
4 | import java.io.DataOutputStream;
5 | import java.io.IOException;
6 | import kademlia.dht.JKademliaStorageEntry;
7 | import kademlia.dht.KademliaStorageEntry;
8 | import kademlia.node.Node;
9 | import kademlia.util.serializer.JsonSerializer;
10 |
11 | /**
12 | * A Message used to send content between nodes
13 | *
14 | * @author Joshua Kissoon
15 | * @since 20140226
16 | */
17 | public class ContentMessage implements Message
18 | {
19 |
20 | public static final byte CODE = 0x04;
21 |
22 | private JKademliaStorageEntry content;
23 | private Node origin;
24 |
25 | /**
26 | * @param origin Where the message came from
27 | * @param content The content to be stored
28 | *
29 | */
30 | public ContentMessage(Node origin, JKademliaStorageEntry content)
31 | {
32 | this.content = content;
33 | this.origin = origin;
34 | }
35 |
36 | public ContentMessage(DataInputStream in) throws IOException
37 | {
38 | this.fromStream(in);
39 | }
40 |
41 | @Override
42 | public void toStream(DataOutputStream out) throws IOException
43 | {
44 | this.origin.toStream(out);
45 |
46 | /* Serialize the KadContent, then send it to the stream */
47 | new JsonSerializer().write(content, out);
48 | }
49 |
50 | @Override
51 | public final void fromStream(DataInputStream in) throws IOException
52 | {
53 | this.origin = new Node(in);
54 |
55 | try
56 | {
57 | this.content = new JsonSerializer().read(in);
58 | }
59 | catch (ClassNotFoundException e)
60 | {
61 | System.err.println("ClassNotFoundException when reading StorageEntry; Message: " + e.getMessage());
62 | }
63 | }
64 |
65 | public Node getOrigin()
66 | {
67 | return this.origin;
68 | }
69 |
70 | public JKademliaStorageEntry getContent()
71 | {
72 | return this.content;
73 | }
74 |
75 | @Override
76 | public byte code()
77 | {
78 | return CODE;
79 | }
80 |
81 | @Override
82 | public String toString()
83 | {
84 | return "ContentMessage[origin=" + origin + ",content=" + content + "]";
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/kademlia/operation/BucketRefreshOperation.java:
--------------------------------------------------------------------------------
1 | package kademlia.operation;
2 |
3 | import java.io.IOException;
4 | import kademlia.KadConfiguration;
5 | import kademlia.KadServer;
6 | import kademlia.KademliaNode;
7 | import kademlia.node.KademliaId;
8 |
9 | /**
10 | * At each time interval t, nodes need to refresh their K-Buckets
11 | * This operation takes care of refreshing this node's K-Buckets
12 | *
13 | * @author Joshua Kissoon
14 | * @created 20140224
15 | */
16 | public class BucketRefreshOperation implements Operation
17 | {
18 |
19 | private final KadServer server;
20 | private final KademliaNode localNode;
21 | private final KadConfiguration config;
22 |
23 | public BucketRefreshOperation(KadServer server, KademliaNode localNode, KadConfiguration config)
24 | {
25 | this.server = server;
26 | this.localNode = localNode;
27 | this.config = config;
28 | }
29 |
30 | /**
31 | * Each bucket need to be refreshed at every time interval t.
32 | * Find an identifier in each bucket's range, use it to look for nodes closest to this identifier
33 | * allowing the bucket to be refreshed.
34 | *
35 | * Then Do a NodeLookupOperation for each of the generated NodeIds,
36 | * This will find the K-Closest nodes to that ID, and update the necessary K-Bucket
37 | *
38 | * @throws java.io.IOException
39 | */
40 | @Override
41 | public synchronized void execute() throws IOException
42 | {
43 | for (int i = 1; i < KademliaId.ID_LENGTH; i++)
44 | {
45 | /* Construct a NodeId that is i bits away from the current node Id */
46 | final KademliaId current = this.localNode.getNode().getNodeId().generateNodeIdByDistance(i);
47 |
48 | /* Run the Node Lookup Operation, each in a different thread to speed up things */
49 | new Thread()
50 | {
51 | @Override
52 | public void run()
53 | {
54 | try
55 | {
56 | new NodeLookupOperation(server, localNode, current, BucketRefreshOperation.this.config).execute();
57 | }
58 | catch (IOException e)
59 | {
60 | //System.err.println("Bucket Refresh Operation Failed. Msg: " + e.getMessage());
61 | }
62 | }
63 | }.start();
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/kademlia/simulations/ContentUpdatingTest.java:
--------------------------------------------------------------------------------
1 | package kademlia.simulations;
2 |
3 | import java.io.IOException;
4 | import kademlia.dht.GetParameter;
5 | import kademlia.JKademliaNode;
6 | import kademlia.dht.KademliaStorageEntry;
7 | import kademlia.exceptions.ContentNotFoundException;
8 | import kademlia.node.KademliaId;
9 |
10 | /**
11 | * Testing sending and receiving content between 2 Nodes on a network
12 | *
13 | * @author Joshua Kissoon
14 | * @since 20140224
15 | */
16 | public class ContentUpdatingTest
17 | {
18 |
19 | public static void main(String[] args)
20 | {
21 | try
22 | {
23 | /* Setting up 2 Kad networks */
24 | JKademliaNode kad1 = new JKademliaNode("JoshuaK", new KademliaId("ASF45678947584567467"), 7574);
25 | System.out.println("Created Node Kad 1: " + kad1.getNode().getNodeId());
26 | JKademliaNode kad2 = new JKademliaNode("Crystal", new KademliaId("ASERTKJDHGVHERJHGFLK"), 7572);
27 | System.out.println("Created Node Kad 2: " + kad2.getNode().getNodeId());
28 | kad2.bootstrap(kad1.getNode());
29 |
30 | /* Lets create the content and share it */
31 | DHTContentImpl c = new DHTContentImpl(kad2.getOwnerId(), "Some Data");
32 | kad2.put(c);
33 |
34 | /* Lets retrieve the content */
35 | System.out.println("Retrieving Content");
36 | GetParameter gp = new GetParameter(c.getKey(), DHTContentImpl.TYPE, c.getOwnerId());
37 |
38 | System.out.println("Get Parameter: " + gp);
39 | KademliaStorageEntry conte = kad2.get(gp);
40 | System.out.println("Content Found: " + new DHTContentImpl().fromSerializedForm(conte.getContent()));
41 | System.out.println("Content Metadata: " + conte.getContentMetadata());
42 |
43 | /* Lets update the content and put it again */
44 | c.setData("Some New Data");
45 | kad2.put(c);
46 |
47 | /* Lets retrieve the content */
48 | System.out.println("Retrieving Content Again");
49 | conte = kad2.get(gp);
50 | System.out.println("Content Found: " + new DHTContentImpl().fromSerializedForm(conte.getContent()));
51 | System.out.println("Content Metadata: " + conte.getContentMetadata());
52 |
53 | }
54 | catch (IOException | ContentNotFoundException e)
55 | {
56 | e.printStackTrace();
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/kademlia/message/NodeReplyMessage.java:
--------------------------------------------------------------------------------
1 | package kademlia.message;
2 |
3 | import java.io.DataInputStream;
4 | import java.io.DataOutputStream;
5 | import java.io.IOException;
6 | import java.util.ArrayList;
7 | import java.util.List;
8 | import kademlia.node.Node;
9 |
10 | /**
11 | * A message used to connect nodes.
12 | * When a NodeLookup Request comes in, we respond with a NodeReplyMessage.
13 | *
14 | * @author Joshua Kissoon
15 | * @created 20140218
16 | */
17 | public class NodeReplyMessage implements Message
18 | {
19 |
20 | private Node origin;
21 | public static final byte CODE = 0x06;
22 | private List nodes;
23 |
24 | public NodeReplyMessage(Node origin, List nodes)
25 | {
26 | this.origin = origin;
27 | this.nodes = nodes;
28 | }
29 |
30 | public NodeReplyMessage(DataInputStream in) throws IOException
31 | {
32 | this.fromStream(in);
33 | }
34 |
35 | @Override
36 | public final void fromStream(DataInputStream in) throws IOException
37 | {
38 | /* Read in the origin */
39 | this.origin = new Node(in);
40 |
41 | /* Get the number of incoming nodes */
42 | int len = in.readInt();
43 | this.nodes = new ArrayList<>(len);
44 |
45 | /* Read in all nodes */
46 | for (int i = 0; i < len; i++)
47 | {
48 | this.nodes.add(new Node(in));
49 | }
50 | }
51 |
52 | @Override
53 | public void toStream(DataOutputStream out) throws IOException
54 | {
55 | /* Add the origin node to the stream */
56 | origin.toStream(out);
57 |
58 | /* Add all other nodes to the stream */
59 | int len = this.nodes.size();
60 | if (len > 255)
61 | {
62 | throw new IndexOutOfBoundsException("Too many nodes in list to send in NodeReplyMessage. Size: " + len);
63 | }
64 |
65 | /* Writing the nodes to the stream */
66 | out.writeInt(len);
67 | for (Node n : this.nodes)
68 | {
69 | n.toStream(out);
70 | }
71 | }
72 |
73 | public Node getOrigin()
74 | {
75 | return this.origin;
76 | }
77 |
78 | @Override
79 | public byte code()
80 | {
81 | return CODE;
82 | }
83 |
84 | public List getNodes()
85 | {
86 | return this.nodes;
87 | }
88 |
89 | @Override
90 | public String toString()
91 | {
92 | return "NodeReplyMessage[origin NodeId=" + origin.getNodeId() + "]";
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/kademlia/routing/KademliaBucket.java:
--------------------------------------------------------------------------------
1 | package kademlia.routing;
2 |
3 | import java.util.List;
4 | import kademlia.node.Node;
5 |
6 | /**
7 | * A bucket used to store Contacts in the routing table.
8 | *
9 | * @author Joshua Kissoon
10 | * @created 20140215
11 | */
12 | public interface KademliaBucket
13 | {
14 |
15 | /**
16 | * Adds a contact to the bucket
17 | *
18 | * @param c the new contact
19 | */
20 | public void insert(Contact c);
21 |
22 | /**
23 | * Create a new contact and insert it into the bucket.
24 | *
25 | * @param n The node to create the contact from
26 | */
27 | public void insert(Node n);
28 |
29 | /**
30 | * Checks if this bucket contain a contact
31 | *
32 | * @param c The contact to check for
33 | *
34 | * @return boolean
35 | */
36 | public boolean containsContact(Contact c);
37 |
38 | /**
39 | * Checks if this bucket contain a node
40 | *
41 | * @param n The node to check for
42 | *
43 | * @return boolean
44 | */
45 | public boolean containsNode(Node n);
46 |
47 | /**
48 | * Remove a contact from this bucket.
49 | *
50 | * If there are replacement contacts in the replacement cache,
51 | * select the last seen one and put it into the bucket while removing the required contact.
52 | *
53 | * If there are no contacts in the replacement cache, then we just mark the contact requested to be removed as stale.
54 | * Marking as stale would actually be incrementing the stale count of the contact.
55 | *
56 | * @param c The contact to remove
57 | *
58 | * @return Boolean whether the removal was successful.
59 | */
60 | public boolean removeContact(Contact c);
61 |
62 | /**
63 | * Remove the contact object related to a node from this bucket
64 | *
65 | * @param n The node of the contact to remove
66 | *
67 | * @return Boolean whether the removal was successful.
68 | */
69 | public boolean removeNode(Node n);
70 |
71 | /**
72 | * Counts the number of contacts in this bucket.
73 | *
74 | * @return Integer The number of contacts in this bucket
75 | */
76 | public int numContacts();
77 |
78 | /**
79 | * @return Integer The depth of this bucket in the RoutingTable
80 | */
81 | public int getDepth();
82 |
83 | /**
84 | * @return An Iterable structure with all contacts in this bucket
85 | */
86 | public List getContacts();
87 | }
88 |
--------------------------------------------------------------------------------
/src/kademlia/simulations/SaveStateTest2.java:
--------------------------------------------------------------------------------
1 | package kademlia.simulations;
2 |
3 | import kademlia.JKademliaNode;
4 | import kademlia.dht.GetParameter;
5 | import kademlia.dht.KademliaStorageEntry;
6 | import kademlia.node.KademliaId;
7 |
8 | /**
9 | * Testing the save and retrieve state operations.
10 | * Here we also try to look for content on a restored node
11 | *
12 | * @author Joshua Kissoon
13 | * @since 20140309
14 | */
15 | public class SaveStateTest2
16 | {
17 |
18 | public SaveStateTest2()
19 | {
20 | try
21 | {
22 | /* Setting up 2 Kad networks */
23 | JKademliaNode kad1 = new JKademliaNode("JoshuaK", new KademliaId("ASF45678947584567463"), 12049);
24 | JKademliaNode kad2 = new JKademliaNode("Crystal", new KademliaId("ASF45678947584567464"), 4585);
25 |
26 | /* Connecting 2 to 1 */
27 | System.out.println("Connecting Nodes 1 & 2");
28 | kad2.bootstrap(kad1.getNode());
29 | System.out.println(kad1);
30 | System.out.println(kad2);
31 |
32 | DHTContentImpl c;
33 | synchronized (this)
34 | {
35 | System.out.println("\n\n\n\nSTORING CONTENT 1\n\n\n\n");
36 | c = new DHTContentImpl(kad2.getOwnerId(), "Some Data");
37 | System.out.println(c);
38 | kad1.putLocally(c);
39 | }
40 |
41 | System.out.println(kad1);
42 | System.out.println(kad2);
43 |
44 | /* Shutting down kad1 and restarting it */
45 | System.out.println("\n\n\nShutting down Kad 1 instance");
46 | kad1.shutdown(true);
47 |
48 | System.out.println("\n\n\nReloading Kad instance from file");
49 | kad1 = JKademliaNode.loadFromFile("JoshuaK");
50 | kad1.bootstrap(kad2.getNode());
51 | System.out.println(kad2);
52 |
53 | /* Trying to get a content stored on the restored node */
54 | GetParameter gp = new GetParameter(c.getKey(), kad2.getOwnerId(), c.getType());
55 | KademliaStorageEntry content = kad2.get(gp);
56 | DHTContentImpl cc = new DHTContentImpl().fromSerializedForm(content.getContent());
57 | System.out.println("Content received: " + cc);
58 | }
59 |
60 | catch (Exception e)
61 | {
62 | e.printStackTrace();
63 | }
64 | }
65 |
66 | public static void main(String[] args)
67 | {
68 | new SaveStateTest2();
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/kademlia/KadStatistician.java:
--------------------------------------------------------------------------------
1 | package kademlia;
2 |
3 | /**
4 | * Specification for class that keeps statistics for a Kademlia instance.
5 | *
6 | * These statistics are temporary and will be lost when Kad is shut down.
7 | *
8 | * @author Joshua Kissoon
9 | * @since 20140507
10 | */
11 | public interface KadStatistician
12 | {
13 |
14 | /**
15 | * Used to indicate some data is sent
16 | *
17 | * @param size The size of the data sent
18 | */
19 | public void sentData(long size);
20 |
21 | /**
22 | * @return The total data sent in KiloBytes
23 | */
24 | public long getTotalDataSent();
25 |
26 | /**
27 | * Used to indicate some data was received
28 | *
29 | * @param size The size of the data received
30 | */
31 | public void receivedData(long size);
32 |
33 | /**
34 | * @return The total data received in KiloBytes
35 | */
36 | public long getTotalDataReceived();
37 |
38 | /**
39 | * Sets the bootstrap time for this Kademlia Node
40 | *
41 | * @param time The bootstrap time in nanoseconds
42 | */
43 | public void setBootstrapTime(long time);
44 |
45 | /**
46 | * @return How long the system took to bootstrap in milliseconds
47 | */
48 | public long getBootstrapTime();
49 |
50 | /**
51 | * Add the timing for a new content lookup operation that took place
52 | *
53 | * @param time The time the content lookup took in nanoseconds
54 | * @param routeLength The length of the route it took to get the content
55 | * @param isSuccessful Whether the content lookup was successful or not
56 | */
57 | public void addContentLookup(long time, int routeLength, boolean isSuccessful);
58 |
59 | /**
60 | * @return The total number of content lookups performed.
61 | */
62 | public int numContentLookups();
63 |
64 | /**
65 | * @return How many content lookups have failed.
66 | */
67 | public int numFailedContentLookups();
68 |
69 | /**
70 | * @return The total time spent on content lookups.
71 | */
72 | public long totalContentLookupTime();
73 |
74 | /**
75 | * Compute the average time a content lookup took
76 | *
77 | * @return The average time in milliseconds
78 | */
79 | public double averageContentLookupTime();
80 |
81 | /**
82 | * Compute the average route length of content lookup operations.
83 | *
84 | * @return The average route length
85 | */
86 | public double averageContentLookupRouteLength();
87 | }
88 |
--------------------------------------------------------------------------------
/src/kademlia/message/ContentLookupReceiver.java:
--------------------------------------------------------------------------------
1 | package kademlia.message;
2 |
3 | import java.io.IOException;
4 | import java.util.NoSuchElementException;
5 | import kademlia.KadConfiguration;
6 | import kademlia.KadServer;
7 | import kademlia.KademliaNode;
8 | import kademlia.dht.KademliaDHT;
9 |
10 | /**
11 | * Responds to a ContentLookupMessage by sending a ContentMessage containing the requested content;
12 | * if the requested content is not found, a NodeReplyMessage containing the K closest nodes to the request key is sent.
13 | *
14 | * @author Joshua Kissoon
15 | * @since 20140226
16 | */
17 | public class ContentLookupReceiver implements Receiver
18 | {
19 |
20 | private final KadServer server;
21 | private final KademliaNode localNode;
22 | private final KademliaDHT dht;
23 | private final KadConfiguration config;
24 |
25 | public ContentLookupReceiver(KadServer server, KademliaNode localNode, KademliaDHT dht, KadConfiguration config)
26 | {
27 | this.server = server;
28 | this.localNode = localNode;
29 | this.dht = dht;
30 | this.config = config;
31 | }
32 |
33 | @Override
34 | public void receive(Message incoming, int comm) throws IOException
35 | {
36 | ContentLookupMessage msg = (ContentLookupMessage) incoming;
37 | this.localNode.getRoutingTable().insert(msg.getOrigin());
38 |
39 | /* Check if we can have this data */
40 | if (this.dht.contains(msg.getParameters()))
41 | {
42 | try
43 | {
44 | /* Return a ContentMessage with the required data */
45 | ContentMessage cMsg = new ContentMessage(localNode.getNode(), this.dht.get(msg.getParameters()));
46 | server.reply(msg.getOrigin(), cMsg, comm);
47 | }
48 | catch (NoSuchElementException ex)
49 | {
50 | /* @todo Not sure why this exception is thrown here, checkup the system when tests are writtem*/
51 | }
52 | }
53 | else
54 | {
55 | /**
56 | * Return a the K closest nodes to this content identifier
57 | * We create a NodeLookupReceiver and let this receiver handle this operation
58 | */
59 | NodeLookupMessage lkpMsg = new NodeLookupMessage(msg.getOrigin(), msg.getParameters().getKey());
60 | new NodeLookupReceiver(server, localNode, this.config).receive(lkpMsg, comm);
61 | }
62 | }
63 |
64 | @Override
65 | public void timeout(int comm)
66 | {
67 |
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/kademlia/simulations/DHTContentImpl.java:
--------------------------------------------------------------------------------
1 | package kademlia.simulations;
2 |
3 | import com.google.gson.Gson;
4 | import kademlia.dht.KadContent;
5 | import kademlia.node.KademliaId;
6 |
7 | /**
8 | * A simple DHT Content object to test DHT storage
9 | *
10 | * @author Joshua Kissoon
11 | * @since 20140224
12 | */
13 | public class DHTContentImpl implements KadContent
14 | {
15 |
16 | public static final transient String TYPE = "DHTContentImpl";
17 |
18 | private KademliaId key;
19 | private String data;
20 | private String ownerId;
21 | private final long createTs;
22 | private long updateTs;
23 |
24 |
25 | {
26 | this.createTs = this.updateTs = System.currentTimeMillis() / 1000L;
27 | }
28 |
29 | public DHTContentImpl()
30 | {
31 |
32 | }
33 |
34 | public DHTContentImpl(String ownerId, String data)
35 | {
36 | this.ownerId = ownerId;
37 | this.data = data;
38 | this.key = new KademliaId();
39 | }
40 |
41 | public DHTContentImpl(KademliaId key, String ownerId)
42 | {
43 | this.key = key;
44 | this.ownerId = ownerId;
45 | }
46 |
47 | public void setData(String newData)
48 | {
49 | this.data = newData;
50 | this.setUpdated();
51 | }
52 |
53 | @Override
54 | public KademliaId getKey()
55 | {
56 | return this.key;
57 | }
58 |
59 | @Override
60 | public String getType()
61 | {
62 | return TYPE;
63 | }
64 |
65 | @Override
66 | public String getOwnerId()
67 | {
68 | return this.ownerId;
69 | }
70 |
71 | /**
72 | * Set the content as updated
73 | */
74 | public void setUpdated()
75 | {
76 | this.updateTs = System.currentTimeMillis() / 1000L;
77 | }
78 |
79 | @Override
80 | public long getCreatedTimestamp()
81 | {
82 | return this.createTs;
83 | }
84 |
85 | @Override
86 | public long getLastUpdatedTimestamp()
87 | {
88 | return this.updateTs;
89 | }
90 |
91 | @Override
92 | public byte[] toSerializedForm()
93 | {
94 | Gson gson = new Gson();
95 | return gson.toJson(this).getBytes();
96 | }
97 |
98 | @Override
99 | public DHTContentImpl fromSerializedForm(byte[] data)
100 | {
101 | Gson gson = new Gson();
102 | DHTContentImpl val = gson.fromJson(new String(data), DHTContentImpl.class);
103 | return val;
104 | }
105 |
106 | @Override
107 | public String toString()
108 | {
109 | return "DHTContentImpl[{data=" + this.data + "{ {key:" + this.key + "}]";
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/kademlia/util/RouteLengthChecker.java:
--------------------------------------------------------------------------------
1 | package kademlia.util;
2 |
3 | import java.util.Collection;
4 | import java.util.HashMap;
5 | import kademlia.node.Node;
6 |
7 | /**
8 | * Class that helps compute the route length taken to complete an operation.
9 | *
10 | * Only used for routing operations - mainly the NodeLookup and ContentLookup Operations.
11 | *
12 | * Idea:
13 | * - Add the original set of nodes with route length 0;
14 | * - 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
15 | *
16 | * @author Joshua Kissoon
17 | * @since 20140510
18 | */
19 | public class RouteLengthChecker
20 | {
21 |
22 | /* Store the nodes and their route length (RL) */
23 | private final HashMap nodes;
24 |
25 | /* Lets cache the max route length instead of having to go and search for it later */
26 | private int maxRouteLength;
27 |
28 |
29 | {
30 | this.nodes = new HashMap<>();
31 | this.maxRouteLength = 1;
32 | }
33 |
34 | /**
35 | * Add the initial nodes in the routing operation
36 | *
37 | * @param initialNodes The set of initial nodes
38 | */
39 | public void addInitialNodes(Collection initialNodes)
40 | {
41 | for (Node n : initialNodes)
42 | {
43 | this.nodes.put(n, 1);
44 | }
45 | }
46 |
47 | /**
48 | * Add any nodes that we get from a node reply.
49 | *
50 | * The route length of these nodes will be their sender + 1;
51 | *
52 | * @param inputSet The set of nodes we receive
53 | * @param sender The node who send the set
54 | */
55 | public void addNodes(Collection inputSet, Node sender)
56 | {
57 | if (!this.nodes.containsKey(sender))
58 | {
59 | return;
60 | }
61 |
62 | /* Get the route length of the input set - sender RL + 1 */
63 | int inputSetRL = this.nodes.get(sender) + 1;
64 |
65 | if (inputSetRL > this.maxRouteLength)
66 | {
67 | this.maxRouteLength = inputSetRL;
68 | }
69 |
70 | /* Add the nodes to our set */
71 | for (Node n : inputSet)
72 | {
73 | /* We only add if the node is not already there... */
74 | if (!this.nodes.containsKey(n))
75 | {
76 | this.nodes.put(n, inputSetRL);
77 | }
78 | }
79 | }
80 |
81 | /**
82 | * Get the route length of the operation!
83 | *
84 | * It will be the max route length of all the nodes here.
85 | *
86 | * @return The route length
87 | */
88 | public int getRouteLength()
89 | {
90 | return this.maxRouteLength;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/kademlia/DefaultConfiguration.java:
--------------------------------------------------------------------------------
1 | package kademlia;
2 |
3 | import java.io.File;
4 |
5 | /**
6 | * A set of Kademlia configuration parameters. Default values are
7 | * supplied and can be changed by the application as necessary.
8 | *
9 | */
10 | public class DefaultConfiguration implements KadConfiguration
11 | {
12 |
13 | private final static long RESTORE_INTERVAL = 60 * 1000; // in milliseconds
14 | private final static long RESPONSE_TIMEOUT = 2000;
15 | private final static long OPERATION_TIMEOUT = 2000;
16 | private final static int CONCURRENCY = 10;
17 | private final static int K = 5;
18 | private final static int RCSIZE = 3;
19 | private final static int STALE = 1;
20 | private final static String LOCAL_FOLDER = "kademlia";
21 |
22 | private final static boolean IS_TESTING = true;
23 |
24 | /**
25 | * Default constructor to support Gson Serialization
26 | */
27 | public DefaultConfiguration()
28 | {
29 |
30 | }
31 |
32 | @Override
33 | public long restoreInterval()
34 | {
35 | return RESTORE_INTERVAL;
36 | }
37 |
38 | @Override
39 | public long responseTimeout()
40 | {
41 | return RESPONSE_TIMEOUT;
42 | }
43 |
44 | @Override
45 | public long operationTimeout()
46 | {
47 | return OPERATION_TIMEOUT;
48 | }
49 |
50 | @Override
51 | public int maxConcurrentMessagesTransiting()
52 | {
53 | return CONCURRENCY;
54 | }
55 |
56 | @Override
57 | public int k()
58 | {
59 | return K;
60 | }
61 |
62 | @Override
63 | public int replacementCacheSize()
64 | {
65 | return RCSIZE;
66 | }
67 |
68 | @Override
69 | public int stale()
70 | {
71 | return STALE;
72 | }
73 |
74 | @Override
75 | public String getNodeDataFolder(String ownerId)
76 | {
77 | /* Setup the main storage folder if it doesn't exist */
78 | String path = System.getProperty("user.home") + File.separator + DefaultConfiguration.LOCAL_FOLDER;
79 | File folder = new File(path);
80 | if (!folder.isDirectory())
81 | {
82 | folder.mkdir();
83 | }
84 |
85 | /* Setup subfolder for this owner if it doesn't exist */
86 | File ownerFolder = new File(folder + File.separator + ownerId);
87 | if (!ownerFolder.isDirectory())
88 | {
89 | ownerFolder.mkdir();
90 | }
91 |
92 | /* Return the path */
93 | return ownerFolder.toString();
94 | }
95 |
96 | @Override
97 | public boolean isTesting()
98 | {
99 | return IS_TESTING;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/kademlia/routing/KademliaRoutingTable.java:
--------------------------------------------------------------------------------
1 | package kademlia.routing;
2 |
3 | import java.util.List;
4 | import kademlia.KadConfiguration;
5 | import kademlia.node.Node;
6 | import kademlia.node.KademliaId;
7 |
8 | /**
9 | * Specification for Kademlia's Routing Table
10 | *
11 | * @author Joshua Kissoon
12 | * @since 20140501
13 | */
14 | public interface KademliaRoutingTable
15 | {
16 |
17 | /**
18 | * Initialize the RoutingTable to it's default state
19 | */
20 | public void initialize();
21 |
22 | /**
23 | * Sets the configuration file for this routing table
24 | *
25 | * @param config
26 | */
27 | public void setConfiguration(KadConfiguration config);
28 |
29 | /**
30 | * Adds a contact to the routing table based on how far it is from the LocalNode.
31 | *
32 | * @param c The contact to add
33 | */
34 | public void insert(Contact c);
35 |
36 | /**
37 | * Adds a node to the routing table based on how far it is from the LocalNode.
38 | *
39 | * @param n The node to add
40 | */
41 | public void insert(Node n);
42 |
43 | /**
44 | * 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.
45 | *
46 | * @param nid The NodeId for which we want to find which bucket it belong to
47 | *
48 | * @return Integer The bucket ID in which the given node should be placed.
49 | */
50 | public int getBucketId(KademliaId nid);
51 |
52 | /**
53 | * Find the closest set of contacts to a given NodeId
54 | *
55 | * @param target The NodeId to find contacts close to
56 | * @param numNodesRequired The number of contacts to find
57 | *
58 | * @return List A List of contacts closest to target
59 | */
60 | public List findClosest(KademliaId target, int numNodesRequired);
61 |
62 | /**
63 | * @return List A List of all Nodes in this RoutingTable
64 | */
65 | public List getAllNodes();
66 |
67 | /**
68 | * @return List A List of all Nodes in this RoutingTable
69 | */
70 | public List getAllContacts();
71 |
72 | /**
73 | * @return Bucket[] The buckets in this Kad Instance
74 | */
75 | public KademliaBucket[] getBuckets();
76 |
77 | /**
78 | * Method used by operations to notify the routing table of any contacts that have been unresponsive.
79 | *
80 | * @param contacts The set of unresponsive contacts
81 | */
82 | public void setUnresponsiveContacts(List contacts);
83 |
84 | /**
85 | * Method used by operations to notify the routing table of any contacts that have been unresponsive.
86 | *
87 | * @param n
88 | */
89 | public void setUnresponsiveContact(Node n);
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/src/kademlia/message/MessageFactory.java:
--------------------------------------------------------------------------------
1 | package kademlia.message;
2 |
3 | import java.io.DataInputStream;
4 | import java.io.IOException;
5 | import kademlia.KadConfiguration;
6 | import kademlia.KadServer;
7 | import kademlia.KademliaNode;
8 | import kademlia.dht.KademliaDHT;
9 |
10 | /**
11 | * Handles creating messages and receivers
12 | *
13 | * @author Joshua Kissoon
14 | * @since 20140202
15 | */
16 | public class MessageFactory implements KademliaMessageFactory
17 | {
18 |
19 | private final KademliaNode localNode;
20 | private final KademliaDHT dht;
21 | private final KadConfiguration config;
22 |
23 | public MessageFactory(KademliaNode local, KademliaDHT dht, KadConfiguration config)
24 | {
25 | this.localNode = local;
26 | this.dht = dht;
27 | this.config = config;
28 | }
29 |
30 | @Override
31 | public Message createMessage(byte code, DataInputStream in) throws IOException
32 | {
33 | switch (code)
34 | {
35 | case AcknowledgeMessage.CODE:
36 | return new AcknowledgeMessage(in);
37 | case ConnectMessage.CODE:
38 | return new ConnectMessage(in);
39 | case ContentMessage.CODE:
40 | return new ContentMessage(in);
41 | case ContentLookupMessage.CODE:
42 | return new ContentLookupMessage(in);
43 | case NodeLookupMessage.CODE:
44 | return new NodeLookupMessage(in);
45 | case NodeReplyMessage.CODE:
46 | return new NodeReplyMessage(in);
47 | case SimpleMessage.CODE:
48 | return new SimpleMessage(in);
49 | case StoreContentMessage.CODE:
50 | return new StoreContentMessage(in);
51 | default:
52 | //System.out.println(this.localNode + " - No Message handler found for message. Code: " + code);
53 | return new SimpleMessage(in);
54 |
55 | }
56 | }
57 |
58 | @Override
59 | public Receiver createReceiver(byte code, KadServer server)
60 | {
61 | switch (code)
62 | {
63 | case ConnectMessage.CODE:
64 | return new ConnectReceiver(server, this.localNode);
65 | case ContentLookupMessage.CODE:
66 | return new ContentLookupReceiver(server, this.localNode, this.dht, this.config);
67 | case NodeLookupMessage.CODE:
68 | return new NodeLookupReceiver(server, this.localNode, this.config);
69 | case StoreContentMessage.CODE:
70 | return new StoreContentReceiver(server, this.localNode, this.dht);
71 | default:
72 | //System.out.println("No receiver found for message. Code: " + code);
73 | return new SimpleReceiver();
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/nbproject/project.properties:
--------------------------------------------------------------------------------
1 | annotation.processing.enabled=true
2 | annotation.processing.enabled.in.editor=false
3 | annotation.processing.processors.list=
4 | annotation.processing.run.all.processors=true
5 | annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output
6 | application.title=Kademlia
7 | application.vendor=Joshua
8 | build.classes.dir=${build.dir}/classes
9 | build.classes.excludes=**/*.java,**/*.form
10 | # This directory is removed when the project is cleaned:
11 | build.dir=build
12 | build.generated.dir=${build.dir}/generated
13 | build.generated.sources.dir=${build.dir}/generated-sources
14 | # Only compile against the classpath explicitly listed here:
15 | build.sysclasspath=ignore
16 | build.test.classes.dir=${build.dir}/test/classes
17 | build.test.results.dir=${build.dir}/test/results
18 | # Uncomment to specify the preferred debugger connection transport:
19 | #debug.transport=dt_socket
20 | debug.classpath=\
21 | ${run.classpath}
22 | debug.test.classpath=\
23 | ${run.test.classpath}
24 | # Files in build.classes.dir which should be excluded from distribution jar
25 | dist.archive.excludes=
26 | # This directory is removed when the project is cleaned:
27 | dist.dir=dist
28 | dist.jar=${dist.dir}/Kademlia.jar
29 | dist.javadoc.dir=${dist.dir}/javadoc
30 | endorsed.classpath=
31 | excludes=
32 | file.reference.gson-2.2.4.jar=C:\\Users\\Joshua\\Documents\\NetBeansProjects\\Libraries\\gson-2.2.4.jar
33 | includes=**
34 | jar.compress=false
35 | javac.classpath=\
36 | ${file.reference.gson-2.2.4.jar}
37 | # Space-separated list of extra javac options
38 | javac.compilerargs=
39 | javac.deprecation=false
40 | javac.processorpath=\
41 | ${javac.classpath}
42 | javac.source=1.8
43 | javac.target=1.8
44 | javac.test.classpath=\
45 | ${javac.classpath}:\
46 | ${build.classes.dir}
47 | javac.test.processorpath=\
48 | ${javac.test.classpath}
49 | javadoc.additionalparam=
50 | javadoc.author=false
51 | javadoc.encoding=${source.encoding}
52 | javadoc.noindex=false
53 | javadoc.nonavbar=false
54 | javadoc.notree=false
55 | javadoc.private=false
56 | javadoc.splitindex=true
57 | javadoc.use=true
58 | javadoc.version=false
59 | javadoc.windowtitle=
60 | main.class=kademlia.KademliaBasic
61 | manifest.file=manifest.mf
62 | meta.inf.dir=${src.dir}/META-INF
63 | mkdist.disabled=false
64 | platform.active=default_platform
65 | run.classpath=\
66 | ${javac.classpath}:\
67 | ${build.classes.dir}
68 | # Space-separated list of JVM arguments used when running the project.
69 | # You may also define separate properties like run-sys-prop.name=value instead of -Dname=value.
70 | # To set system properties for unit tests define test-sys-prop.name=value:
71 | run.jvmargs=
72 | run.test.classpath=\
73 | ${javac.test.classpath}:\
74 | ${build.test.classes.dir}
75 | source.encoding=UTF-8
76 | src.dir=src
77 | test.src.dir=test
78 |
--------------------------------------------------------------------------------
/src/kademlia/dht/GetParameter.java:
--------------------------------------------------------------------------------
1 | package kademlia.dht;
2 |
3 | import kademlia.node.KademliaId;
4 |
5 | /**
6 | * A GET request can get content based on Key, Owner, Type, etc
7 | *
8 | * This is a class containing the parameters to be passed in a GET request
9 | *
10 | * We use a class since the number of filtering parameters can change later
11 | *
12 | * @author Joshua Kissoon
13 | * @since 20140224
14 | */
15 | public class GetParameter
16 | {
17 |
18 | private KademliaId key;
19 | private String ownerId = null;
20 | private String type = null;
21 |
22 | /**
23 | * Construct a GetParameter to search for data by NodeId and owner
24 | *
25 | * @param key
26 | * @param type
27 | */
28 | public GetParameter(KademliaId key, String type)
29 | {
30 | this.key = key;
31 | this.type = type;
32 | }
33 |
34 | /**
35 | * Construct a GetParameter to search for data by NodeId, owner, type
36 | *
37 | * @param key
38 | * @param type
39 | * @param owner
40 | */
41 | public GetParameter(KademliaId key, String type, String owner)
42 | {
43 | this(key, type);
44 | this.ownerId = owner;
45 | }
46 |
47 | /**
48 | * Construct our get parameter from a Content
49 | *
50 | * @param c
51 | */
52 | public GetParameter(KadContent c)
53 | {
54 | this.key = c.getKey();
55 |
56 | if (c.getType() != null)
57 | {
58 | this.type = c.getType();
59 | }
60 |
61 | if (c.getOwnerId() != null)
62 | {
63 | this.ownerId = c.getOwnerId();
64 | }
65 | }
66 |
67 | /**
68 | * Construct our get parameter from a StorageEntryMeta data
69 | *
70 | * @param md
71 | */
72 | public GetParameter(KademliaStorageEntryMetadata md)
73 | {
74 | this.key = md.getKey();
75 |
76 | if (md.getType() != null)
77 | {
78 | this.type = md.getType();
79 | }
80 |
81 | if (md.getOwnerId() != null)
82 | {
83 | this.ownerId = md.getOwnerId();
84 | }
85 | }
86 |
87 | public KademliaId getKey()
88 | {
89 | return this.key;
90 | }
91 |
92 | public void setOwnerId(String ownerId)
93 | {
94 | this.ownerId = ownerId;
95 | }
96 |
97 | public String getOwnerId()
98 | {
99 | return this.ownerId;
100 | }
101 |
102 | public void setType(String type)
103 | {
104 | this.type = type;
105 | }
106 |
107 | public String getType()
108 | {
109 | return this.type;
110 | }
111 |
112 | @Override
113 | public String toString()
114 | {
115 | return "GetParameter - [Key: " + key + "][Owner: " + this.ownerId + "][Type: " + this.type + "]";
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/kademlia/operation/StoreOperation.java:
--------------------------------------------------------------------------------
1 | package kademlia.operation;
2 |
3 | import java.io.IOException;
4 | import java.util.List;
5 | import kademlia.KadConfiguration;
6 | import kademlia.KadServer;
7 | import kademlia.KademliaNode;
8 | import kademlia.dht.JKademliaStorageEntry;
9 | import kademlia.dht.KademliaDHT;
10 | import kademlia.dht.KademliaStorageEntry;
11 | import kademlia.message.Message;
12 | import kademlia.message.StoreContentMessage;
13 | import kademlia.node.Node;
14 |
15 | /**
16 | * Operation that stores a DHT Content onto the K closest nodes to the content Key
17 | *
18 | * @author Joshua Kissoon
19 | * @since 20140224
20 | */
21 | public class StoreOperation implements Operation
22 | {
23 |
24 | private final KadServer server;
25 | private final KademliaNode localNode;
26 | private final JKademliaStorageEntry storageEntry;
27 | private final KademliaDHT localDht;
28 | private final KadConfiguration config;
29 |
30 | /**
31 | * @param server
32 | * @param localNode
33 | * @param storageEntry The content to be stored on the DHT
34 | * @param localDht The local DHT
35 | * @param config
36 | */
37 | public StoreOperation(KadServer server, KademliaNode localNode, JKademliaStorageEntry storageEntry, KademliaDHT localDht, KadConfiguration config)
38 | {
39 | this.server = server;
40 | this.localNode = localNode;
41 | this.storageEntry = storageEntry;
42 | this.localDht = localDht;
43 | this.config = config;
44 | }
45 |
46 | @Override
47 | public synchronized void execute() throws IOException
48 | {
49 | /* Get the nodes on which we need to store the content */
50 | NodeLookupOperation ndlo = new NodeLookupOperation(this.server, this.localNode, this.storageEntry.getContentMetadata().getKey(), this.config);
51 | ndlo.execute();
52 | List nodes = ndlo.getClosestNodes();
53 |
54 | /* Create the message */
55 | Message msg = new StoreContentMessage(this.localNode.getNode(), this.storageEntry);
56 |
57 | /*Store the message on all of the K-Nodes*/
58 | for (Node n : nodes)
59 | {
60 | if (n.equals(this.localNode.getNode()))
61 | {
62 | /* Store the content locally */
63 | this.localDht.store(this.storageEntry);
64 | }
65 | else
66 | {
67 | /**
68 | * @todo Create a receiver that receives a store acknowledgement message to count how many nodes a content have been stored at
69 | */
70 | this.server.sendMessage(n, msg, null);
71 | }
72 | }
73 | }
74 |
75 | /**
76 | * @return The number of nodes that have stored this content
77 | *
78 | * @todo Implement this method
79 | */
80 | public int numNodesStoredAt()
81 | {
82 | return 1;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/kademlia/simulations/AutoRefreshOperation2.java:
--------------------------------------------------------------------------------
1 | package kademlia.simulations;
2 |
3 | import java.util.Timer;
4 | import java.util.TimerTask;
5 | import kademlia.DefaultConfiguration;
6 | import kademlia.JKademliaNode;
7 | import kademlia.KadConfiguration;
8 | import kademlia.node.KademliaId;
9 |
10 | /**
11 | * Testing the Kademlia Auto Content and Node table refresh operations
12 | *
13 | * @author Joshua Kissoon
14 | * @since 20140309
15 | */
16 | public class AutoRefreshOperation2 implements Simulation
17 | {
18 |
19 | @Override
20 | public void runSimulation()
21 | {
22 | try
23 | {
24 | /* Setting up 2 Kad networks */
25 | final JKademliaNode kad1 = new JKademliaNode("JoshuaK", new KademliaId("ASF456789djem4567463"), 12049);
26 | final JKademliaNode kad2 = new JKademliaNode("Crystal", new KademliaId("AS84k678DJRW84567465"), 4585);
27 | final JKademliaNode kad3 = new JKademliaNode("Shameer", new KademliaId("AS84k67894758456746A"), 8104);
28 |
29 | /* Connecting nodes */
30 | System.out.println("Connecting Nodes");
31 | kad2.bootstrap(kad1.getNode());
32 | kad3.bootstrap(kad2.getNode());
33 |
34 | DHTContentImpl c = new DHTContentImpl(new KademliaId("AS84k678947584567465"), kad1.getOwnerId());
35 | c.setData("Setting the data");
36 | kad1.putLocally(c);
37 |
38 | System.out.println("\n Content ID: " + c.getKey());
39 | System.out.println(kad1.getNode() + " Distance from content: " + kad1.getNode().getNodeId().getDistance(c.getKey()));
40 | System.out.println(kad2.getNode() + " Distance from content: " + kad2.getNode().getNodeId().getDistance(c.getKey()));
41 | System.out.println(kad3.getNode() + " Distance from content: " + kad3.getNode().getNodeId().getDistance(c.getKey()));
42 | System.out.println("\nSTORING CONTENT 1 locally on " + kad1.getOwnerId() + "\n\n\n\n");
43 |
44 | System.out.println(kad1);
45 | System.out.println(kad2);
46 | System.out.println(kad3);
47 |
48 | /* Print the node states every few minutes */
49 | KadConfiguration config = new DefaultConfiguration();
50 | Timer timer = new Timer(true);
51 | timer.schedule(
52 | new TimerTask()
53 | {
54 | @Override
55 | public void run()
56 | {
57 | System.out.println(kad1);
58 | System.out.println(kad2);
59 | System.out.println(kad3);
60 | }
61 | },
62 | // Delay // Interval
63 | config.restoreInterval(), config.restoreInterval()
64 | );
65 | }
66 |
67 | catch (Exception e)
68 | {
69 | e.printStackTrace();
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/kademlia/util/HashCalculator.java:
--------------------------------------------------------------------------------
1 | package kademlia.util;
2 |
3 | import java.security.MessageDigest;
4 | import java.security.NoSuchAlgorithmException;
5 |
6 | /**
7 | * A class that is used to calculate the hash of strings.
8 | *
9 | * @author Joshua Kissoon
10 | * @since 20140405
11 | */
12 | public class HashCalculator
13 | {
14 |
15 | /**
16 | * Computes the SHA-1 Hash.
17 | *
18 | * @param toHash The string to hash
19 | *
20 | * @return byte[20] The hashed string
21 | *
22 | * @throws java.security.NoSuchAlgorithmException
23 | */
24 | public static byte[] sha1Hash(String toHash) throws NoSuchAlgorithmException
25 | {
26 | /* Create a MessageDigest */
27 | MessageDigest md = MessageDigest.getInstance("SHA-1");
28 |
29 | /* Add password bytes to digest */
30 | md.update(toHash.getBytes());
31 |
32 | /* Get the hashed bytes */
33 | return md.digest();
34 | }
35 |
36 | /**
37 | * Computes the SHA-1 Hash using a Salt.
38 | *
39 | * @param toHash The string to hash
40 | * @param salt A salt used to blind the hash
41 | *
42 | * @return byte[20] The hashed string
43 | *
44 | * @throws java.security.NoSuchAlgorithmException
45 | */
46 | public static byte[] sha1Hash(String toHash, String salt) throws NoSuchAlgorithmException
47 | {
48 | /* Create a MessageDigest */
49 | MessageDigest md = MessageDigest.getInstance("SHA-1");
50 |
51 | /* Add password bytes to digest */
52 | md.update(toHash.getBytes());
53 |
54 | /* Get the hashed bytes */
55 | return md.digest(salt.getBytes());
56 | }
57 |
58 | /**
59 | * Computes the MD5 Hash.
60 | *
61 | * @param toHash The string to hash
62 | *
63 | * @return byte[16] The hashed string
64 | *
65 | * @throws java.security.NoSuchAlgorithmException
66 | */
67 | public static byte[] md5Hash(String toHash) throws NoSuchAlgorithmException
68 | {
69 | /* Create a MessageDigest */
70 | MessageDigest md = MessageDigest.getInstance("MD5");
71 |
72 | /* Add password bytes to digest */
73 | md.update(toHash.getBytes());
74 |
75 | /* Get the hashed bytes */
76 | return md.digest();
77 | }
78 |
79 | /**
80 | * Computes the MD5 Hash using a salt.
81 | *
82 | * @param toHash The string to hash
83 | * @param salt A salt used to blind the hash
84 | *
85 | * @return byte[16] The hashed string
86 | *
87 | * @throws java.security.NoSuchAlgorithmException
88 | */
89 | public static byte[] md5Hash(String toHash, String salt) throws NoSuchAlgorithmException
90 | {
91 | /* Create a MessageDigest */
92 | MessageDigest md = MessageDigest.getInstance("MD5");
93 |
94 | /* Add password bytes to digest */
95 | md.update(toHash.getBytes());
96 |
97 | /* Get the hashed bytes */
98 | return md.digest(salt.getBytes());
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/kademlia/util/serializer/JsonDHTSerializer.java:
--------------------------------------------------------------------------------
1 | package kademlia.util.serializer;
2 |
3 | import com.google.gson.Gson;
4 | import com.google.gson.reflect.TypeToken;
5 | import com.google.gson.stream.JsonReader;
6 | import com.google.gson.stream.JsonWriter;
7 | import java.io.DataInputStream;
8 | import java.io.DataOutputStream;
9 | import java.io.IOException;
10 | import java.io.InputStreamReader;
11 | import java.io.OutputStreamWriter;
12 | import java.lang.reflect.Type;
13 | import java.util.List;
14 | import kademlia.dht.DHT;
15 | import kademlia.dht.KademliaDHT;
16 | import kademlia.dht.KademliaStorageEntryMetadata;
17 |
18 | /**
19 | * A KadSerializer that serializes DHT to JSON format
20 | * The generic serializer is not working for DHT
21 | *
22 | * Why a DHT specific serializer?
23 | * The DHT structure:
24 | * - DHT
25 | * -- StorageEntriesManager
26 | * --- Map>
27 | * ---- NodeId:KeyBytes
28 | * ---- List
29 | * ----- StorageEntry: Key, OwnerId, Type, Hash
30 | *
31 | * The above structure seems to be causing some problem for Gson, especially at the Map part.
32 | *
33 | * Solution
34 | * - Make the StorageEntriesManager transient
35 | * - Simply store all StorageEntry in the serialized object
36 | * - When reloading, re-add all StorageEntry to the DHT
37 | *
38 | * @author Joshua Kissoon
39 | *
40 | * @since 20140310
41 | */
42 | public class JsonDHTSerializer implements KadSerializer
43 | {
44 |
45 | private final Gson gson;
46 | private final Type storageEntriesCollectionType;
47 |
48 |
49 | {
50 | gson = new Gson();
51 |
52 | storageEntriesCollectionType = new TypeToken>()
53 | {
54 | }.getType();
55 | }
56 |
57 | @Override
58 | public void write(KademliaDHT data, DataOutputStream out) throws IOException
59 | {
60 | try (JsonWriter writer = new JsonWriter(new OutputStreamWriter(out)))
61 | {
62 | writer.beginArray();
63 |
64 | /* Write the basic DHT */
65 | gson.toJson(data, DHT.class, writer);
66 |
67 | /* Now Store the Entries */
68 | gson.toJson(data.getStorageEntries(), this.storageEntriesCollectionType, writer);
69 |
70 | writer.endArray();
71 | }
72 |
73 | }
74 |
75 | @Override
76 | public KademliaDHT read(DataInputStream in) throws IOException, ClassNotFoundException
77 | {
78 | try (DataInputStream din = new DataInputStream(in);
79 | JsonReader reader = new JsonReader(new InputStreamReader(in)))
80 | {
81 | reader.beginArray();
82 |
83 | /* Read the basic DHT */
84 | DHT dht = gson.fromJson(reader, DHT.class);
85 | dht.initialize();
86 |
87 | /* Now get the entries and add them back to the DHT */
88 | List entries = gson.fromJson(reader, this.storageEntriesCollectionType);
89 | dht.putStorageEntries(entries);
90 |
91 | reader.endArray();
92 | return dht;
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/kademlia/routing/Contact.java:
--------------------------------------------------------------------------------
1 | package kademlia.routing;
2 |
3 | import kademlia.node.Node;
4 |
5 | /**
6 | * Keeps information about contacts of the Node; Contacts are stored in the Buckets in the Routing Table.
7 | *
8 | * Contacts are used instead of nodes because more information is needed than just the node information.
9 | * - Information such as
10 | * -- Last seen time
11 | *
12 | * @author Joshua Kissoon
13 | * @since 20140425
14 | * @updated 20140426
15 | */
16 | public class Contact implements Comparable
17 | {
18 |
19 | private final Node n;
20 | private long lastSeen;
21 |
22 | /**
23 | * Stale as described by Kademlia paper page 64
24 | * When a contact fails to respond, if the replacement cache is empty and there is no replacement for the contact,
25 | * just mark it as stale.
26 | *
27 | * Now when a new contact is added, if the contact is stale, it is removed.
28 | */
29 | private int staleCount;
30 |
31 | /**
32 | * Create a contact object
33 | *
34 | * @param n The node associated with this contact
35 | */
36 | public Contact(Node n)
37 | {
38 | this.n = n;
39 | this.lastSeen = System.currentTimeMillis() / 1000L;
40 | }
41 |
42 | public Node getNode()
43 | {
44 | return this.n;
45 | }
46 |
47 | /**
48 | * When a Node sees a contact a gain, the Node will want to update that it's seen recently,
49 | * this method updates the last seen timestamp for this contact.
50 | */
51 | public void setSeenNow()
52 | {
53 | this.lastSeen = System.currentTimeMillis() / 1000L;
54 | }
55 |
56 | /**
57 | * When last was this contact seen?
58 | *
59 | * @return long The last time this contact was seen.
60 | */
61 | public long lastSeen()
62 | {
63 | return this.lastSeen;
64 | }
65 |
66 | @Override
67 | public boolean equals(Object c)
68 | {
69 | if (c instanceof Contact)
70 | {
71 | return ((Contact) c).getNode().equals(this.getNode());
72 | }
73 |
74 | return false;
75 | }
76 |
77 | /**
78 | * Increments the amount of times this count has failed to respond to a request.
79 | */
80 | public void incrementStaleCount()
81 | {
82 | staleCount++;
83 | }
84 |
85 | /**
86 | * @return Integer Stale count
87 | */
88 | public int staleCount()
89 | {
90 | return this.staleCount;
91 | }
92 |
93 | /**
94 | * Reset the stale count of the contact if it's recently seen
95 | */
96 | public void resetStaleCount()
97 | {
98 | this.staleCount = 0;
99 | }
100 |
101 | @Override
102 | public int compareTo(Contact o)
103 | {
104 | if (this.getNode().equals(o.getNode()))
105 | {
106 | return 0;
107 | }
108 |
109 | return (this.lastSeen() > o.lastSeen()) ? 1 : -1;
110 | }
111 |
112 | @Override
113 | public int hashCode()
114 | {
115 | return this.getNode().hashCode();
116 | }
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/src/kademlia/simulations/SaveStateTest.java:
--------------------------------------------------------------------------------
1 | package kademlia.simulations;
2 |
3 | import kademlia.JKademliaNode;
4 | import kademlia.node.KademliaId;
5 |
6 | /**
7 | * Testing the save and retrieve state operations
8 | *
9 | * @author Joshua Kissoon
10 | * @since 20140309
11 | */
12 | public class SaveStateTest
13 | {
14 |
15 | public SaveStateTest()
16 | {
17 | try
18 | {
19 | /* Setting up 2 Kad networks */
20 | JKademliaNode kad1 = new JKademliaNode("JoshuaK", new KademliaId("ASF45678947584567463"), 12049);
21 | JKademliaNode kad2 = new JKademliaNode("Crystal", new KademliaId("ASF45678947584567464"), 4585);
22 | JKademliaNode kad3 = new JKademliaNode("Shameer", new KademliaId("ASF45678947584567465"), 8104);
23 | JKademliaNode kad4 = new JKademliaNode("Lokesh", new KademliaId("ASF45678947584567466"), 8335);
24 | JKademliaNode kad5 = new JKademliaNode("Chandu", new KademliaId("ASF45678947584567467"), 13345);
25 |
26 | /* Connecting 2 to 1 */
27 | System.out.println("Connecting Nodes 1 & 2");
28 | kad2.bootstrap(kad1.getNode());
29 | System.out.println(kad1);
30 | System.out.println(kad2);
31 |
32 | kad3.bootstrap(kad2.getNode());
33 | System.out.println(kad1);
34 | System.out.println(kad2);
35 | System.out.println(kad3);
36 |
37 | kad4.bootstrap(kad2.getNode());
38 | System.out.println(kad1);
39 | System.out.println(kad2);
40 | System.out.println(kad3);
41 | System.out.println(kad4);
42 |
43 | kad5.bootstrap(kad4.getNode());
44 |
45 | System.out.println(kad1);
46 | System.out.println(kad2);
47 | System.out.println(kad3);
48 | System.out.println(kad4);
49 | System.out.println(kad5);
50 |
51 | synchronized (this)
52 | {
53 | System.out.println("\n\n\n\nSTORING CONTENT 1\n\n\n\n");
54 | DHTContentImpl c = new DHTContentImpl(kad2.getOwnerId(), "Some Data");
55 | System.out.println(c);
56 | kad2.put(c);
57 | }
58 |
59 | synchronized (this)
60 | {
61 | System.out.println("\n\n\n\nSTORING CONTENT 2\n\n\n\n");
62 | DHTContentImpl c2 = new DHTContentImpl(kad2.getOwnerId(), "Some other Data");
63 | System.out.println(c2);
64 | kad4.put(c2);
65 | }
66 |
67 | System.out.println(kad1);
68 | System.out.println(kad2);
69 | System.out.println(kad3);
70 | System.out.println(kad4);
71 | System.out.println(kad5);
72 |
73 | /* Shutting down kad1 and restarting it */
74 | System.out.println("\n\n\nShutting down Kad instance");
75 | System.out.println(kad2);
76 | kad1.shutdown(true);
77 |
78 | System.out.println("\n\n\nReloading Kad instance from file");
79 | JKademliaNode kadR2 = JKademliaNode.loadFromFile("JoshuaK");
80 | System.out.println(kadR2);
81 | }
82 | catch (IllegalStateException e)
83 | {
84 |
85 | }
86 | catch (Exception e)
87 | {
88 | e.printStackTrace();
89 | }
90 | }
91 |
92 | public static void main(String[] args)
93 | {
94 | new SaveStateTest();
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/kademlia/simulations/NodeConnectionTest.java:
--------------------------------------------------------------------------------
1 | package kademlia.simulations;
2 |
3 | import java.io.IOException;
4 | import kademlia.JKademliaNode;
5 | import kademlia.node.KademliaId;
6 |
7 | /**
8 | * Testing connecting 2 nodes to each other
9 | *
10 | * @author Joshua Kissoon
11 | * @created 20140219
12 | */
13 | public class NodeConnectionTest
14 | {
15 |
16 | public static void main(String[] args)
17 | {
18 | try
19 | {
20 | /* Setting up 2 Kad networks */
21 | JKademliaNode kad1 = new JKademliaNode("JoshuaK", new KademliaId("ASF45678947584567467"), 7574);
22 | System.out.println("Created Node Kad 1: " + kad1.getNode().getNodeId());
23 |
24 | JKademliaNode kad2 = new JKademliaNode("Crystal", new KademliaId("ASERTKJDHGVHERJHGFLK"), 7572);
25 | //NodeId diff12 = kad1.getNode().getNodeId().xor(kad2.getNode().getNodeId());
26 | System.out.println("Created Node Kad 2: " + kad2.getNode().getNodeId());
27 | // System.out.println(kad1.getNode().getNodeId() + " ^ " + kad2.getNode().getNodeId() + " = " + diff12);
28 | // System.out.println("Kad 1 - Kad 2 distance: " + diff12.getFirstSetBitIndex());
29 |
30 | /* Connecting 2 to 1 */
31 | System.out.println("Connecting Kad 1 and Kad 2");
32 | kad1.bootstrap(kad2.getNode());
33 |
34 | // System.out.println("Kad 1: ");
35 | // System.out.println(kad1.getNode().getRoutingTable());
36 | // System.out.println("Kad 2: ");
37 | // System.out.println(kad2.getNode().getRoutingTable());
38 |
39 | /* Creating a new node 3 and connecting it to 1, hoping it'll get onto 2 also */
40 | JKademliaNode kad3 = new JKademliaNode("Jessica", new KademliaId("ASERTKJDOLKMNBVFR45G"), 7783);
41 | System.out.println("\n\n\n\n\n\nCreated Node Kad 3: " + kad3.getNode().getNodeId());
42 |
43 | System.out.println("Connecting Kad 3 and Kad 2");
44 | kad3.bootstrap(kad2.getNode());
45 |
46 | // NodeId diff32 = kad3.getNode().getNodeId().xor(kad2.getNode().getNodeId());
47 | // NodeId diff31 = kad1.getNode().getNodeId().xor(kad3.getNode().getNodeId());
48 | // System.out.println("Kad 3 - Kad 1 distance: " + diff31.getFirstSetBitIndex());
49 | // System.out.println("Kad 3 - Kad 2 distance: " + diff32.getFirstSetBitIndex());
50 | JKademliaNode kad4 = new JKademliaNode("Sandy", new KademliaId("ASERTK85OLKMN85FR4SS"), 7789);
51 | System.out.println("\n\n\n\n\n\nCreated Node Kad 4: " + kad4.getNode().getNodeId());
52 |
53 | System.out.println("Connecting Kad 4 and Kad 2");
54 | kad4.bootstrap(kad2.getNode());
55 |
56 | System.out.println("\n\nKad 1: " + kad1.getNode().getNodeId() + " Routing Table: ");
57 | System.out.println(kad1.getRoutingTable());
58 | System.out.println("\n\nKad 2: " + kad2.getNode().getNodeId() + " Routing Table: ");
59 | System.out.println(kad2.getRoutingTable());
60 | System.out.println("\n\nKad 3: " + kad3.getNode().getNodeId() + " Routing Table: ");
61 | System.out.println(kad3.getRoutingTable());
62 | System.out.println("\n\nKad 4: " + kad4.getNode().getNodeId() + " Routing Table: ");
63 | System.out.println(kad4.getRoutingTable());
64 | }
65 | catch (IOException e)
66 | {
67 | e.printStackTrace();
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/kademlia/node/Node.java:
--------------------------------------------------------------------------------
1 | package kademlia.node;
2 |
3 | import java.io.DataInputStream;
4 | import java.io.DataOutputStream;
5 | import java.io.IOException;
6 | import java.io.Serializable;
7 | import java.net.InetAddress;
8 | import java.net.InetSocketAddress;
9 | import kademlia.message.Streamable;
10 |
11 | /**
12 | * A Node in the Kademlia network - Contains basic node network information.
13 | *
14 | * @author Joshua Kissoon
15 | * @since 20140202
16 | * @version 0.1
17 | */
18 | public class Node implements Streamable, Serializable
19 | {
20 |
21 | private KademliaId nodeId;
22 | private InetAddress inetAddress;
23 | private int port;
24 | private final String strRep;
25 |
26 | public Node(KademliaId nid, InetAddress ip, int port)
27 | {
28 | this.nodeId = nid;
29 | this.inetAddress = ip;
30 | this.port = port;
31 | this.strRep = this.nodeId.toString();
32 | }
33 |
34 | /**
35 | * Load the Node's data from a DataInput stream
36 | *
37 | * @param in
38 | *
39 | * @throws IOException
40 | */
41 | public Node(DataInputStream in) throws IOException
42 | {
43 | this.fromStream(in);
44 | this.strRep = this.nodeId.toString();
45 | }
46 |
47 | /**
48 | * Set the InetAddress of this node
49 | *
50 | * @param addr The new InetAddress of this node
51 | */
52 | public void setInetAddress(InetAddress addr)
53 | {
54 | this.inetAddress = addr;
55 | }
56 |
57 | /**
58 | * @return The NodeId object of this node
59 | */
60 | public KademliaId getNodeId()
61 | {
62 | return this.nodeId;
63 | }
64 |
65 | /**
66 | * Creates a SocketAddress for this node
67 | *
68 | * @return
69 | */
70 | public InetSocketAddress getSocketAddress()
71 | {
72 | return new InetSocketAddress(this.inetAddress, this.port);
73 | }
74 |
75 | @Override
76 | public void toStream(DataOutputStream out) throws IOException
77 | {
78 | /* Add the NodeId to the stream */
79 | this.nodeId.toStream(out);
80 |
81 | /* Add the Node's IP address to the stream */
82 | byte[] a = inetAddress.getAddress();
83 | if (a.length != 4)
84 | {
85 | throw new RuntimeException("Expected InetAddress of 4 bytes, got " + a.length);
86 | }
87 | out.write(a);
88 |
89 | /* Add the port to the stream */
90 | out.writeInt(port);
91 | }
92 |
93 | @Override
94 | public final void fromStream(DataInputStream in) throws IOException
95 | {
96 | /* Load the NodeId */
97 | this.nodeId = new KademliaId(in);
98 |
99 | /* Load the IP Address */
100 | byte[] ip = new byte[4];
101 | in.readFully(ip);
102 | this.inetAddress = InetAddress.getByAddress(ip);
103 |
104 | /* Read in the port */
105 | this.port = in.readInt();
106 | }
107 |
108 | @Override
109 | public boolean equals(Object o)
110 | {
111 | if (o instanceof Node)
112 | {
113 | Node n = (Node) o;
114 | if (n == this)
115 | {
116 | return true;
117 | }
118 | return this.getNodeId().equals(n.getNodeId());
119 | }
120 | return false;
121 | }
122 |
123 | @Override
124 | public int hashCode()
125 | {
126 | return this.getNodeId().hashCode();
127 | }
128 |
129 | @Override
130 | public String toString()
131 | {
132 | return this.getNodeId().toString();
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/kademlia/util/serializer/JsonRoutingTableSerializer.java:
--------------------------------------------------------------------------------
1 | package kademlia.util.serializer;
2 |
3 | import com.google.gson.Gson;
4 | import com.google.gson.reflect.TypeToken;
5 | import com.google.gson.stream.JsonReader;
6 | import com.google.gson.stream.JsonWriter;
7 | import java.io.DataInputStream;
8 | import java.io.DataOutputStream;
9 | import java.io.IOException;
10 | import java.io.InputStreamReader;
11 | import java.io.OutputStreamWriter;
12 | import kademlia.routing.JKademliaRoutingTable;
13 | import java.lang.reflect.Type;
14 | import java.util.List;
15 | import kademlia.KadConfiguration;
16 | import kademlia.routing.Contact;
17 | import kademlia.routing.KademliaRoutingTable;
18 |
19 | /**
20 | * A KadSerializer that serializes routing tables to JSON format
21 | The generic serializer is not working for routing tables
22 |
23 | Why a JKademliaRoutingTable specific serializer?
24 | The routing table structure:
25 | - JKademliaRoutingTable
26 | -- Buckets[]
27 | --- Map
28 | * ---- NodeId:KeyBytes
29 | * ---- Node: NodeId, InetAddress, Port
30 | *
31 | * The above structure seems to be causing some problem for Gson,
32 | * especially at the Map part.
33 | *
34 | * Solution
35 | - Make the Buckets[] transient
36 | - Simply store all Nodes in the serialized object
37 | - When reloading, re-add all nodes to the JKademliaRoutingTable
38 | *
39 | * @author Joshua Kissoon
40 | *
41 | * @since 20140310
42 | */
43 | public class JsonRoutingTableSerializer implements KadSerializer
44 | {
45 |
46 | private final Gson gson;
47 |
48 | Type contactCollectionType = new TypeToken>()
49 | {
50 | }.getType();
51 |
52 | private final KadConfiguration config;
53 |
54 |
55 | {
56 | gson = new Gson();
57 | }
58 |
59 | /**
60 | * Initialize the class
61 | *
62 | * @param config
63 | */
64 | public JsonRoutingTableSerializer(KadConfiguration config)
65 | {
66 | this.config = config;
67 | }
68 |
69 | @Override
70 | public void write(KademliaRoutingTable data, DataOutputStream out) throws IOException
71 | {
72 | try (JsonWriter writer = new JsonWriter(new OutputStreamWriter(out)))
73 | {
74 | writer.beginArray();
75 |
76 | /* Write the basic JKademliaRoutingTable */
77 | gson.toJson(data, JKademliaRoutingTable.class, writer);
78 |
79 | /* Now Store the Contacts */
80 | gson.toJson(data.getAllContacts(), contactCollectionType, writer);
81 |
82 | writer.endArray();
83 | }
84 | }
85 |
86 | @Override
87 | public KademliaRoutingTable read(DataInputStream in) throws IOException, ClassNotFoundException
88 | {
89 | try (DataInputStream din = new DataInputStream(in);
90 | JsonReader reader = new JsonReader(new InputStreamReader(in)))
91 | {
92 | reader.beginArray();
93 |
94 | /* Read the basic JKademliaRoutingTable */
95 | KademliaRoutingTable tbl = gson.fromJson(reader, KademliaRoutingTable.class);
96 | tbl.setConfiguration(config);
97 |
98 | /* Now get the Contacts and add them back to the JKademliaRoutingTable */
99 | List contacts = gson.fromJson(reader, contactCollectionType);
100 | tbl.initialize();
101 |
102 | for (Contact c : contacts)
103 | {
104 | tbl.insert(c);
105 | }
106 |
107 | reader.endArray();
108 | /* Read and return the Content*/
109 | return tbl;
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/kademlia/simulations/AutoRefreshOperation.java:
--------------------------------------------------------------------------------
1 | package kademlia.simulations;
2 |
3 | import java.util.Timer;
4 | import java.util.TimerTask;
5 | import kademlia.DefaultConfiguration;
6 | import kademlia.JKademliaNode;
7 | import kademlia.KadConfiguration;
8 | import kademlia.node.KademliaId;
9 |
10 | /**
11 | * Testing the Kademlia Auto Content and Node table refresh operations
12 | *
13 | * @author Joshua Kissoon
14 | * @since 20140309
15 | */
16 | public class AutoRefreshOperation implements Simulation
17 | {
18 |
19 | @Override
20 | public void runSimulation()
21 | {
22 | try
23 | {
24 | /* Setting up 2 Kad networks */
25 | final JKademliaNode kad1 = new JKademliaNode("JoshuaK", new KademliaId("ASF456789djem45674DH"), 12049);
26 | final JKademliaNode kad2 = new JKademliaNode("Crystal", new KademliaId("AJDHR678947584567464"), 4585);
27 | final JKademliaNode kad3 = new JKademliaNode("Shameer", new KademliaId("AS84k6789KRNS45KFJ8W"), 8104);
28 | final JKademliaNode kad4 = new JKademliaNode("Lokesh.", new KademliaId("ASF45678947A845674GG"), 8335);
29 | final JKademliaNode kad5 = new JKademliaNode("Chandu.", new KademliaId("AS84kUD894758456dyrj"), 13345);
30 |
31 | /* Connecting nodes */
32 | System.out.println("Connecting Nodes");
33 | kad2.bootstrap(kad1.getNode());
34 | kad3.bootstrap(kad2.getNode());
35 | kad4.bootstrap(kad2.getNode());
36 | kad5.bootstrap(kad4.getNode());
37 |
38 | DHTContentImpl c = new DHTContentImpl(new KademliaId("AS84k678947584567465"), kad1.getOwnerId());
39 | c.setData("Setting the data");
40 |
41 | System.out.println("\n Content ID: " + c.getKey());
42 | System.out.println(kad1.getNode() + " Distance from content: " + kad1.getNode().getNodeId().getDistance(c.getKey()));
43 | System.out.println(kad2.getNode() + " Distance from content: " + kad2.getNode().getNodeId().getDistance(c.getKey()));
44 | System.out.println(kad3.getNode() + " Distance from content: " + kad3.getNode().getNodeId().getDistance(c.getKey()));
45 | System.out.println(kad4.getNode() + " Distance from content: " + kad4.getNode().getNodeId().getDistance(c.getKey()));
46 | System.out.println(kad5.getNode() + " Distance from content: " + kad5.getNode().getNodeId().getDistance(c.getKey()));
47 | System.out.println("\nSTORING CONTENT 1 locally on " + kad1.getOwnerId() + "\n\n\n\n");
48 |
49 | kad1.putLocally(c);
50 |
51 | System.out.println(kad1);
52 | System.out.println(kad2);
53 | System.out.println(kad3);
54 | System.out.println(kad4);
55 | System.out.println(kad5);
56 |
57 | /* Print the node states every few minutes */
58 | KadConfiguration config = new DefaultConfiguration();
59 | Timer timer = new Timer(true);
60 | timer.schedule(
61 | new TimerTask()
62 | {
63 | @Override
64 | public void run()
65 | {
66 | System.out.println(kad1);
67 | System.out.println(kad2);
68 | System.out.println(kad3);
69 | System.out.println(kad4);
70 | System.out.println(kad5);
71 | }
72 | },
73 | // Delay // Interval
74 | config.restoreInterval(), config.restoreInterval()
75 | );
76 | }
77 |
78 | catch (Exception e)
79 | {
80 | e.printStackTrace();
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/kademlia/operation/ContentRefreshOperation.java:
--------------------------------------------------------------------------------
1 | package kademlia.operation;
2 |
3 | import java.io.IOException;
4 | import java.util.List;
5 | import kademlia.KadConfiguration;
6 | import kademlia.KadServer;
7 | import kademlia.KademliaNode;
8 | import kademlia.dht.KademliaDHT;
9 | import kademlia.dht.KademliaStorageEntryMetadata;
10 | import kademlia.dht.StorageEntryMetadata;
11 | import kademlia.exceptions.ContentNotFoundException;
12 | import kademlia.message.Message;
13 | import kademlia.message.StoreContentMessage;
14 | import kademlia.node.Node;
15 |
16 | /**
17 | * Refresh/Restore the data on this node by sending the data to the K-Closest nodes to the data
18 | *
19 | * @author Joshua Kissoon
20 | * @since 20140306
21 | */
22 | public class ContentRefreshOperation implements Operation
23 | {
24 |
25 | private final KadServer server;
26 | private final KademliaNode localNode;
27 | private final KademliaDHT dht;
28 | private final KadConfiguration config;
29 |
30 | public ContentRefreshOperation(KadServer server, KademliaNode localNode, KademliaDHT dht, KadConfiguration config)
31 | {
32 | this.server = server;
33 | this.localNode = localNode;
34 | this.dht = dht;
35 | this.config = config;
36 | }
37 |
38 | /**
39 | * For each content stored on this DHT, distribute it to the K closest nodes
40 | Also delete the content if this node is no longer one of the K closest nodes
41 |
42 | We assume that our JKademliaRoutingTable is updated, and we can get the K closest nodes from that table
43 | *
44 | * @throws java.io.IOException
45 | */
46 | @Override
47 | public void execute() throws IOException
48 | {
49 | /* Get a list of all storage entries for content */
50 | List entries = this.dht.getStorageEntries();
51 |
52 | /* If a content was last republished before this time, then we need to republish it */
53 | final long minRepublishTime = (System.currentTimeMillis() / 1000L) - this.config.restoreInterval();
54 |
55 | /* For each storage entry, distribute it */
56 | for (KademliaStorageEntryMetadata e : entries)
57 | {
58 | /* Check last update time of this entry and only distribute it if it has been last updated > 1 hour ago */
59 | if (e.lastRepublished() > minRepublishTime)
60 | {
61 | continue;
62 | }
63 |
64 | /* Set that this content is now republished */
65 | e.updateLastRepublished();
66 |
67 | /* Get the K closest nodes to this entries */
68 | List closestNodes = this.localNode.getRoutingTable().findClosest(e.getKey(), this.config.k());
69 |
70 | /* Create the message */
71 | Message msg = new StoreContentMessage(this.localNode.getNode(), dht.get(e));
72 |
73 | /*Store the message on all of the K-Nodes*/
74 | for (Node n : closestNodes)
75 | {
76 | /*We don't need to again store the content locally, it's already here*/
77 | if (!n.equals(this.localNode.getNode()))
78 | {
79 | /* Send a contentstore operation to the K-Closest nodes */
80 | this.server.sendMessage(n, msg, null);
81 | }
82 | }
83 |
84 | /* Delete any content on this node that this node is not one of the K-Closest nodes to */
85 | try
86 | {
87 | if (!closestNodes.contains(this.localNode.getNode()))
88 | {
89 | this.dht.remove(e);
90 | }
91 | }
92 | catch (ContentNotFoundException cnfe)
93 | {
94 | /* It would be weird if the content is not found here */
95 | System.err.println("ContentRefreshOperation: Removing content from local node, content not found... Message: " + cnfe.getMessage());
96 | }
97 | }
98 |
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/kademlia/dht/KademliaDHT.java:
--------------------------------------------------------------------------------
1 | package kademlia.dht;
2 |
3 | import java.io.FileNotFoundException;
4 | import java.io.IOException;
5 | import java.util.List;
6 | import java.util.NoSuchElementException;
7 | import kademlia.KadConfiguration;
8 | import kademlia.exceptions.ContentNotFoundException;
9 | import kademlia.node.KademliaId;
10 | import kademlia.util.serializer.KadSerializer;
11 |
12 | /**
13 | * The main Distributed Hash Table interface that manages the entire DHT
14 | *
15 | * @author Joshua Kissoon
16 | * @since 20140523
17 | */
18 | public interface KademliaDHT
19 | {
20 |
21 | /**
22 | * Initialize this DHT to it's default state
23 | */
24 | public void initialize();
25 |
26 | /**
27 | * Set a new configuration. Mainly used when we restore the DHT state from a file
28 | *
29 | * @param con The new configuration file
30 | */
31 | public void setConfiguration(KadConfiguration con);
32 |
33 | /**
34 | * Creates a new Serializer or returns an existing serializer
35 | *
36 | * @return The new ContentSerializer
37 | */
38 | public KadSerializer getSerializer();
39 |
40 | /**
41 | * Handle storing content locally
42 | *
43 | * @param content The DHT content to store
44 | *
45 | * @return boolean true if we stored the content, false if the content already exists and is up to date
46 | *
47 | * @throws java.io.IOException
48 | */
49 | public boolean store(JKademliaStorageEntry content) throws IOException;
50 |
51 | public boolean store(KadContent content) throws IOException;
52 |
53 | /**
54 | * Retrieves a Content from local storage
55 | *
56 | * @param key The Key of the content to retrieve
57 | * @param hashCode The hash code of the content to retrieve
58 | *
59 | * @return A KadContent object
60 | *
61 | * @throws java.io.FileNotFoundException
62 | * @throws java.lang.ClassNotFoundException
63 | */
64 | public JKademliaStorageEntry retrieve(KademliaId key, int hashCode) throws FileNotFoundException, IOException, ClassNotFoundException;
65 |
66 | /**
67 | * Check if any content for the given criteria exists in this DHT
68 | *
69 | * @param param The content search criteria
70 | *
71 | * @return boolean Whether any content exist that satisfy the criteria
72 | */
73 | public boolean contains(GetParameter param);
74 |
75 | /**
76 | * Retrieve and create a KadContent object given the StorageEntry object
77 | *
78 | * @param entry The StorageEntry used to retrieve this content
79 | *
80 | * @return KadContent The content object
81 | *
82 | * @throws java.io.IOException
83 | */
84 | public JKademliaStorageEntry get(KademliaStorageEntryMetadata entry) throws IOException, NoSuchElementException;
85 |
86 | /**
87 | * Get the StorageEntry for the content if any exist.
88 | *
89 | * @param param The parameters used to filter the content needed
90 | *
91 | * @return KadContent A KadContent found on the DHT satisfying the given criteria
92 | *
93 | * @throws java.io.IOException
94 | */
95 | public JKademliaStorageEntry get(GetParameter param) throws NoSuchElementException, IOException;
96 |
97 | /**
98 | * Delete a content from local storage
99 | *
100 | * @param content The Content to Remove
101 | *
102 | *
103 | * @throws kademlia.exceptions.ContentNotFoundException
104 | */
105 | public void remove(KadContent content) throws ContentNotFoundException;
106 |
107 | public void remove(KademliaStorageEntryMetadata entry) throws ContentNotFoundException;
108 |
109 | /**
110 | * @return A List of all StorageEntries for this node
111 | */
112 | public List getStorageEntries();
113 |
114 | /**
115 | * Used to add a list of storage entries for existing content to the DHT.
116 | * Mainly used when retrieving StorageEntries from a saved state file.
117 | *
118 | * @param ientries The entries to add
119 | */
120 | public void putStorageEntries(List ientries);
121 |
122 | }
123 |
--------------------------------------------------------------------------------
/src/kademlia/dht/StorageEntryMetadata.java:
--------------------------------------------------------------------------------
1 | package kademlia.dht;
2 |
3 | import java.util.Objects;
4 | import kademlia.node.KademliaId;
5 |
6 | /**
7 | * Keeps track of data for a Content stored in the DHT
8 | * Used by the StorageEntryManager class
9 | *
10 | * @author Joshua Kissoon
11 | * @since 20140226
12 | */
13 | public class StorageEntryMetadata implements KademliaStorageEntryMetadata
14 | {
15 |
16 | private final KademliaId key;
17 | private final String ownerId;
18 | private final String type;
19 | private final int contentHash;
20 | private final long updatedTs;
21 |
22 | /* This value is the last time this content was last updated from the network */
23 | private long lastRepublished;
24 |
25 | public StorageEntryMetadata(KadContent content)
26 | {
27 | this.key = content.getKey();
28 | this.ownerId = content.getOwnerId();
29 | this.type = content.getType();
30 | this.contentHash = content.hashCode();
31 | this.updatedTs = content.getLastUpdatedTimestamp();
32 |
33 | this.lastRepublished = System.currentTimeMillis() / 1000L;
34 | }
35 |
36 | @Override
37 | public KademliaId getKey()
38 | {
39 | return this.key;
40 | }
41 |
42 | @Override
43 | public String getOwnerId()
44 | {
45 | return this.ownerId;
46 | }
47 |
48 | @Override
49 | public String getType()
50 | {
51 | return this.type;
52 | }
53 |
54 | @Override
55 | public int getContentHash()
56 | {
57 | return this.contentHash;
58 | }
59 |
60 | @Override
61 | public long getLastUpdatedTimestamp()
62 | {
63 | return this.updatedTs;
64 | }
65 |
66 | /**
67 | * When a node is looking for content, he sends the search criteria in a GetParameter object
68 | * Here we take this GetParameter object and check if this StorageEntry satisfies the given parameters
69 | *
70 | * @param params
71 | *
72 | * @return boolean Whether this content satisfies the parameters
73 | */
74 | @Override
75 | public boolean satisfiesParameters(GetParameter params)
76 | {
77 | /* Check that owner id matches */
78 | if ((params.getOwnerId() != null) && (!params.getOwnerId().equals(this.ownerId)))
79 | {
80 | return false;
81 | }
82 |
83 | /* Check that type matches */
84 | if ((params.getType() != null) && (!params.getType().equals(this.type)))
85 | {
86 | return false;
87 | }
88 |
89 | /* Check that key matches */
90 | if ((params.getKey() != null) && (!params.getKey().equals(this.key)))
91 | {
92 | return false;
93 | }
94 |
95 | return true;
96 | }
97 |
98 | @Override
99 | public long lastRepublished()
100 | {
101 | return this.lastRepublished;
102 | }
103 |
104 | /**
105 | * Whenever we republish a content or get this content from the network, we update the last republished time
106 | */
107 | @Override
108 | public void updateLastRepublished()
109 | {
110 | this.lastRepublished = System.currentTimeMillis() / 1000L;
111 | }
112 |
113 | @Override
114 | public boolean equals(Object o)
115 | {
116 | if (o instanceof KademliaStorageEntryMetadata)
117 | {
118 | return this.hashCode() == o.hashCode();
119 | }
120 |
121 | return false;
122 | }
123 |
124 | @Override
125 | public int hashCode()
126 | {
127 | int hash = 3;
128 | hash = 23 * hash + Objects.hashCode(this.key);
129 | hash = 23 * hash + Objects.hashCode(this.ownerId);
130 | hash = 23 * hash + Objects.hashCode(this.type);
131 | return hash;
132 | }
133 |
134 | @Override
135 | public String toString()
136 | {
137 | StringBuilder sb = new StringBuilder("[StorageEntry: ");
138 |
139 | sb.append("{Key: ");
140 | sb.append(this.key);
141 | sb.append("} ");
142 | sb.append("{Owner: ");
143 | sb.append(this.ownerId);
144 | sb.append("} ");
145 | sb.append("{Type: ");
146 | sb.append(this.type);
147 | sb.append("} ");
148 | sb.append("]");
149 |
150 | return sb.toString();
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Kademlia
2 | ========
3 |
4 | This is an implementation of the Kademlia (http://en.wikipedia.org/wiki/Kademlia) routing protocol and DHT.
5 |
6 | I wrote an article [An Introduction to Kademlia DHT & How It Works](http://gleamly.com/article/introduction-kademlia-dht-how-it-works). Have a read.
7 |
8 | Kademlia original Publication: http://link.springer.com/chapter/10.1007/3-540-45748-8_5
9 |
10 | Note: This repository is a Netbeans project which you can simply download and import.
11 |
12 | Usage
13 | -----
14 | The Implementation is meant to be self contained and very easy to setup and use. There are several tests (https://github.com/JoshuaKissoon/Kademlia/tree/master/src/kademlia/tests) which demonstrates the usage of the protocol and DHT.
15 |
16 |
17 | **Configuration**
18 |
19 | There is a configuration file available in the kademlia.core package which have all settings used throughout the protocol, all of these settings are described in depth in the Configuration file.
20 |
21 |
22 | **Creating a Kad Instance**
23 |
24 | All of Kademlia's sub-components (DHT, Node, Routing Table, Server, etc) are wrapped within the Kademlia object to simplify the usage of the protocol. To create an instance, simply call:
25 |
26 | ```Java
27 | Kademlia kad1 = new Kademlia("OwnerName1", new NodeId("ASF45678947584567463"), 12049);
28 | Kademlia kad2 = new Kademlia("OwnerName2", new NodeId(), 12057); // Random NodeId will be generated
29 | ```
30 | Param 1: The Name of the owner of this instance, can be any name.
31 | Param 2: A NodeId for this node
32 | Param 3: The port on which this Kademlia instance will run on.
33 |
34 | After this initialization phase, the 2 Kad instances will basically be 2 separate networks. Lets connect them so they'll be in the same network.
35 |
36 |
37 | **Connecting Nodes**
38 |
39 | Test: https://github.com/JoshuaKissoon/Kademlia/blob/master/src/kademlia/tests/NodeConnectionTest.java
40 | ```Java
41 | kad2.bootstrap(kad1.getNode()); // Bootstrap kad2 by using kad1 as the main network node
42 | ```
43 |
44 |
45 | **Storing Content**
46 |
47 | Test: https://github.com/JoshuaKissoon/Kademlia/blob/master/src/kademlia/tests/ContentSendingTest.java
48 | ```Java
49 | /* Working example at: https://github.com/JoshuaKissoon/Kademlia/blob/master/src/kademlia/tests/ContentSendingTest.java */
50 | DHTContentImpl c = new DHTContentImpl(kad2.getOwnerId(), "Some Data"); // Create a content
51 | kad2.put(c); // Put the content on the network
52 |
53 | ```
54 |
55 |
56 | **Retrieving Content**
57 |
58 | Test: https://github.com/JoshuaKissoon/Kademlia/blob/master/src/kademlia/tests/ContentSendingTest.java
59 | ```Java
60 | /* Create a GetParameter object with the parameters of the content to retrieve */
61 | GetParameter gp = new GetParameter(c.getKey()); // Lets look for content by key
62 | gp.setType(DHTContentImpl.TYPE); // We also only want content of this type
63 | gp.setOwnerId(c.getOwnerId()); // And content from this owner
64 |
65 | /* Now we call get specifying the GetParameters and the Number of results we want */
66 | List conte = kad2.get(gp, 1);
67 | ```
68 |
69 |
70 | **Saving and Retrieving a Node State**
71 |
72 | Test: https://github.com/JoshuaKissoon/Kademlia/blob/master/src/kademlia/tests/SaveStateTest.java
73 |
74 | You may want to save the Node state when your application is shut down and Retrieve the Node state on startup to remove the need of rebuilding the Node State (Routing Table, DHT Content Entries, etc). Lets look at how we do this.
75 |
76 | ```Java
77 | /**
78 | * Shutting down the Kad instance.
79 | * Calling .shutdown() ill automatically store the node state in the location specified in the Configuration file
80 | */
81 | kad1.shutdown();
82 |
83 | /**
84 | * Retrieving the Node state
85 | * This is done by simply building the Kademlia instance by calling .loadFromFile()
86 | * and passing in the instance Owner name as a parameter
87 | */
88 | Kademlia kad1Reloaded = Kademlia.loadFromFile("OwnerName1");
89 | ```
90 |
91 | For more information on using Kademlia, check the tests at: https://github.com/JoshuaKissoon/Kademlia/tree/master/src/kademlia/tests
92 |
93 |
94 | Usage in a Real Project
95 | -----------------------
96 | I am currently using this implementation of Kademlia in developing a Distributed Online Social Network Architecture, you can look at that project at https://github.com/JoshuaKissoon/DOSNA for more ideas on using Kademlia.
97 |
--------------------------------------------------------------------------------
/src/kademlia/simulations/RoutingTableStateTesting.java:
--------------------------------------------------------------------------------
1 | package kademlia.simulations;
2 |
3 | import java.io.IOException;
4 | import java.util.Scanner;
5 | import kademlia.JKademliaNode;
6 | import kademlia.dht.KadContent;
7 | import kademlia.node.KademliaId;
8 |
9 | /**
10 | * Testing how the routing table works and it's state after different operations
11 | *
12 | * @author Joshua Kissoon
13 | * @since 20140426
14 | */
15 | public class RoutingTableStateTesting
16 | {
17 |
18 | JKademliaNode[] kads;
19 |
20 | public int numKads = 10;
21 |
22 | public RoutingTableStateTesting()
23 | {
24 | try
25 | {
26 | /* Setting up Kad networks */
27 | kads = new JKademliaNode[numKads];
28 |
29 | kads[0] = new JKademliaNode("user0", new KademliaId("HRF456789SD584567460"), 1334);
30 | kads[1] = new JKademliaNode("user1", new KademliaId("ASF456789475DS567461"), 1209);
31 | kads[2] = new JKademliaNode("user2", new KademliaId("AFG45678947584567462"), 4585);
32 | kads[3] = new JKademliaNode("user3", new KademliaId("FSF45J38947584567463"), 8104);
33 | kads[4] = new JKademliaNode("user4", new KademliaId("ASF45678947584567464"), 8335);
34 | kads[5] = new JKademliaNode("user5", new KademliaId("GHF4567894DR84567465"), 13345);
35 | kads[6] = new JKademliaNode("user6", new KademliaId("ASF45678947584567466"), 12049);
36 | kads[7] = new JKademliaNode("user7", new KademliaId("AE345678947584567467"), 14585);
37 | kads[8] = new JKademliaNode("user8", new KademliaId("ASAA5678947584567468"), 18104);
38 | kads[9] = new JKademliaNode("user9", new KademliaId("ASF456789475845674U9"), 18335);
39 |
40 | for (int i = 1; i < numKads; i++)
41 | {
42 | kads[i].bootstrap(kads[0].getNode());
43 | }
44 |
45 | /* Lets shut down a node and then try putting a content on the network. We'll then see how the un-responsive contacts work */
46 | }
47 | catch (Exception e)
48 | {
49 | e.printStackTrace();
50 | }
51 | }
52 |
53 | public KadContent putContent(String content, JKademliaNode owner)
54 | {
55 | DHTContentImpl c = null;
56 | try
57 | {
58 | c = new DHTContentImpl(owner.getOwnerId(), "Some Data");
59 | owner.put(c);
60 | return c;
61 | }
62 | catch (IOException e)
63 | {
64 | System.err.println("Error whiles putting content " + content + " from owner: " + owner.getOwnerId());
65 | }
66 |
67 | return c;
68 | }
69 |
70 | public void shutdownKad(JKademliaNode kad)
71 | {
72 | try
73 | {
74 | kad.shutdown(false);
75 | }
76 | catch (IOException ex)
77 | {
78 | System.err.println("Error whiles shutting down node with owner: " + kad.getOwnerId());
79 | }
80 | }
81 |
82 | public void printRoutingTable(int kadId)
83 | {
84 | System.out.println(kads[kadId].getRoutingTable());
85 | }
86 |
87 | public void printRoutingTables()
88 | {
89 | for (int i = 0; i < numKads; i++)
90 | {
91 | this.printRoutingTable(i);
92 | }
93 | }
94 |
95 | public void printStorage(int kadId)
96 | {
97 | System.out.println(kads[kadId].getDHT());
98 | }
99 |
100 | public void printStorage()
101 | {
102 | for (int i = 0; i < numKads; i++)
103 | {
104 | this.printStorage(i);
105 | }
106 | }
107 |
108 | public static void main(String[] args)
109 | {
110 |
111 | RoutingTableStateTesting rtss = new RoutingTableStateTesting();
112 |
113 | try
114 | {
115 | rtss.printRoutingTables();
116 |
117 | /* Lets shut down a node to test the node removal operation */
118 | rtss.shutdownKad(rtss.kads[3]);
119 |
120 | rtss.putContent("Content owned by kad0", rtss.kads[0]);
121 | rtss.printStorage();
122 |
123 | Thread.sleep(1000);
124 |
125 | /* kad3 should be removed from their routing tables by now. */
126 | rtss.printRoutingTables();
127 | }
128 | catch (InterruptedException ex)
129 | {
130 |
131 | }
132 |
133 | Scanner sc = new Scanner(System.in);
134 | while (true)
135 | {
136 | System.out.println("\n\n ************************* Options **************************** \n");
137 | System.out.println("1 i - Print routing table of node i");
138 | int val1 = sc.nextInt();
139 | int val2 = sc.nextInt();
140 |
141 | switch (val1)
142 | {
143 | case 1:
144 | rtss.printRoutingTable(val2);
145 | break;
146 | }
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/src/kademlia/KademliaNode.java:
--------------------------------------------------------------------------------
1 | package kademlia;
2 |
3 | import java.io.IOException;
4 | import java.util.NoSuchElementException;
5 | import kademlia.dht.GetParameter;
6 | import kademlia.dht.JKademliaStorageEntry;
7 | import kademlia.dht.KadContent;
8 | import kademlia.dht.KademliaDHT;
9 | import kademlia.dht.KademliaStorageEntry;
10 | import kademlia.exceptions.ContentNotFoundException;
11 | import kademlia.exceptions.RoutingException;
12 | import kademlia.node.Node;
13 | import kademlia.routing.KademliaRoutingTable;
14 |
15 | /**
16 | * The main Kademlia Node on the network, this node manages everything for this local system.
17 | *
18 | * @author Joshua Kissoon
19 | * @since 20140523
20 | *
21 | */
22 | public interface KademliaNode
23 | {
24 |
25 | /**
26 | * Schedule the recurring refresh operation
27 | */
28 | public void startRefreshOperation();
29 |
30 | /**
31 | * Stop the recurring refresh operation
32 | */
33 | public void stopRefreshOperation();
34 |
35 | /**
36 | * @return Node The local node for this system
37 | */
38 | public Node getNode();
39 |
40 | /**
41 | * @return The KadServer used to send/receive messages
42 | */
43 | public KadServer getServer();
44 |
45 | /**
46 | * @return The DHT for this kad instance
47 | */
48 | public KademliaDHT getDHT();
49 |
50 | /**
51 | * @return The current KadConfiguration object being used
52 | */
53 | public KadConfiguration getCurrentConfiguration();
54 |
55 | /**
56 | * Connect to an existing peer-to-peer network.
57 | *
58 | * @param n The known node in the peer-to-peer network
59 | *
60 | * @throws RoutingException If the bootstrap node could not be contacted
61 | * @throws IOException If a network error occurred
62 | * @throws IllegalStateException If this object is closed
63 | * */
64 | public void bootstrap(Node n) throws IOException, RoutingException;
65 |
66 | /**
67 | * Stores the specified value under the given key
68 | * This value is stored on K nodes on the network, or all nodes if there are > K total nodes in the network
69 | *
70 | * @param content The content to put onto the DHT
71 | *
72 | * @return Integer How many nodes the content was stored on
73 | *
74 | * @throws java.io.IOException
75 | *
76 | */
77 | public int put(KadContent content) throws IOException;
78 |
79 | /**
80 | * Stores the specified value under the given key
81 | * This value is stored on K nodes on the network, or all nodes if there are > K total nodes in the network
82 | *
83 | * @param entry The StorageEntry with the content to put onto the DHT
84 | *
85 | * @return Integer How many nodes the content was stored on
86 | *
87 | * @throws java.io.IOException
88 | *
89 | */
90 | public int put(JKademliaStorageEntry entry) throws IOException;
91 |
92 | /**
93 | * Store a content on the local node's DHT
94 | *
95 | * @param content The content to put on the DHT
96 | *
97 | * @throws java.io.IOException
98 | */
99 | public void putLocally(KadContent content) throws IOException;
100 |
101 | /**
102 | * Get some content stored on the DHT
103 | *
104 | * @param param The parameters used to search for the content
105 | *
106 | * @return DHTContent The content
107 | *
108 | * @throws java.io.IOException
109 | * @throws kademlia.exceptions.ContentNotFoundException
110 | */
111 | public JKademliaStorageEntry get(GetParameter param) throws NoSuchElementException, IOException, ContentNotFoundException;
112 |
113 | /**
114 | * Allow the user of the System to call refresh even out of the normal Kad refresh timing
115 | *
116 | * @throws java.io.IOException
117 | */
118 | public void refresh() throws IOException;
119 |
120 | /**
121 | * @return String The ID of the owner of this local network
122 | */
123 | public String getOwnerId();
124 |
125 | /**
126 | * @return Integer The port on which this kad instance is running
127 | */
128 | public int getPort();
129 |
130 | /**
131 | * Here we handle properly shutting down the Kademlia instance
132 | *
133 | * @param saveState Whether to save the application state or not
134 | *
135 | * @throws java.io.FileNotFoundException
136 | */
137 | public void shutdown(final boolean saveState) throws IOException;
138 |
139 | /**
140 | * Saves the node state to a text file
141 | *
142 | * @throws java.io.FileNotFoundException
143 | */
144 | public void saveKadState() throws IOException;
145 |
146 | /**
147 | * @return The routing table for this node.
148 | */
149 | public KademliaRoutingTable getRoutingTable();
150 |
151 | /**
152 | * @return The statistician that manages all statistics
153 | */
154 | public KadStatistician getStatistician();
155 | }
156 |
--------------------------------------------------------------------------------
/src/kademlia/operation/ConnectOperation.java:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Joshua Kissoon
3 | * @created 20140218
4 | * @desc Operation that handles connecting to an existing Kademlia network using a bootstrap node
5 | */
6 | package kademlia.operation;
7 |
8 | import kademlia.message.Receiver;
9 | import java.io.IOException;
10 | import kademlia.JKademliaNode;
11 | import kademlia.KadConfiguration;
12 | import kademlia.KadServer;
13 | import kademlia.KademliaNode;
14 | import kademlia.exceptions.RoutingException;
15 | import kademlia.message.AcknowledgeMessage;
16 | import kademlia.message.ConnectMessage;
17 | import kademlia.message.Message;
18 | import kademlia.node.Node;
19 |
20 | public class ConnectOperation implements Operation, Receiver
21 | {
22 |
23 | public static final int MAX_CONNECT_ATTEMPTS = 5; // Try 5 times to connect to a node
24 |
25 | private final KadServer server;
26 | private final KademliaNode localNode;
27 | private final Node bootstrapNode;
28 | private final KadConfiguration config;
29 |
30 | private boolean error;
31 | private int attempts;
32 |
33 | /**
34 | * @param server The message server used to send/receive messages
35 | * @param local The local node
36 | * @param bootstrap Node to use to bootstrap the local node onto the network
37 | * @param config
38 | */
39 | public ConnectOperation(KadServer server, KademliaNode local, Node bootstrap, KadConfiguration config)
40 | {
41 | this.server = server;
42 | this.localNode = local;
43 | this.bootstrapNode = bootstrap;
44 | this.config = config;
45 | }
46 |
47 | @Override
48 | public synchronized void execute() throws IOException
49 | {
50 | try
51 | {
52 | /* Contact the bootstrap node */
53 | this.error = true;
54 | this.attempts = 0;
55 | Message m = new ConnectMessage(this.localNode.getNode());
56 |
57 | /* Send a connect message to the bootstrap node */
58 | server.sendMessage(this.bootstrapNode, m, this);
59 |
60 | /* If we haven't finished as yet, wait for a maximum of config.operationTimeout() time */
61 | int totalTimeWaited = 0;
62 | int timeInterval = 50; // We re-check every 300 milliseconds
63 | while (totalTimeWaited < this.config.operationTimeout())
64 | {
65 | if (error)
66 | {
67 | wait(timeInterval);
68 | totalTimeWaited += timeInterval;
69 | }
70 | else
71 | {
72 | break;
73 | }
74 | }
75 | if (error)
76 | {
77 | /* If we still haven't received any responses by then, do a routing timeout */
78 | throw new RoutingException("ConnectOperation: Bootstrap node did not respond: " + bootstrapNode);
79 | }
80 |
81 | /* Perform lookup for our own ID to get nodes close to us */
82 | Operation lookup = new NodeLookupOperation(this.server, this.localNode, this.localNode.getNode().getNodeId(), this.config);
83 | lookup.execute();
84 |
85 | /**
86 | * Refresh buckets to get a good routing table
87 | * After the above lookup operation, K nodes will be in our routing table,
88 | * Now we try to populate all of our buckets.
89 | */
90 | new BucketRefreshOperation(this.server, this.localNode, this.config).execute();
91 | }
92 | catch (InterruptedException e)
93 | {
94 | System.err.println("Connect operation was interrupted. ");
95 | }
96 | }
97 |
98 | /**
99 | * Receives an AcknowledgeMessage from the bootstrap node.
100 | *
101 | * @param comm
102 | */
103 | @Override
104 | public synchronized void receive(Message incoming, int comm)
105 | {
106 | /* The incoming message will be an acknowledgement message */
107 | AcknowledgeMessage msg = (AcknowledgeMessage) incoming;
108 |
109 | /* The bootstrap node has responded, insert it into our space */
110 | this.localNode.getRoutingTable().insert(this.bootstrapNode);
111 |
112 | /* We got a response, so the error is false */
113 | error = false;
114 |
115 | /* Wake up any waiting thread */
116 | notify();
117 | }
118 |
119 | /**
120 | * Resends a ConnectMessage to the boot strap node a maximum of MAX_ATTEMPTS
121 | * times.
122 | *
123 | * @param comm
124 | *
125 | * @throws java.io.IOException
126 | */
127 | @Override
128 | public synchronized void timeout(int comm) throws IOException
129 | {
130 | if (++this.attempts < MAX_CONNECT_ATTEMPTS)
131 | {
132 | this.server.sendMessage(this.bootstrapNode, new ConnectMessage(this.localNode.getNode()), this);
133 | }
134 | else
135 | {
136 | /* We just exit, so notify all other threads that are possibly waiting */
137 | notify();
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/kademlia/Statistician.java:
--------------------------------------------------------------------------------
1 | package kademlia;
2 |
3 | import java.text.DecimalFormat;
4 |
5 | /**
6 | * Class that keeps statistics for this Kademlia instance.
7 | *
8 | * These statistics are temporary and will be lost when Kad is shut down.
9 | *
10 | * @author Joshua Kissoon
11 | * @since 20140505
12 | */
13 | public class Statistician implements KadStatistician
14 | {
15 |
16 | /* How much data was sent and received by the server over the network */
17 | private long totalDataSent, totalDataReceived;
18 | private long numDataSent, numDataReceived;
19 |
20 | /* Bootstrap timings */
21 | private long bootstrapTime;
22 |
23 | /* Content lookup operation timing & route length */
24 | private int numContentLookups, numFailedContentLookups;
25 | private long totalContentLookupTime;
26 | private long totalRouteLength;
27 |
28 |
29 | {
30 | this.totalDataSent = 0;
31 | this.totalDataReceived = 0;
32 | this.bootstrapTime = 0;
33 | this.numContentLookups = 0;
34 | this.totalContentLookupTime = 0;
35 | this.totalRouteLength = 0;
36 | }
37 |
38 | @Override
39 | public void sentData(long size)
40 | {
41 | this.totalDataSent += size;
42 | this.numDataSent++;
43 | }
44 |
45 | @Override
46 | public long getTotalDataSent()
47 | {
48 | if (this.totalDataSent == 0)
49 | {
50 | return 0L;
51 | }
52 |
53 | return this.totalDataSent / 1000L;
54 | }
55 |
56 | @Override
57 | public void receivedData(long size)
58 | {
59 | this.totalDataReceived += size;
60 | this.numDataReceived++;
61 | }
62 |
63 | @Override
64 | public long getTotalDataReceived()
65 | {
66 | if (this.totalDataReceived == 0)
67 | {
68 | return 0L;
69 | }
70 | return this.totalDataReceived / 1000L;
71 | }
72 |
73 | @Override
74 | public void setBootstrapTime(long time)
75 | {
76 | this.bootstrapTime = time;
77 | }
78 |
79 | @Override
80 | public long getBootstrapTime()
81 | {
82 | return this.bootstrapTime / 1000000L;
83 | }
84 |
85 | @Override
86 | public void addContentLookup(long time, int routeLength, boolean isSuccessful)
87 | {
88 | if (isSuccessful)
89 | {
90 | this.numContentLookups++;
91 | this.totalContentLookupTime += time;
92 | this.totalRouteLength += routeLength;
93 | }
94 | else
95 | {
96 | this.numFailedContentLookups++;
97 | }
98 | }
99 |
100 | @Override
101 | public int numContentLookups()
102 | {
103 | return this.numContentLookups;
104 | }
105 |
106 | @Override
107 | public int numFailedContentLookups()
108 | {
109 | return this.numFailedContentLookups;
110 | }
111 |
112 | @Override
113 | public long totalContentLookupTime()
114 | {
115 | return this.totalContentLookupTime;
116 | }
117 |
118 | @Override
119 | public double averageContentLookupTime()
120 | {
121 | if (this.numContentLookups == 0)
122 | {
123 | return 0D;
124 | }
125 |
126 | double avg = (double) ((double) this.totalContentLookupTime / (double) this.numContentLookups) / 1000000D;
127 | DecimalFormat df = new DecimalFormat("#.00");
128 | return new Double(df.format(avg));
129 | }
130 |
131 | @Override
132 | public double averageContentLookupRouteLength()
133 | {
134 | if (this.numContentLookups == 0)
135 | {
136 | return 0D;
137 | }
138 | double avg = (double) ((double) this.totalRouteLength / (double) this.numContentLookups);
139 | DecimalFormat df = new DecimalFormat("#.00");
140 | return new Double(df.format(avg));
141 | }
142 |
143 | @Override
144 | public String toString()
145 | {
146 | StringBuilder sb = new StringBuilder("Statistician: [");
147 |
148 | sb.append("Bootstrap Time: ");
149 | sb.append(this.getBootstrapTime());
150 | sb.append("; ");
151 |
152 | sb.append("Data Sent: ");
153 | sb.append("(");
154 | sb.append(this.numDataSent);
155 | sb.append(") ");
156 | sb.append(this.getTotalDataSent());
157 | sb.append(" bytes; ");
158 |
159 | sb.append("Data Received: ");
160 | sb.append("(");
161 | sb.append(this.numDataReceived);
162 | sb.append(") ");
163 | sb.append(this.getTotalDataReceived());
164 | sb.append(" bytes; ");
165 |
166 | sb.append("Num Content Lookups: ");
167 | sb.append(this.numContentLookups());
168 | sb.append("; ");
169 |
170 | sb.append("Avg Content Lookup Time: ");
171 | sb.append(this.averageContentLookupTime());
172 | sb.append("; ");
173 |
174 | sb.append("Avg Content Lookup Route Lth: ");
175 | sb.append(this.averageContentLookupRouteLength());
176 | sb.append("; ");
177 |
178 | sb.append("]");
179 |
180 | return sb.toString();
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/src/kademlia/dht/StoredContentManager.java:
--------------------------------------------------------------------------------
1 | package kademlia.dht;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.List;
6 | import java.util.Map;
7 | import java.util.NoSuchElementException;
8 | import kademlia.exceptions.ContentExistException;
9 | import kademlia.exceptions.ContentNotFoundException;
10 | import kademlia.node.KademliaId;
11 |
12 | /**
13 | * It would be infeasible to keep all content in memory to be send when requested
14 | * Instead we store content into files
15 | * We use this Class to keep track of all content stored
16 | *
17 | * @author Joshua Kissoon
18 | * @since 20140226
19 | */
20 | class StoredContentManager
21 | {
22 |
23 | private final Map> entries;
24 |
25 |
26 | {
27 | entries = new HashMap<>();
28 | }
29 |
30 | /**
31 | * Add a new entry to our storage
32 | *
33 | * @param content The content to store a reference to
34 | */
35 | public KademliaStorageEntryMetadata put(KadContent content) throws ContentExistException
36 | {
37 | return this.put(new StorageEntryMetadata(content));
38 | }
39 |
40 | /**
41 | * Add a new entry to our storage
42 | *
43 | * @param entry The StorageEntry to store
44 | */
45 | public KademliaStorageEntryMetadata put(KademliaStorageEntryMetadata entry) throws ContentExistException
46 | {
47 | if (!this.entries.containsKey(entry.getKey()))
48 | {
49 | this.entries.put(entry.getKey(), new ArrayList<>());
50 | }
51 |
52 | /* If this entry doesn't already exist, then we add it */
53 | if (!this.contains(entry))
54 | {
55 | this.entries.get(entry.getKey()).add(entry);
56 |
57 | return entry;
58 | }
59 | else
60 | {
61 | throw new ContentExistException("Content already exists on this DHT");
62 | }
63 | }
64 |
65 | /**
66 | * Checks if our DHT has a Content for the given criteria
67 | *
68 | * @param param The parameters used to search for a content
69 | *
70 | * @return boolean
71 | */
72 | public synchronized boolean contains(GetParameter param)
73 | {
74 | if (this.entries.containsKey(param.getKey()))
75 | {
76 | /* Content with this key exist, check if any match the rest of the search criteria */
77 | for (KademliaStorageEntryMetadata e : this.entries.get(param.getKey()))
78 | {
79 | /* If any entry satisfies the given parameters, return true */
80 | if (e.satisfiesParameters(param))
81 | {
82 | return true;
83 | }
84 | }
85 | }
86 | else
87 | {
88 | }
89 | return false;
90 | }
91 |
92 | /**
93 | * Check if a content exist in the DHT
94 | */
95 | public synchronized boolean contains(KadContent content)
96 | {
97 | return this.contains(new GetParameter(content));
98 | }
99 |
100 | /**
101 | * Check if a StorageEntry exist on this DHT
102 | */
103 | public synchronized boolean contains(KademliaStorageEntryMetadata entry)
104 | {
105 | return this.contains(new GetParameter(entry));
106 | }
107 |
108 | /**
109 | * Checks if our DHT has a Content for the given criteria
110 | *
111 | * @param param The parameters used to search for a content
112 | *
113 | * @return List of content for the specific search parameters
114 | */
115 | public KademliaStorageEntryMetadata get(GetParameter param) throws NoSuchElementException
116 | {
117 | if (this.entries.containsKey(param.getKey()))
118 | {
119 | /* Content with this key exist, check if any match the rest of the search criteria */
120 | for (KademliaStorageEntryMetadata e : this.entries.get(param.getKey()))
121 | {
122 | /* If any entry satisfies the given parameters, return true */
123 | if (e.satisfiesParameters(param))
124 | {
125 | return e;
126 | }
127 | }
128 |
129 | /* If we got here, means we didn't find any entry */
130 | throw new NoSuchElementException();
131 | }
132 | else
133 | {
134 | throw new NoSuchElementException("No content exist for the given parameters");
135 | }
136 | }
137 |
138 | public KademliaStorageEntryMetadata get(KademliaStorageEntryMetadata md)
139 | {
140 | return this.get(new GetParameter(md));
141 | }
142 |
143 | /**
144 | * @return A list of all storage entries
145 | */
146 | public synchronized List getAllEntries()
147 | {
148 | List entriesRet = new ArrayList<>();
149 |
150 | for (List entrySet : this.entries.values())
151 | {
152 | if (entrySet.size() > 0)
153 | {
154 | entriesRet.addAll(entrySet);
155 | }
156 | }
157 |
158 | return entriesRet;
159 | }
160 |
161 | public void remove(KadContent content) throws ContentNotFoundException
162 | {
163 | this.remove(new StorageEntryMetadata(content));
164 | }
165 |
166 | public void remove(KademliaStorageEntryMetadata entry) throws ContentNotFoundException
167 | {
168 | if (contains(entry))
169 | {
170 | this.entries.get(entry.getKey()).remove(entry);
171 | }
172 | else
173 | {
174 | throw new ContentNotFoundException("This content does not exist in the Storage Entries");
175 | }
176 | }
177 |
178 | @Override
179 | public synchronized String toString()
180 | {
181 | StringBuilder sb = new StringBuilder("Stored Content: \n");
182 | int count = 0;
183 | for (List es : this.entries.values())
184 | {
185 | if (entries.size() < 1)
186 | {
187 | continue;
188 | }
189 |
190 | for (KademliaStorageEntryMetadata e : es)
191 | {
192 | sb.append(++count);
193 | sb.append(". ");
194 | sb.append(e);
195 | sb.append("\n");
196 | }
197 | }
198 |
199 | sb.append("\n");
200 | return sb.toString();
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/src/kademlia/routing/JKademliaRoutingTable.java:
--------------------------------------------------------------------------------
1 | package kademlia.routing;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.TreeSet;
6 | import kademlia.KadConfiguration;
7 | import kademlia.node.KeyComparator;
8 | import kademlia.node.Node;
9 | import kademlia.node.KademliaId;
10 |
11 | /**
12 | * Implementation of a Kademlia routing table
13 | *
14 | * @author Joshua Kissoon
15 | * @created 20140215
16 | */
17 | public class JKademliaRoutingTable implements KademliaRoutingTable
18 | {
19 |
20 | private final Node localNode; // The current node
21 | private transient KademliaBucket[] buckets;
22 |
23 | private transient KadConfiguration config;
24 |
25 | public JKademliaRoutingTable(Node localNode, KadConfiguration config)
26 | {
27 | this.localNode = localNode;
28 | this.config = config;
29 |
30 | /* Initialize all of the buckets to a specific depth */
31 | this.initialize();
32 |
33 | /* Insert the local node */
34 | this.insert(localNode);
35 | }
36 |
37 | /**
38 | * Initialize the JKademliaRoutingTable to it's default state
39 | */
40 | @Override
41 | public final void initialize()
42 | {
43 | this.buckets = new KademliaBucket[KademliaId.ID_LENGTH];
44 | for (int i = 0; i < KademliaId.ID_LENGTH; i++)
45 | {
46 | buckets[i] = new JKademliaBucket(i, this.config);
47 | }
48 | }
49 |
50 | @Override
51 | public void setConfiguration(KadConfiguration config)
52 | {
53 | this.config = config;
54 | }
55 |
56 | /**
57 | * Adds a contact to the routing table based on how far it is from the LocalNode.
58 | *
59 | * @param c The contact to add
60 | */
61 | @Override
62 | public synchronized final void insert(Contact c)
63 | {
64 | this.buckets[this.getBucketId(c.getNode().getNodeId())].insert(c);
65 | }
66 |
67 | /**
68 | * Adds a node to the routing table based on how far it is from the LocalNode.
69 | *
70 | * @param n The node to add
71 | */
72 | @Override
73 | public synchronized final void insert(Node n)
74 | {
75 | this.buckets[this.getBucketId(n.getNodeId())].insert(n);
76 | }
77 |
78 | /**
79 | * 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.
80 | *
81 | * @param nid The NodeId for which we want to find which bucket it belong to
82 | *
83 | * @return Integer The bucket ID in which the given node should be placed.
84 | */
85 | @Override
86 | public final int getBucketId(KademliaId nid)
87 | {
88 | int bId = this.localNode.getNodeId().getDistance(nid) - 1;
89 |
90 | /* 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 */
91 | return bId < 0 ? 0 : bId;
92 | }
93 |
94 | /**
95 | * Find the closest set of contacts to a given NodeId
96 | *
97 | * @param target The NodeId to find contacts close to
98 | * @param numNodesRequired The number of contacts to find
99 | *
100 | * @return List A List of contacts closest to target
101 | */
102 | @Override
103 | public synchronized final List findClosest(KademliaId target, int numNodesRequired)
104 | {
105 | TreeSet sortedSet = new TreeSet<>(new KeyComparator(target));
106 | sortedSet.addAll(this.getAllNodes());
107 |
108 | List closest = new ArrayList<>(numNodesRequired);
109 |
110 | /* Now we have the sorted set, lets get the top numRequired */
111 | int count = 0;
112 | for (Node n : sortedSet)
113 | {
114 | closest.add(n);
115 | if (++count == numNodesRequired)
116 | {
117 | break;
118 | }
119 | }
120 | return closest;
121 | }
122 |
123 | /**
124 | * @return List A List of all Nodes in this JKademliaRoutingTable
125 | */
126 | @Override
127 | public synchronized final List getAllNodes()
128 | {
129 | List nodes = new ArrayList<>();
130 |
131 | for (KademliaBucket b : this.buckets)
132 | {
133 | for (Contact c : b.getContacts())
134 | {
135 | nodes.add(c.getNode());
136 | }
137 | }
138 |
139 | return nodes;
140 | }
141 |
142 | /**
143 | * @return List A List of all Nodes in this JKademliaRoutingTable
144 | */
145 | @Override
146 | public final List getAllContacts()
147 | {
148 | List contacts = new ArrayList<>();
149 |
150 | for (KademliaBucket b : this.buckets)
151 | {
152 | contacts.addAll(b.getContacts());
153 | }
154 |
155 | return contacts;
156 | }
157 |
158 | /**
159 | * @return Bucket[] The buckets in this Kad Instance
160 | */
161 | @Override
162 | public final KademliaBucket[] getBuckets()
163 | {
164 | return this.buckets;
165 | }
166 |
167 | /**
168 | * Set the KadBuckets of this routing table, mainly used when retrieving saved state
169 | *
170 | * @param buckets
171 | */
172 | public final void setBuckets(KademliaBucket[] buckets)
173 | {
174 | this.buckets = buckets;
175 | }
176 |
177 | /**
178 | * Method used by operations to notify the routing table of any contacts that have been unresponsive.
179 | *
180 | * @param contacts The set of unresponsive contacts
181 | */
182 | @Override
183 | public void setUnresponsiveContacts(List contacts)
184 | {
185 | if (contacts.isEmpty())
186 | {
187 | return;
188 | }
189 | for (Node n : contacts)
190 | {
191 | this.setUnresponsiveContact(n);
192 | }
193 | }
194 |
195 | /**
196 | * Method used by operations to notify the routing table of any contacts that have been unresponsive.
197 | *
198 | * @param n
199 | */
200 | @Override
201 | public synchronized void setUnresponsiveContact(Node n)
202 | {
203 | int bucketId = this.getBucketId(n.getNodeId());
204 |
205 | /* Remove the contact from the bucket */
206 | this.buckets[bucketId].removeNode(n);
207 | }
208 |
209 | @Override
210 | public synchronized final String toString()
211 | {
212 | StringBuilder sb = new StringBuilder("\nPrinting Routing Table Started ***************** \n");
213 | int totalContacts = 0;
214 | for (KademliaBucket b : this.buckets)
215 | {
216 | if (b.numContacts() > 0)
217 | {
218 | totalContacts += b.numContacts();
219 | sb.append("# nodes in Bucket with depth ");
220 | sb.append(b.getDepth());
221 | sb.append(": ");
222 | sb.append(b.numContacts());
223 | sb.append("\n");
224 | sb.append(b.toString());
225 | sb.append("\n");
226 | }
227 | }
228 |
229 | sb.append("\nTotal Contacts: ");
230 | sb.append(totalContacts);
231 | sb.append("\n\n");
232 |
233 | sb.append("Printing Routing Table Ended ******************** ");
234 |
235 | return sb.toString();
236 | }
237 |
238 | }
239 |
--------------------------------------------------------------------------------
/src/kademlia/node/KademliaId.java:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Joshua Kissoon
3 | * @created 20140215
4 | * @desc Represents a Kademlia Node ID
5 | */
6 | package kademlia.node;
7 |
8 | import java.io.DataInputStream;
9 | import java.io.DataOutputStream;
10 | import java.io.IOException;
11 | import java.io.Serializable;
12 | import java.math.BigInteger;
13 | import java.util.Arrays;
14 | import java.util.BitSet;
15 | import java.util.Random;
16 | import kademlia.message.Streamable;
17 |
18 | public class KademliaId implements Streamable, Serializable
19 | {
20 |
21 | public final transient static int ID_LENGTH = 160;
22 | private byte[] keyBytes;
23 |
24 | /**
25 | * Construct the NodeId from some string
26 | *
27 | * @param data The user generated key string
28 | */
29 | public KademliaId(String data)
30 | {
31 | keyBytes = data.getBytes();
32 | if (keyBytes.length != ID_LENGTH / 8)
33 | {
34 | throw new IllegalArgumentException("Specified Data need to be " + (ID_LENGTH / 8) + " characters long.");
35 | }
36 | }
37 |
38 | /**
39 | * Generate a random key
40 | */
41 | public KademliaId()
42 | {
43 | keyBytes = new byte[ID_LENGTH / 8];
44 | new Random().nextBytes(keyBytes);
45 | }
46 |
47 | /**
48 | * Generate the NodeId from a given byte[]
49 | *
50 | * @param bytes
51 | */
52 | public KademliaId(byte[] bytes)
53 | {
54 | if (bytes.length != ID_LENGTH / 8)
55 | {
56 | throw new IllegalArgumentException("Specified Data need to be " + (ID_LENGTH / 8) + " characters long. Data Given: '" + new String(bytes) + "'");
57 | }
58 | this.keyBytes = bytes;
59 | }
60 |
61 | /**
62 | * Load the NodeId from a DataInput stream
63 | *
64 | * @param in The stream from which to load the NodeId
65 | *
66 | * @throws IOException
67 | */
68 | public KademliaId(DataInputStream in) throws IOException
69 | {
70 | this.fromStream(in);
71 | }
72 |
73 | public byte[] getBytes()
74 | {
75 | return this.keyBytes;
76 | }
77 |
78 | /**
79 | * @return The BigInteger representation of the key
80 | */
81 | public BigInteger getInt()
82 | {
83 | return new BigInteger(1, this.getBytes());
84 | }
85 |
86 | /**
87 | * Compares a NodeId to this NodeId
88 | *
89 | * @param o The NodeId to compare to this NodeId
90 | *
91 | * @return boolean Whether the 2 NodeIds are equal
92 | */
93 | @Override
94 | public boolean equals(Object o)
95 | {
96 | if (o instanceof KademliaId)
97 | {
98 | KademliaId nid = (KademliaId) o;
99 | return this.hashCode() == nid.hashCode();
100 | }
101 | return false;
102 | }
103 |
104 | @Override
105 | public int hashCode()
106 | {
107 | int hash = 7;
108 | hash = 83 * hash + Arrays.hashCode(this.keyBytes);
109 | return hash;
110 | }
111 |
112 | /**
113 | * Checks the distance between this and another NodeId
114 | *
115 | * @param nid
116 | *
117 | * @return The distance of this NodeId from the given NodeId
118 | */
119 | public KademliaId xor(KademliaId nid)
120 | {
121 | byte[] result = new byte[ID_LENGTH / 8];
122 | byte[] nidBytes = nid.getBytes();
123 |
124 | for (int i = 0; i < ID_LENGTH / 8; i++)
125 | {
126 | result[i] = (byte) (this.keyBytes[i] ^ nidBytes[i]);
127 | }
128 |
129 | KademliaId resNid = new KademliaId(result);
130 |
131 | return resNid;
132 | }
133 |
134 | /**
135 | * Generates a NodeId that is some distance away from this NodeId
136 | *
137 | * @param distance in number of bits
138 | *
139 | * @return NodeId The newly generated NodeId
140 | */
141 | public KademliaId generateNodeIdByDistance(int distance)
142 | {
143 | byte[] result = new byte[ID_LENGTH / 8];
144 |
145 | /* Since distance = ID_LENGTH - prefixLength, we need to fill that amount with 0's */
146 | int numByteZeroes = (ID_LENGTH - distance) / 8;
147 | int numBitZeroes = 8 - (distance % 8);
148 |
149 | /* Filling byte zeroes */
150 | for (int i = 0; i < numByteZeroes; i++)
151 | {
152 | result[i] = 0;
153 | }
154 |
155 | /* Filling bit zeroes */
156 | BitSet bits = new BitSet(8);
157 | bits.set(0, 8);
158 |
159 | for (int i = 0; i < numBitZeroes; i++)
160 | {
161 | /* Shift 1 zero into the start of the value */
162 | bits.clear(i);
163 | }
164 | bits.flip(0, 8); // Flip the bits since they're in reverse order
165 | result[numByteZeroes] = (byte) bits.toByteArray()[0];
166 |
167 | /* Set the remaining bytes to Maximum value */
168 | for (int i = numByteZeroes + 1; i < result.length; i++)
169 | {
170 | result[i] = Byte.MAX_VALUE;
171 | }
172 |
173 | return this.xor(new KademliaId(result));
174 | }
175 |
176 | /**
177 | * Counts the number of leading 0's in this NodeId
178 | *
179 | * @return Integer The number of leading 0's
180 | */
181 | public int getFirstSetBitIndex()
182 | {
183 | int prefixLength = 0;
184 |
185 | for (byte b : this.keyBytes)
186 | {
187 | if (b == 0)
188 | {
189 | prefixLength += 8;
190 | }
191 | else
192 | {
193 | /* If the byte is not 0, we need to count how many MSBs are 0 */
194 | int count = 0;
195 | for (int i = 7; i >= 0; i--)
196 | {
197 | boolean a = (b & (1 << i)) == 0;
198 | if (a)
199 | {
200 | count++;
201 | }
202 | else
203 | {
204 | break; // Reset the count if we encounter a non-zero number
205 | }
206 | }
207 |
208 | /* Add the count of MSB 0s to the prefix length */
209 | prefixLength += count;
210 |
211 | /* Break here since we've now covered the MSB 0s */
212 | break;
213 | }
214 | }
215 | return prefixLength;
216 | }
217 |
218 | /**
219 | * Gets the distance from this NodeId to another NodeId
220 | *
221 | * @param to
222 | *
223 | * @return Integer The distance
224 | */
225 | public int getDistance(KademliaId to)
226 | {
227 | /**
228 | * Compute the xor of this and to
229 | * Get the index i of the first set bit of the xor returned NodeId
230 | * The distance between them is ID_LENGTH - i
231 | */
232 | return ID_LENGTH - this.xor(to).getFirstSetBitIndex();
233 | }
234 |
235 | @Override
236 | public void toStream(DataOutputStream out) throws IOException
237 | {
238 | /* Add the NodeId to the stream */
239 | out.write(this.getBytes());
240 | }
241 |
242 | @Override
243 | public final void fromStream(DataInputStream in) throws IOException
244 | {
245 | byte[] input = new byte[ID_LENGTH / 8];
246 | in.readFully(input);
247 | this.keyBytes = input;
248 | }
249 |
250 | public String hexRepresentation()
251 | {
252 | /* Returns the hex format of this NodeId */
253 | BigInteger bi = new BigInteger(1, this.keyBytes);
254 | return String.format("%0" + (this.keyBytes.length << 1) + "X", bi);
255 | }
256 |
257 | @Override
258 | public String toString()
259 | {
260 | return this.hexRepresentation();
261 | }
262 |
263 | }
264 |
--------------------------------------------------------------------------------
/src/kademlia/routing/JKademliaBucket.java:
--------------------------------------------------------------------------------
1 | package kademlia.routing;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.NoSuchElementException;
6 | import java.util.TreeSet;
7 | import kademlia.KadConfiguration;
8 | import kademlia.node.Node;
9 |
10 | /**
11 | * A bucket in the Kademlia routing table
12 | *
13 | * @author Joshua Kissoon
14 | * @created 20140215
15 | */
16 | public class JKademliaBucket implements KademliaBucket
17 | {
18 |
19 | /* How deep is this bucket in the Routing Table */
20 | private final int depth;
21 |
22 | /* Contacts stored in this routing table */
23 | private final TreeSet contacts;
24 |
25 | /* A set of last seen contacts that can replace any current contact that is unresponsive */
26 | private final TreeSet replacementCache;
27 |
28 | private final KadConfiguration config;
29 |
30 |
31 | {
32 | contacts = new TreeSet<>();
33 | replacementCache = new TreeSet<>();
34 | }
35 |
36 | /**
37 | * @param depth How deep in the routing tree is this bucket
38 | * @param config
39 | */
40 | public JKademliaBucket(int depth, KadConfiguration config)
41 | {
42 | this.depth = depth;
43 | this.config = config;
44 | }
45 |
46 | @Override
47 | public synchronized void insert(Contact c)
48 | {
49 | if (this.contacts.contains(c))
50 | {
51 | /**
52 | * If the contact is already in the bucket, lets update that we've seen it
53 | * We need to remove and re-add the contact to get the Sorted Set to update sort order
54 | */
55 | Contact tmp = this.removeFromContacts(c.getNode());
56 | tmp.setSeenNow();
57 | tmp.resetStaleCount();
58 | this.contacts.add(tmp);
59 | }
60 | else
61 | {
62 | /* If the bucket is filled, so put the contacts in the replacement cache */
63 | if (contacts.size() >= this.config.k())
64 | {
65 | /* If the cache is empty, we check if any contacts are stale and replace the stalest one */
66 | Contact stalest = null;
67 | for (Contact tmp : this.contacts)
68 | {
69 | if (tmp.staleCount() >= this.config.stale())
70 | {
71 | /* Contact is stale */
72 | if (stalest == null)
73 | {
74 | stalest = tmp;
75 | }
76 | else if (tmp.staleCount() > stalest.staleCount())
77 | {
78 | stalest = tmp;
79 | }
80 | }
81 | }
82 |
83 | /* If we have a stale contact, remove it and add the new contact to the bucket */
84 | if (stalest != null)
85 | {
86 | this.contacts.remove(stalest);
87 | this.contacts.add(c);
88 | }
89 | else
90 | {
91 | /* No stale contact, lets insert this into replacement cache */
92 | this.insertIntoReplacementCache(c);
93 | }
94 | }
95 | else
96 | {
97 | this.contacts.add(c);
98 | }
99 | }
100 | }
101 |
102 | @Override
103 | public synchronized void insert(Node n)
104 | {
105 | this.insert(new Contact(n));
106 | }
107 |
108 | @Override
109 | public synchronized boolean containsContact(Contact c)
110 | {
111 | return this.contacts.contains(c);
112 | }
113 |
114 | @Override
115 | public synchronized boolean containsNode(Node n)
116 | {
117 | return this.containsContact(new Contact(n));
118 | }
119 |
120 | @Override
121 | public synchronized boolean removeContact(Contact c)
122 | {
123 | /* If the contact does not exist, then we failed to remove it */
124 | if (!this.contacts.contains(c))
125 | {
126 | return false;
127 | }
128 |
129 | /* Contact exist, lets remove it only if our replacement cache has a replacement */
130 | if (!this.replacementCache.isEmpty())
131 | {
132 | /* Replace the contact with one from the replacement cache */
133 | this.contacts.remove(c);
134 | Contact replacement = this.replacementCache.first();
135 | this.contacts.add(replacement);
136 | this.replacementCache.remove(replacement);
137 | }
138 | else
139 | {
140 | /* There is no replacement, just increment the contact's stale count */
141 | this.getFromContacts(c.getNode()).incrementStaleCount();
142 | }
143 |
144 | return true;
145 | }
146 |
147 | private synchronized Contact getFromContacts(Node n)
148 | {
149 | for (Contact c : this.contacts)
150 | {
151 | if (c.getNode().equals(n))
152 | {
153 | return c;
154 | }
155 | }
156 |
157 | /* This contact does not exist */
158 | throw new NoSuchElementException("The contact does not exist in the contacts list.");
159 | }
160 |
161 | private synchronized Contact removeFromContacts(Node n)
162 | {
163 | for (Contact c : this.contacts)
164 | {
165 | if (c.getNode().equals(n))
166 | {
167 | this.contacts.remove(c);
168 | return c;
169 | }
170 | }
171 |
172 | /* We got here means this element does not exist */
173 | throw new NoSuchElementException("Node does not exist in the replacement cache. ");
174 | }
175 |
176 | @Override
177 | public synchronized boolean removeNode(Node n)
178 | {
179 | return this.removeContact(new Contact(n));
180 | }
181 |
182 | @Override
183 | public synchronized int numContacts()
184 | {
185 | return this.contacts.size();
186 | }
187 |
188 | @Override
189 | public synchronized int getDepth()
190 | {
191 | return this.depth;
192 | }
193 |
194 | @Override
195 | public synchronized List getContacts()
196 | {
197 | final ArrayList ret = new ArrayList<>();
198 |
199 | /* If we have no contacts, return the blank arraylist */
200 | if (this.contacts.isEmpty())
201 | {
202 | return ret;
203 | }
204 |
205 | /* We have contacts, lets copy put them into the arraylist and return */
206 | for (Contact c : this.contacts)
207 | {
208 | ret.add(c);
209 | }
210 |
211 | return ret;
212 | }
213 |
214 | /**
215 | * When the bucket is filled, we keep extra contacts in the replacement cache.
216 | */
217 | private synchronized void insertIntoReplacementCache(Contact c)
218 | {
219 | /* Just return if this contact is already in our replacement cache */
220 | if (this.replacementCache.contains(c))
221 | {
222 | /**
223 | * If the contact is already in the bucket, lets update that we've seen it
224 | * We need to remove and re-add the contact to get the Sorted Set to update sort order
225 | */
226 | Contact tmp = this.removeFromReplacementCache(c.getNode());
227 | tmp.setSeenNow();
228 | this.replacementCache.add(tmp);
229 | }
230 | else if (this.replacementCache.size() > this.config.k())
231 | {
232 | /* if our cache is filled, we remove the least recently seen contact */
233 | this.replacementCache.remove(this.replacementCache.last());
234 | this.replacementCache.add(c);
235 | }
236 | else
237 | {
238 | this.replacementCache.add(c);
239 | }
240 | }
241 |
242 | private synchronized Contact removeFromReplacementCache(Node n)
243 | {
244 | for (Contact c : this.replacementCache)
245 | {
246 | if (c.getNode().equals(n))
247 | {
248 | this.replacementCache.remove(c);
249 | return c;
250 | }
251 | }
252 |
253 | /* We got here means this element does not exist */
254 | throw new NoSuchElementException("Node does not exist in the replacement cache. ");
255 | }
256 |
257 | @Override
258 | public synchronized String toString()
259 | {
260 | StringBuilder sb = new StringBuilder("Bucket at depth: ");
261 | sb.append(this.depth);
262 | sb.append("\n Nodes: \n");
263 | for (Contact n : this.contacts)
264 | {
265 | sb.append("Node: ");
266 | sb.append(n.getNode().getNodeId().toString());
267 | sb.append(" (stale: ");
268 | sb.append(n.staleCount());
269 | sb.append(")");
270 | sb.append("\n");
271 | }
272 |
273 | return sb.toString();
274 | }
275 | }
276 |
--------------------------------------------------------------------------------
/src/kademlia/dht/DHT.java:
--------------------------------------------------------------------------------
1 | package kademlia.dht;
2 |
3 | import java.io.DataInputStream;
4 | import java.io.DataOutputStream;
5 | import java.io.File;
6 | import java.io.FileInputStream;
7 | import java.io.FileNotFoundException;
8 | import java.io.FileOutputStream;
9 | import java.io.IOException;
10 | import java.util.List;
11 | import java.util.NoSuchElementException;
12 | import kademlia.KadConfiguration;
13 | import kademlia.exceptions.ContentExistException;
14 | import kademlia.exceptions.ContentNotFoundException;
15 | import kademlia.node.KademliaId;
16 | import kademlia.util.serializer.JsonSerializer;
17 | import kademlia.util.serializer.KadSerializer;
18 |
19 | /**
20 | * The main Distributed Hash Table class that manages the entire DHT
21 | *
22 | * @author Joshua Kissoon
23 | * @since 20140226
24 | */
25 | public class DHT implements KademliaDHT
26 | {
27 |
28 | private transient StoredContentManager contentManager;
29 | private transient KadSerializer serializer = null;
30 | private transient KadConfiguration config;
31 |
32 | private final String ownerId;
33 |
34 | public DHT(String ownerId, KadConfiguration config)
35 | {
36 | this.ownerId = ownerId;
37 | this.config = config;
38 | this.initialize();
39 | }
40 |
41 | @Override
42 | public final void initialize()
43 | {
44 | contentManager = new StoredContentManager();
45 | }
46 |
47 | @Override
48 | public void setConfiguration(KadConfiguration con)
49 | {
50 | this.config = con;
51 | }
52 |
53 | @Override
54 | public KadSerializer getSerializer()
55 | {
56 | if (null == serializer)
57 | {
58 | serializer = new JsonSerializer<>();
59 | }
60 |
61 | return serializer;
62 | }
63 |
64 | @Override
65 | public boolean store(JKademliaStorageEntry content) throws IOException
66 | {
67 | /* Lets check if we have this content and it's the updated version */
68 | if (this.contentManager.contains(content.getContentMetadata()))
69 | {
70 | KademliaStorageEntryMetadata current = this.contentManager.get(content.getContentMetadata());
71 |
72 | /* update the last republished time */
73 | current.updateLastRepublished();
74 |
75 | if (current.getLastUpdatedTimestamp() >= content.getContentMetadata().getLastUpdatedTimestamp())
76 | {
77 | /* We have the current content, no need to update it! just leave this method now */
78 | return false;
79 | }
80 | else
81 | {
82 | /* We have this content, but not the latest version, lets delete it so the new version will be added below */
83 | try
84 | {
85 | //System.out.println("Removing older content to update it");
86 | this.remove(content.getContentMetadata());
87 | }
88 | catch (ContentNotFoundException ex)
89 | {
90 | /* This won't ever happen at this point since we only get here if the content is found, lets ignore it */
91 | }
92 | }
93 | }
94 |
95 | /**
96 | * If we got here means we don't have this content, or we need to update the content
97 | * If we need to update the content, the code above would've already deleted it, so we just need to re-add it
98 | */
99 | try
100 | {
101 | //System.out.println("Adding new content.");
102 | /* Keep track of this content in the entries manager */
103 | KademliaStorageEntryMetadata sEntry = this.contentManager.put(content.getContentMetadata());
104 |
105 | /* Now we store the content locally in a file */
106 | String contentStorageFolder = this.getContentStorageFolderName(content.getContentMetadata().getKey());
107 |
108 | try (FileOutputStream fout = new FileOutputStream(contentStorageFolder + File.separator + sEntry.hashCode() + ".kct");
109 | DataOutputStream dout = new DataOutputStream(fout))
110 | {
111 | this.getSerializer().write(content, dout);
112 | }
113 | return true;
114 | }
115 | catch (ContentExistException e)
116 | {
117 | /**
118 | * Content already exist on the DHT
119 | * This won't happen because above takes care of removing the content if it's older and needs to be updated,
120 | * or returning if we already have the current content version.
121 | */
122 | return false;
123 | }
124 | }
125 |
126 | @Override
127 | public boolean store(KadContent content) throws IOException
128 | {
129 | return this.store(new JKademliaStorageEntry(content));
130 | }
131 |
132 | @Override
133 | public JKademliaStorageEntry retrieve(KademliaId key, int hashCode) throws FileNotFoundException, IOException, ClassNotFoundException
134 | {
135 | String folder = this.getContentStorageFolderName(key);
136 | DataInputStream din = new DataInputStream(new FileInputStream(folder + File.separator + hashCode + ".kct"));
137 | return this.getSerializer().read(din);
138 | }
139 |
140 | @Override
141 | public boolean contains(GetParameter param)
142 | {
143 | return this.contentManager.contains(param);
144 | }
145 |
146 | @Override
147 | public JKademliaStorageEntry get(KademliaStorageEntryMetadata entry) throws IOException, NoSuchElementException
148 | {
149 | try
150 | {
151 | return this.retrieve(entry.getKey(), entry.hashCode());
152 | }
153 | catch (FileNotFoundException e)
154 | {
155 | System.err.println("Error while loading file for content. Message: " + e.getMessage());
156 | }
157 | catch (ClassNotFoundException e)
158 | {
159 | System.err.println("The class for some content was not found. Message: " + e.getMessage());
160 | }
161 |
162 | /* If we got here, means we got no entries */
163 | throw new NoSuchElementException();
164 | }
165 |
166 | @Override
167 | public JKademliaStorageEntry get(GetParameter param) throws NoSuchElementException, IOException
168 | {
169 | /* Load a KadContent if any exist for the given criteria */
170 | try
171 | {
172 | KademliaStorageEntryMetadata e = this.contentManager.get(param);
173 | return this.retrieve(e.getKey(), e.hashCode());
174 | }
175 | catch (FileNotFoundException e)
176 | {
177 | System.err.println("Error while loading file for content. Message: " + e.getMessage());
178 | }
179 | catch (ClassNotFoundException e)
180 | {
181 | System.err.println("The class for some content was not found. Message: " + e.getMessage());
182 | }
183 |
184 | /* If we got here, means we got no entries */
185 | throw new NoSuchElementException();
186 | }
187 |
188 | @Override
189 | public void remove(KadContent content) throws ContentNotFoundException
190 | {
191 | this.remove(new StorageEntryMetadata(content));
192 | }
193 |
194 | @Override
195 | public void remove(KademliaStorageEntryMetadata entry) throws ContentNotFoundException
196 | {
197 | String folder = this.getContentStorageFolderName(entry.getKey());
198 | File file = new File(folder + File.separator + entry.hashCode() + ".kct");
199 |
200 | contentManager.remove(entry);
201 |
202 | if (file.exists())
203 | {
204 | file.delete();
205 | }
206 | else
207 | {
208 | throw new ContentNotFoundException();
209 | }
210 | }
211 |
212 | /**
213 | * Get the name of the folder for which a content should be stored
214 | *
215 | * @param key The key of the content
216 | *
217 | * @return String The name of the folder
218 | */
219 | private String getContentStorageFolderName(KademliaId key)
220 | {
221 | /**
222 | * Each content is stored in a folder named after the first 2 characters of the NodeId
223 | *
224 | * The name of the file containing the content is the hash of this content
225 | */
226 | String folderName = key.hexRepresentation().substring(0, 2);
227 | File contentStorageFolder = new File(this.config.getNodeDataFolder(ownerId) + File.separator + folderName);
228 |
229 | /* Create the content folder if it doesn't exist */
230 | if (!contentStorageFolder.isDirectory())
231 | {
232 | contentStorageFolder.mkdir();
233 | }
234 |
235 | return contentStorageFolder.toString();
236 | }
237 |
238 | @Override
239 | public List getStorageEntries()
240 | {
241 | return contentManager.getAllEntries();
242 | }
243 |
244 | @Override
245 | public void putStorageEntries(List ientries)
246 | {
247 | for (KademliaStorageEntryMetadata e : ientries)
248 | {
249 | try
250 | {
251 | this.contentManager.put(e);
252 | }
253 | catch (ContentExistException ex)
254 | {
255 | /* Entry already exist, no need to store it again */
256 | }
257 | }
258 | }
259 |
260 | @Override
261 | public synchronized String toString()
262 | {
263 | return this.contentManager.toString();
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/src/kademlia/operation/NodeLookupOperation.java:
--------------------------------------------------------------------------------
1 | package kademlia.operation;
2 |
3 | import kademlia.message.Receiver;
4 | import java.io.IOException;
5 | import java.util.ArrayList;
6 | import java.util.Comparator;
7 | import java.util.HashMap;
8 | import java.util.List;
9 | import java.util.Map;
10 | import java.util.TreeMap;
11 | import kademlia.KadConfiguration;
12 | import kademlia.KadServer;
13 | import kademlia.KademliaNode;
14 | import kademlia.exceptions.RoutingException;
15 | import kademlia.message.Message;
16 | import kademlia.message.NodeLookupMessage;
17 | import kademlia.message.NodeReplyMessage;
18 | import kademlia.node.KeyComparator;
19 | import kademlia.node.Node;
20 | import kademlia.node.KademliaId;
21 |
22 | /**
23 | * Finds the K closest nodes to a specified identifier
24 | * The algorithm terminates when it has gotten responses from the K closest nodes it has seen.
25 | * Nodes that fail to respond are removed from consideration
26 | *
27 | * @author Joshua Kissoon
28 | * @created 20140219
29 | */
30 | public class NodeLookupOperation implements Operation, Receiver
31 | {
32 |
33 | /* Constants */
34 | private static final String UNASKED = "UnAsked";
35 | private static final String AWAITING = "Awaiting";
36 | private static final String ASKED = "Asked";
37 | private static final String FAILED = "Failed";
38 |
39 | private final KadServer server;
40 | private final KademliaNode localNode;
41 | private final KadConfiguration config;
42 |
43 | private final Message lookupMessage; // Message sent to each peer
44 | private final Map nodes;
45 |
46 | /* Tracks messages in transit and awaiting reply */
47 | private final Map messagesTransiting;
48 |
49 | /* Used to sort nodes */
50 | private final Comparator comparator;
51 |
52 |
53 | {
54 | messagesTransiting = new HashMap<>();
55 | }
56 |
57 | /**
58 | * @param server KadServer used for communication
59 | * @param localNode The local node making the communication
60 | * @param lookupId The ID for which to find nodes close to
61 | * @param config
62 | */
63 | public NodeLookupOperation(KadServer server, KademliaNode localNode, KademliaId lookupId, KadConfiguration config)
64 | {
65 | this.server = server;
66 | this.localNode = localNode;
67 | this.config = config;
68 |
69 | this.lookupMessage = new NodeLookupMessage(localNode.getNode(), lookupId);
70 |
71 | /**
72 | * We initialize a TreeMap to store nodes.
73 | * This map will be sorted by which nodes are closest to the lookupId
74 | */
75 | this.comparator = new KeyComparator(lookupId);
76 | this.nodes = new TreeMap(this.comparator);
77 | }
78 |
79 | /**
80 | * @throws java.io.IOException
81 | * @throws kademlia.exceptions.RoutingException
82 | */
83 | @Override
84 | public synchronized void execute() throws IOException, RoutingException
85 | {
86 | try
87 | {
88 | /* Set the local node as already asked */
89 | nodes.put(this.localNode.getNode(), ASKED);
90 |
91 | /**
92 | * We add all nodes here instead of the K-Closest because there may be the case that the K-Closest are offline
93 | * - The operation takes care of looking at the K-Closest.
94 | */
95 | this.addNodes(this.localNode.getRoutingTable().getAllNodes());
96 |
97 | /* If we haven't finished as yet, wait for a maximum of config.operationTimeout() time */
98 | int totalTimeWaited = 0;
99 | int timeInterval = 10; // We re-check every n milliseconds
100 | while (totalTimeWaited < this.config.operationTimeout())
101 | {
102 | if (!this.askNodesorFinish())
103 | {
104 | wait(timeInterval);
105 | totalTimeWaited += timeInterval;
106 | }
107 | else
108 | {
109 | break;
110 | }
111 | }
112 |
113 | /* Now after we've finished, we would have an idea of offline nodes, lets update our routing table */
114 | this.localNode.getRoutingTable().setUnresponsiveContacts(this.getFailedNodes());
115 |
116 | }
117 | catch (InterruptedException e)
118 | {
119 | throw new RuntimeException(e);
120 | }
121 | }
122 |
123 | public List getClosestNodes()
124 | {
125 | return this.closestNodes(ASKED);
126 | }
127 |
128 | /**
129 | * Add nodes from this list to the set of nodes to lookup
130 | *
131 | * @param list The list from which to add nodes
132 | */
133 | public void addNodes(List list)
134 | {
135 | for (Node o : list)
136 | {
137 | /* If this node is not in the list, add the node */
138 | if (!nodes.containsKey(o))
139 | {
140 | nodes.put(o, UNASKED);
141 | }
142 | }
143 | }
144 |
145 | /**
146 | * Asks some of the K closest nodes seen but not yet queried.
147 | * Assures that no more than DefaultConfiguration.CONCURRENCY messages are in transit at a time
148 | *
149 | * This method should be called every time a reply is received or a timeout occurs.
150 | *
151 | * If all K closest nodes have been asked and there are no messages in transit,
152 | * the algorithm is finished.
153 | *
154 | * @return true if finished OR false otherwise
155 | */
156 | private boolean askNodesorFinish() throws IOException
157 | {
158 | /* If >= CONCURRENCY nodes are in transit, don't do anything */
159 | if (this.config.maxConcurrentMessagesTransiting() <= this.messagesTransiting.size())
160 | {
161 | return false;
162 | }
163 |
164 | /* Get unqueried nodes among the K closest seen that have not FAILED */
165 | List unasked = this.closestNodesNotFailed(UNASKED);
166 |
167 | if (unasked.isEmpty() && this.messagesTransiting.isEmpty())
168 | {
169 | /* We have no unasked nodes nor any messages in transit, we're finished! */
170 | return true;
171 | }
172 |
173 | /**
174 | * Send messages to nodes in the list;
175 | * making sure than no more than CONCURRENCY messsages are in transit
176 | */
177 | for (int i = 0; (this.messagesTransiting.size() < this.config.maxConcurrentMessagesTransiting()) && (i < unasked.size()); i++)
178 | {
179 | Node n = (Node) unasked.get(i);
180 |
181 | int comm = server.sendMessage(n, lookupMessage, this);
182 |
183 | this.nodes.put(n, AWAITING);
184 | this.messagesTransiting.put(comm, n);
185 | }
186 |
187 | /* We're not finished as yet, return false */
188 | return false;
189 | }
190 |
191 | /**
192 | * @param status The status of the nodes to return
193 | *
194 | * @return The K closest nodes to the target lookupId given that have the specified status
195 | */
196 | private List closestNodes(String status)
197 | {
198 | List closestNodes = new ArrayList<>(this.config.k());
199 | int remainingSpaces = this.config.k();
200 |
201 | for (Map.Entry e : this.nodes.entrySet())
202 | {
203 | if (status.equals(e.getValue()))
204 | {
205 | /* We got one with the required status, now add it */
206 | closestNodes.add((Node) e.getKey());
207 | if (--remainingSpaces == 0)
208 | {
209 | break;
210 | }
211 | }
212 | }
213 |
214 | return closestNodes;
215 | }
216 |
217 | /**
218 | * Find The K closest nodes to the target lookupId given that have not FAILED.
219 | * From those K, get those that have the specified status
220 | *
221 | * @param status The status of the nodes to return
222 | *
223 | * @return A List of the closest nodes
224 | */
225 | private List closestNodesNotFailed(String status)
226 | {
227 | List closestNodes = new ArrayList<>(this.config.k());
228 | int remainingSpaces = this.config.k();
229 |
230 | for (Map.Entry e : this.nodes.entrySet())
231 | {
232 | if (!FAILED.equals(e.getValue()))
233 | {
234 | if (status.equals(e.getValue()))
235 | {
236 | /* We got one with the required status, now add it */
237 | closestNodes.add(e.getKey());
238 | }
239 |
240 | if (--remainingSpaces == 0)
241 | {
242 | break;
243 | }
244 | }
245 | }
246 |
247 | return closestNodes;
248 | }
249 |
250 | /**
251 | * Receive and handle the incoming NodeReplyMessage
252 | *
253 | * @param comm
254 | *
255 | * @throws java.io.IOException
256 | */
257 | @Override
258 | public synchronized void receive(Message incoming, int comm) throws IOException
259 | {
260 | if (!(incoming instanceof NodeReplyMessage))
261 | {
262 | /* Not sure why we get a message of a different type here... @todo Figure it out. */
263 | return;
264 | }
265 | /* We receive a NodeReplyMessage with a set of nodes, read this message */
266 | NodeReplyMessage msg = (NodeReplyMessage) incoming;
267 |
268 | /* Add the origin node to our routing table */
269 | Node origin = msg.getOrigin();
270 | this.localNode.getRoutingTable().insert(origin);
271 |
272 | /* Set that we've completed ASKing the origin node */
273 | this.nodes.put(origin, ASKED);
274 |
275 | /* Remove this msg from messagesTransiting since it's completed now */
276 | this.messagesTransiting.remove(comm);
277 |
278 | /* Add the received nodes to our nodes list to query */
279 | this.addNodes(msg.getNodes());
280 | this.askNodesorFinish();
281 | }
282 |
283 | /**
284 | * A node does not respond or a packet was lost, we set this node as failed
285 | *
286 | * @param comm
287 | *
288 | * @throws java.io.IOException
289 | */
290 | @Override
291 | public synchronized void timeout(int comm) throws IOException
292 | {
293 | /* Get the node associated with this communication */
294 | Node n = this.messagesTransiting.get(comm);
295 |
296 | if (n == null)
297 | {
298 | return;
299 | }
300 |
301 | /* Mark this node as failed and inform the routing table that it is unresponsive */
302 | this.nodes.put(n, FAILED);
303 | this.localNode.getRoutingTable().setUnresponsiveContact(n);
304 | this.messagesTransiting.remove(comm);
305 |
306 | this.askNodesorFinish();
307 | }
308 |
309 | public List getFailedNodes()
310 | {
311 | List failedNodes = new ArrayList<>();
312 |
313 | for (Map.Entry e : this.nodes.entrySet())
314 | {
315 | if (e.getValue().equals(FAILED))
316 | {
317 | failedNodes.add(e.getKey());
318 | }
319 | }
320 |
321 | return failedNodes;
322 | }
323 | }
324 |
--------------------------------------------------------------------------------