├── .gitignore ├── manifest.mf ├── src └── kademlia │ ├── simulations │ ├── Simulation.java │ ├── SimpleMessageTest.java │ ├── RefreshOperationTest.java │ ├── RoutingTableSimulation.java │ ├── ContentSendingTest.java │ ├── ContentUpdatingTest.java │ ├── SaveStateTest2.java │ ├── DHTContentImpl.java │ ├── AutoRefreshOperation2.java │ ├── SaveStateTest.java │ ├── NodeConnectionTest.java │ ├── AutoRefreshOperation.java │ └── RoutingTableStateTesting.java │ ├── message │ ├── Message.java │ ├── SimpleReceiver.java │ ├── KademliaMessageFactory.java │ ├── Receiver.java │ ├── ConnectMessage.java │ ├── AcknowledgeMessage.java │ ├── Streamable.java │ ├── ConnectReceiver.java │ ├── SimpleMessage.java │ ├── StoreContentReceiver.java │ ├── NodeLookupMessage.java │ ├── ContentLookupMessage.java │ ├── NodeLookupReceiver.java │ ├── StoreContentMessage.java │ ├── ContentMessage.java │ ├── NodeReplyMessage.java │ ├── ContentLookupReceiver.java │ └── MessageFactory.java │ ├── exceptions │ ├── ContentExistException.java │ ├── KadServerDownException.java │ ├── ContentNotFoundException.java │ ├── RoutingException.java │ └── UnknownMessageException.java │ ├── operation │ ├── Operation.java │ ├── PingOperation.java │ ├── KadRefreshOperation.java │ ├── BucketRefreshOperation.java │ ├── StoreOperation.java │ ├── ContentRefreshOperation.java │ ├── ConnectOperation.java │ └── NodeLookupOperation.java │ ├── Todo │ ├── dht │ ├── KademliaStorageEntry.java │ ├── JKademliaStorageEntry.java │ ├── KademliaStorageEntryMetadata.java │ ├── KadContent.java │ ├── GetParameter.java │ ├── KademliaDHT.java │ ├── StorageEntryMetadata.java │ ├── StoredContentManager.java │ └── DHT.java │ ├── routing │ ├── ContactLastSeenComparator.java │ ├── KademliaBucket.java │ ├── KademliaRoutingTable.java │ ├── Contact.java │ ├── JKademliaRoutingTable.java │ └── JKademliaBucket.java │ ├── util │ ├── serializer │ │ ├── KadSerializer.java │ │ ├── JsonSerializer.java │ │ ├── JsonDHTSerializer.java │ │ └── JsonRoutingTableSerializer.java │ ├── RouteLengthChecker.java │ └── HashCalculator.java │ ├── node │ ├── KeyComparator.java │ ├── Node.java │ └── KademliaId.java │ ├── KadConfiguration.java │ ├── KadStatistician.java │ ├── DefaultConfiguration.java │ ├── KademliaNode.java │ └── Statistician.java ├── nbproject ├── genfiles.properties ├── project.xml └── project.properties ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | /build/ -------------------------------------------------------------------------------- /manifest.mf: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | X-COMMENT: Main-Class will be added automatically by build 3 | 4 | -------------------------------------------------------------------------------- /src/kademlia/simulations/Simulation.java: -------------------------------------------------------------------------------- 1 | package kademlia.simulations; 2 | 3 | /** 4 | * A class that specifies the structure for simulations. 5 | * 6 | * @author Joshua Kissoon 7 | * @since 8 | */ 9 | public interface Simulation 10 | { 11 | 12 | /** 13 | * Calling this method runs the simulation 14 | */ 15 | public void runSimulation(); 16 | } 17 | -------------------------------------------------------------------------------- /src/kademlia/message/Message.java: -------------------------------------------------------------------------------- 1 | package kademlia.message; 2 | 3 | public interface Message extends Streamable 4 | { 5 | 6 | /** 7 | * The unique code for the message type, used to differentiate all messages 8 | * from each other. Since this is of byte type there can 9 | * be at most 256 different message types. 10 | * 11 | * @return byte A unique code representing the message type 12 | * */ 13 | public byte code(); 14 | } 15 | -------------------------------------------------------------------------------- /src/kademlia/exceptions/ContentExistException.java: -------------------------------------------------------------------------------- 1 | package kademlia.exceptions; 2 | 3 | /** 4 | * An exception used to indicate that a content already exist on the DHT 5 | * 6 | * @author Joshua Kissoon 7 | * @created 20140322 8 | */ 9 | public class ContentExistException extends Exception 10 | { 11 | 12 | public ContentExistException() 13 | { 14 | super(); 15 | } 16 | 17 | public ContentExistException(String message) 18 | { 19 | super(message); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/kademlia/exceptions/KadServerDownException.java: -------------------------------------------------------------------------------- 1 | package kademlia.exceptions; 2 | 3 | /** 4 | * An exception to be thrown whenever the Kad Server is down 5 | * 6 | * @author Joshua Kissoon 7 | * @created 20140428 8 | */ 9 | public class KadServerDownException extends RoutingException 10 | { 11 | 12 | public KadServerDownException() 13 | { 14 | super(); 15 | } 16 | 17 | public KadServerDownException(String message) 18 | { 19 | super(message); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /nbproject/genfiles.properties: -------------------------------------------------------------------------------- 1 | build.xml.data.CRC32=7e563d6e 2 | build.xml.script.CRC32=c3cd04bd 3 | build.xml.stylesheet.CRC32=8064a381@1.68.1.46 4 | # This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. 5 | # Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. 6 | nbproject/build-impl.xml.data.CRC32=7e563d6e 7 | nbproject/build-impl.xml.script.CRC32=934ae712 8 | nbproject/build-impl.xml.stylesheet.CRC32=876e7a8f@1.74.1.48 9 | -------------------------------------------------------------------------------- /src/kademlia/exceptions/ContentNotFoundException.java: -------------------------------------------------------------------------------- 1 | package kademlia.exceptions; 2 | 3 | /** 4 | * An exception used to indicate that a content does not exist on the DHT 5 | * 6 | * @author Joshua Kissoon 7 | * @created 20140322 8 | */ 9 | public class ContentNotFoundException extends Exception 10 | { 11 | 12 | public ContentNotFoundException() 13 | { 14 | super(); 15 | } 16 | 17 | public ContentNotFoundException(String message) 18 | { 19 | super(message); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/kademlia/exceptions/RoutingException.java: -------------------------------------------------------------------------------- 1 | package kademlia.exceptions; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * An exception to be thrown whenever there is a routing problem 7 | * 8 | * @author Joshua Kissoon 9 | * @created 20140219 10 | */ 11 | public class RoutingException extends IOException 12 | { 13 | 14 | public RoutingException() 15 | { 16 | super(); 17 | } 18 | 19 | public RoutingException(String message) 20 | { 21 | super(message); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/kademlia/exceptions/UnknownMessageException.java: -------------------------------------------------------------------------------- 1 | package kademlia.exceptions; 2 | 3 | /** 4 | * An exception used to indicate an unknown message type or communication identifier 5 | * 6 | * @author Joshua Kissoon 7 | * @created 20140219 8 | */ 9 | public class UnknownMessageException extends RuntimeException 10 | { 11 | 12 | public UnknownMessageException() 13 | { 14 | super(); 15 | } 16 | 17 | public UnknownMessageException(String message) 18 | { 19 | super(message); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/kademlia/operation/Operation.java: -------------------------------------------------------------------------------- 1 | package kademlia.operation; 2 | 3 | import java.io.IOException; 4 | import kademlia.exceptions.RoutingException; 5 | 6 | /** 7 | * An operation in the Kademlia routing protocol 8 | * 9 | * @author Joshua Kissoon 10 | * @created 20140218 11 | */ 12 | public interface Operation 13 | { 14 | 15 | /** 16 | * Starts an operation and returns when the operation is finished 17 | * 18 | * @throws kademlia.exceptions.RoutingException 19 | */ 20 | public void execute() throws IOException, RoutingException; 21 | } 22 | -------------------------------------------------------------------------------- /nbproject/project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | org.netbeans.modules.java.j2seproject 4 | 5 | 6 | Kademlia 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/kademlia/Todo: -------------------------------------------------------------------------------- 1 | # What's left to add to the implementation 2 | 3 | 1. Implement the "Optimized Contact Accounting" features as mentioned in the paper 4 | 2. Implement the "Accelerated Lookups" featured as described in the paper 5 | 3. Add a setup file on how to setup Kademlia and run it. 6 | 7 | 8 | Next Steps: 9 | - Write Unit Tests 10 | - Improve multi-threading system 11 | -- I think server is slow in handling requests because the listen method runs in a single thread. 12 | --- Listen() method should create a new thread to handle every incoming request. 13 | - KadStatistician 14 | -- Let the statistician keep track of failed get requests! -------------------------------------------------------------------------------- /src/kademlia/message/SimpleReceiver.java: -------------------------------------------------------------------------------- 1 | package kademlia.message; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * Default receiver if none other is called 7 | * 8 | * @author Joshua Kissoon 9 | * @created 20140202 10 | */ 11 | public class SimpleReceiver implements Receiver 12 | { 13 | 14 | @Override 15 | public void receive(Message incoming, int conversationId) 16 | { 17 | //System.out.println("Received message: " + incoming); 18 | } 19 | 20 | @Override 21 | public void timeout(int conversationId) throws IOException 22 | { 23 | //System.out.println("SimpleReceiver message timeout."); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/kademlia/dht/KademliaStorageEntry.java: -------------------------------------------------------------------------------- 1 | package kademlia.dht; 2 | 3 | /** 4 | * A StorageEntry interface for the storage entry class used to store a content on the DHT 5 | * 6 | * @author Joshua Kissoon 7 | * @since 20140523 8 | */ 9 | public interface KademliaStorageEntry 10 | { 11 | 12 | /** 13 | * Add the content to the storage entry 14 | * 15 | * @param data The content data in byte[] format 16 | */ 17 | public void setContent(final byte[] data); 18 | 19 | /** 20 | * Get the content from this storage entry 21 | * 22 | * @return The content in byte format 23 | */ 24 | public byte[] getContent(); 25 | 26 | /** 27 | * Get the metadata for this storage entry 28 | * 29 | * @return the storage entry metadata 30 | */ 31 | public KademliaStorageEntryMetadata getContentMetadata(); 32 | } 33 | -------------------------------------------------------------------------------- /src/kademlia/routing/ContactLastSeenComparator.java: -------------------------------------------------------------------------------- 1 | package kademlia.routing; 2 | 3 | import java.util.Comparator; 4 | 5 | /** 6 | * A Comparator to compare 2 contacts by their last seen time 7 | * 8 | * @author Joshua Kissoon 9 | * @since 20140426 10 | */ 11 | public class ContactLastSeenComparator implements Comparator 12 | { 13 | 14 | /** 15 | * Compare two contacts to determine their order in the Bucket, 16 | * Contacts are ordered by their last seen timestamp. 17 | * 18 | * @param c1 Contact 1 19 | * @param c2 Contact 2 20 | */ 21 | @Override 22 | public int compare(Contact c1, Contact c2) 23 | { 24 | if (c1.getNode().equals(c2.getNode())) 25 | { 26 | return 0; 27 | } 28 | else 29 | { 30 | /* We may have 2 different contacts with same last seen values so we can't return 0 here */ 31 | return c1.lastSeen() > c2.lastSeen() ? 1 : -1; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/kademlia/simulations/SimpleMessageTest.java: -------------------------------------------------------------------------------- 1 | package kademlia.simulations; 2 | 3 | import java.io.IOException; 4 | import kademlia.JKademliaNode; 5 | import kademlia.message.SimpleMessage; 6 | import kademlia.node.KademliaId; 7 | import kademlia.message.SimpleReceiver; 8 | 9 | /** 10 | * Test 1: Try sending a simple message between nodes 11 | * 12 | * @author Joshua Kissoon 13 | * @created 20140218 14 | */ 15 | public class SimpleMessageTest 16 | { 17 | 18 | public static void main(String[] args) 19 | { 20 | try 21 | { 22 | JKademliaNode kad1 = new JKademliaNode("Joshua", new KademliaId("12345678901234567890"), 7574); 23 | JKademliaNode kad2 = new JKademliaNode("Crystal", new KademliaId("12345678901234567891"), 7572); 24 | 25 | kad1.getServer().sendMessage(kad2.getNode(), new SimpleMessage("Some Message"), new SimpleReceiver()); 26 | } 27 | catch (IOException e) 28 | { 29 | e.printStackTrace(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/kademlia/message/KademliaMessageFactory.java: -------------------------------------------------------------------------------- 1 | package kademlia.message; 2 | 3 | import java.io.DataInputStream; 4 | import java.io.IOException; 5 | import kademlia.KadServer; 6 | 7 | /** 8 | * A factory that handles creating messages and receivers 9 | * 10 | * @author Joshua Kissoon 11 | * @since 20140523 12 | */ 13 | public interface KademliaMessageFactory 14 | { 15 | 16 | /** 17 | * Method that creates a message based on the code and input stream 18 | * 19 | * @param code The message code 20 | * @param in An input stream with the message data 21 | * 22 | * @return A message 23 | * 24 | * @throws java.io.IOException 25 | */ 26 | public Message createMessage(byte code, DataInputStream in) throws IOException; 27 | 28 | /** 29 | * Method that returns a receiver to handle a specific type of message 30 | * 31 | * @param code The message code 32 | * @param server 33 | * 34 | * @return A receiver 35 | */ 36 | public Receiver createReceiver(byte code, KadServer server); 37 | } 38 | -------------------------------------------------------------------------------- /src/kademlia/message/Receiver.java: -------------------------------------------------------------------------------- 1 | package kademlia.message; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * A receiver waits for incoming messages and perform some action when the message is received 7 | * 8 | * @author Joshua Kissoon 9 | * @created 20140218 10 | */ 11 | public interface Receiver 12 | { 13 | 14 | /** 15 | * Message is received, now handle it 16 | * 17 | * @param conversationId The ID of this conversation, used for further conversations 18 | * @param incoming The incoming 19 | * 20 | * @throws java.io.IOException 21 | */ 22 | public void receive(Message incoming, int conversationId) throws IOException; 23 | 24 | /** 25 | * If no reply is received in MessageServer.TIMEOUT seconds for the 26 | * message with communication id comm, the MessageServer calls this method 27 | * 28 | * @param conversationId The conversation ID of this communication 29 | * 30 | * @throws IOException if an I/O error occurs 31 | * */ 32 | public void timeout(int conversationId) throws IOException; 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Joshua Kissoon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/kademlia/util/serializer/KadSerializer.java: -------------------------------------------------------------------------------- 1 | package kademlia.util.serializer; 2 | 3 | import java.io.DataInputStream; 4 | import java.io.DataOutputStream; 5 | import java.io.IOException; 6 | 7 | /** 8 | * A Serializer is used to transform data to and from a specified form. 9 | * 10 | * Here we define the structure of any Serializer used in Kademlia 11 | * 12 | * @author Joshua Kissoon 13 | * @param The type of content being serialized 14 | * 15 | * @since 20140225 16 | */ 17 | public interface KadSerializer 18 | { 19 | 20 | /** 21 | * Write a KadContent to a DataOutput stream 22 | * 23 | * @param data The data to write 24 | * @param out The output Stream to write to 25 | * 26 | * @throws java.io.IOException 27 | */ 28 | public void write(T data, DataOutputStream out) throws IOException; 29 | 30 | /** 31 | * Read data of type T from a DataInput Stream 32 | * 33 | * @param in The InputStream to read the data from 34 | * 35 | * @return T Data of type T 36 | * 37 | * @throws java.io.IOException 38 | * @throws java.lang.ClassNotFoundException 39 | */ 40 | public T read(DataInputStream in) throws IOException, ClassNotFoundException; 41 | } 42 | -------------------------------------------------------------------------------- /src/kademlia/node/KeyComparator.java: -------------------------------------------------------------------------------- 1 | package kademlia.node; 2 | 3 | import java.math.BigInteger; 4 | import java.util.Comparator; 5 | 6 | /** 7 | * A Comparator to compare 2 keys to a given key 8 | * 9 | * @author Joshua Kissoon 10 | * @since 20140322 11 | */ 12 | public class KeyComparator implements Comparator 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 | --------------------------------------------------------------------------------