From e2ca9326c994cd43f530a4c15f85377d267c6e6c Mon Sep 17 00:00:00 2001 From: Joshua Kissoon Date: Mon, 10 Mar 2014 13:45:13 +0530 Subject: [PATCH] Got system state storage and retrieval of state to work! --- src/kademlia/core/Kademlia.java | 55 +++++++--- src/kademlia/node/Node.java | 12 ++- src/kademlia/node/NodeId.java | 2 +- src/kademlia/routing/KadBucket.java | 12 +-- src/kademlia/routing/RoutingTable.java | 84 ++++++++++----- .../JsonRoutingTableSerializer.java | 100 ++++++++++++++++++ src/kademlia/serializer/JsonSerializer.java | 6 +- src/kademlia/tests/SaveStateTest.java | 6 +- 8 files changed, 223 insertions(+), 54 deletions(-) create mode 100644 src/kademlia/serializer/JsonRoutingTableSerializer.java diff --git a/src/kademlia/core/Kademlia.java b/src/kademlia/core/Kademlia.java index 45da8e2..c85d590 100644 --- a/src/kademlia/core/Kademlia.java +++ b/src/kademlia/core/Kademlia.java @@ -9,7 +9,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.net.InetAddress; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.NoSuchElementException; import java.util.Timer; @@ -26,6 +25,7 @@ import kademlia.operation.Operation; import kademlia.operation.KadRefreshOperation; import kademlia.operation.StoreOperation; import kademlia.routing.RoutingTable; +import kademlia.serializer.JsonRoutingTableSerializer; import kademlia.serializer.JsonSerializer; /** @@ -69,6 +69,7 @@ public class Kademlia * @param ownerId The Name of this node used for storage * @param localNode The Local Node for this Kad instance * @param udpPort The UDP port to use for routing messages + * @param dht The DHT for this instance * * @throws IOException If an error occurred while reading id or local map * from disk or a network error occurred while @@ -120,24 +121,40 @@ public class Kademlia * @return A Kademlia instance loaded from a stored state in a file * * @throws java.io.FileNotFoundException + * @throws java.lang.ClassNotFoundException * * @todo Boot up this Kademlia instance from a saved file state */ - public static void loadFromFile(String ownerId) throws FileNotFoundException, IOException, ClassNotFoundException + public static Kademlia loadFromFile(String ownerId) throws FileNotFoundException, IOException, ClassNotFoundException { - /* Setup the file in which we store the state */ - DataInputStream din = new DataInputStream(new FileInputStream(getStateStorageFolderName() + File.separator + ownerId + ".kns")); + DataInputStream din; - /* Read the UDP Port that this app is running on */ - Integer rPort = new JsonSerializer().read(din); + /** + * @section Read Basic Kad data + */ + din = new DataInputStream(new FileInputStream(getStateStorageFolderName(ownerId) + File.separator + "kad.kns")); + Kademlia ikad = new JsonSerializer().read(din); - /* Read the node state */ - // Node rN = new JsonSerializer().read(din); + /** + * @section Read the routing table + */ + din = new DataInputStream(new FileInputStream(getStateStorageFolderName(ownerId) + File.separator + "routingtable.kns")); + RoutingTable irtbl = new JsonRoutingTableSerializer().read(din); - /* Read the DHT */ - DHT rDht = new JsonSerializer().read(din); + /** + * @section Read the node state + */ + din = new DataInputStream(new FileInputStream(getStateStorageFolderName(ownerId) + File.separator + "node.kns")); + Node inode = new JsonSerializer().read(din); + inode.setRoutingTable(irtbl); - //return new Kademlia(ownerId, rN, rPort, rDht); + /** + * @section Read the DHT + */ + din = new DataInputStream(new FileInputStream(getStateStorageFolderName(ownerId) + File.separator + "dht.kns")); + DHT idht = new JsonSerializer().read(din); + System.out.println("Finished reading data."); + return new Kademlia(ownerId, inode, ikad.getPort(), idht); } /** @@ -242,12 +259,20 @@ public class Kademlia return this.ownerId; } + /** + * @return Integer The port on which this kad instance is running + */ + public int getPort() + { + return this.udpPort; + } + /** * Here we handle properly shutting down the Kademlia instance * * @throws java.io.FileNotFoundException */ - public void shutdown() throws FileNotFoundException, IOException, ClassNotFoundException + public void shutdown() throws FileNotFoundException, IOException { /* Shut down the server */ this.server.shutdown(); @@ -257,11 +282,7 @@ public class Kademlia { /* Save the system state */ this.saveKadState(); - } - - - /* Now we store the content locally in a file */ } /** @@ -292,7 +313,7 @@ public class Kademlia * This will cause a serialization recursion, and in turn a Stack Overflow */ dout = new DataOutputStream(new FileOutputStream(getStateStorageFolderName(this.ownerId) + File.separator + "routingtable.kns")); - new JsonSerializer().write(this.localNode.getRoutingTable(), dout); + new JsonRoutingTableSerializer().write(this.localNode.getRoutingTable(), dout); /** * @section Save the DHT diff --git a/src/kademlia/node/Node.java b/src/kademlia/node/Node.java index d68303e..9426559 100644 --- a/src/kademlia/node/Node.java +++ b/src/kademlia/node/Node.java @@ -24,7 +24,7 @@ public class Node implements Streamable private InetAddress inetAddress; private int port; - private transient final RoutingTable routingTable; + private transient RoutingTable routingTable; { @@ -119,6 +119,16 @@ public class Node implements Streamable return this.routingTable; } + /** + * Sets a new routing table to this node, mainly used when we retrieve the node from a saved state + * + * @param tbl The routing table to use + */ + public void setRoutingTable(RoutingTable tbl) + { + this.routingTable = tbl; + } + @Override public boolean equals(Object o) { diff --git a/src/kademlia/node/NodeId.java b/src/kademlia/node/NodeId.java index e3b7582..dd5691f 100644 --- a/src/kademlia/node/NodeId.java +++ b/src/kademlia/node/NodeId.java @@ -17,7 +17,7 @@ import kademlia.message.Streamable; public class NodeId implements Streamable { - public final static int ID_LENGTH = 160; + public final transient static int ID_LENGTH = 160; private byte[] keyBytes; /** diff --git a/src/kademlia/routing/KadBucket.java b/src/kademlia/routing/KadBucket.java index 925668a..733e5dc 100644 --- a/src/kademlia/routing/KadBucket.java +++ b/src/kademlia/routing/KadBucket.java @@ -1,8 +1,3 @@ -/** - * @author Joshua Kissoon - * @created 20140215 - * @desc A bucket in the Kademlia routing table - */ package kademlia.routing; import java.util.ArrayList; @@ -12,12 +7,17 @@ import java.util.Map; import kademlia.node.Node; import kademlia.node.NodeId; +/** + * A bucket in the Kademlia routing table + * + * @author Joshua Kissoon + * @created 20140215 + */ public class KadBucket implements Bucket { private final int depth; private final Map nodes; - { nodes = new HashMap<>(); diff --git a/src/kademlia/routing/RoutingTable.java b/src/kademlia/routing/RoutingTable.java index 069f6d1..61c9cb3 100644 --- a/src/kademlia/routing/RoutingTable.java +++ b/src/kademlia/routing/RoutingTable.java @@ -1,8 +1,3 @@ -/** - * @author Joshua Kissoon - * @created 20140215 - * @desc Implementation of a Kademlia routing table - */ package kademlia.routing; import java.util.ArrayList; @@ -10,26 +5,35 @@ import java.util.List; import kademlia.node.Node; import kademlia.node.NodeId; +/** + * Implementation of a Kademlia routing table + * + * @author Joshua Kissoon + * @created 20140215 + * + * @todo Make the KadBucket represent the Bucket interface + * @todo Change the code to reflect the bucket interface and not the specific KadBucket implementation + */ public class RoutingTable { - + private final Node localNode; // The current node - private final KadBucket[] buckets; - + private transient KadBucket[] buckets; + { buckets = new KadBucket[NodeId.ID_LENGTH]; // 160 buckets; 1 for each level in the tree } - + public RoutingTable(Node localNode) { this.localNode = localNode; /* Initialize all of the buckets to a specific depth */ - for (int i = 0; i < NodeId.ID_LENGTH; i++) - { - buckets[i] = new KadBucket(i); - } + this.initializeBuckets(); + + /* @todo Insert the local node */ + //this.insert(localNode); } /** @@ -37,10 +41,10 @@ public class RoutingTable * * @param n The contact to add */ - public void insert(Node n) + public final void insert(Node n) { /* bucketId is the distance between these nodes */ - int bucketId = this.localNode.getNodeId().getDistance(n.getNodeId()) - 1; + int bucketId = this.localNode.getNodeId().getDistance(n.getNodeId()); //System.out.println(this.localNode.getNodeId() + " Adding Node " + n.getNodeId() + " to bucket at depth: " + bucketId); @@ -53,7 +57,7 @@ public class RoutingTable * * @param n The node to remove */ - public void remove(Node n) + public final void remove(Node n) { /* Find the first set bit: how far this node is away from the contact node */ int bucketId = this.localNode.getNodeId().getDistance(n.getNodeId()); @@ -73,7 +77,7 @@ public class RoutingTable * * @return List A List of contacts closest to target */ - public List findClosest(NodeId target, int num) + public final List findClosest(NodeId target, int num) { List closest = new ArrayList<>(num); @@ -92,7 +96,7 @@ public class RoutingTable break; } } - + if (closest.size() >= num) { return closest; @@ -139,27 +143,57 @@ public class RoutingTable break; } } - + return closest; } /** * @return List A List of all Nodes in this RoutingTable */ - public List getAllNodes() + public final List getAllNodes() { List nodes = new ArrayList<>(); - + for (KadBucket b : this.buckets) { nodes.addAll(b.getNodes()); } - + return nodes; } + /** + * @return Bucket[] The buckets in this Kad Instance + */ + public final KadBucket[] getBuckets() + { + return this.buckets; + } + + /** + * Set the KadBuckets of this routing table, mainly used when retrieving saved state + * + * @param buckets + */ + public final void setBuckets(KadBucket[] buckets) + { + this.buckets = buckets; + } + + /** + * Initialize the kadBuckets to be empty + */ + public final void initializeBuckets() + { + this.buckets = new KadBucket[NodeId.ID_LENGTH]; + for (int i = 0; i < NodeId.ID_LENGTH; i++) + { + buckets[i] = new KadBucket(i); + } + } + @Override - public String toString() + public final String toString() { StringBuilder sb = new StringBuilder("\nPrinting Routing Table Started ***************** \n"); for (KadBucket b : this.buckets) @@ -177,8 +211,8 @@ public class RoutingTable } } sb.append("\nPrinting Routing Table Ended ******************** "); - + return sb.toString(); } - + } diff --git a/src/kademlia/serializer/JsonRoutingTableSerializer.java b/src/kademlia/serializer/JsonRoutingTableSerializer.java new file mode 100644 index 0000000..ceafbfd --- /dev/null +++ b/src/kademlia/serializer/JsonRoutingTableSerializer.java @@ -0,0 +1,100 @@ +package kademlia.serializer; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import kademlia.routing.RoutingTable; +import java.lang.reflect.Type; +import java.util.List; +import kademlia.node.Node; + +/** + * A KadSerializer that serializes routing tables to JSON format + * The generic serializer is not working for routing tables + * + * Why a RoutingTable specific serializer? + * The routing table structure: + * - RoutingTable + * -- Buckets[] + * --- Map + * ---- NodeId:KeyBytes + * ---- Node: NodeId, InetAddress, Port + * + * The above structure seems to be causing some problem for Gson, + * especially at the Map part. + * + * Solution + * - Make the Buckets[] transient + * - Simply store all Nodes in the serialized object + * - When reloading, re-add all nodes to the RoutingTable + * + * @author Joshua Kissoon + * + * @since 20140225 + */ +public class JsonRoutingTableSerializer implements KadSerializer +{ + + private final Gson gson; + + + { + gson = new Gson(); + } + + @Override + public void write(RoutingTable data, DataOutputStream out) throws IOException + { + try (JsonWriter writer = new JsonWriter(new OutputStreamWriter(out))) + { + writer.beginArray(); + + /* Write the basic RoutingTable */ + gson.toJson(data, RoutingTable.class, writer); + + /* Now Store the Nodes */ + Type collectionType = new TypeToken>() + { + }.getType(); + gson.toJson(data.getAllNodes(), collectionType, writer); + + writer.endArray(); + } + + } + + @Override + public RoutingTable read(DataInputStream in) throws IOException, ClassNotFoundException + { + try (DataInputStream din = new DataInputStream(in); + JsonReader reader = new JsonReader(new InputStreamReader(in))) + { + reader.beginArray(); + + /* Read the basic RoutingTable */ + RoutingTable tbl = gson.fromJson(reader, RoutingTable.class); + + /* Now get the nodes and add them back to the RoutingTable */ + Type collectionType = new TypeToken>() + { + }.getType(); + List nodes = gson.fromJson(reader, collectionType); + tbl.initializeBuckets(); + + for (Node n : nodes) + { + tbl.insert(n); + } + + reader.endArray(); + /* Read and return the Content*/ + return tbl; + } + } +} diff --git a/src/kademlia/serializer/JsonSerializer.java b/src/kademlia/serializer/JsonSerializer.java index 21addfb..8ad88f9 100644 --- a/src/kademlia/serializer/JsonSerializer.java +++ b/src/kademlia/serializer/JsonSerializer.java @@ -58,7 +58,11 @@ public class JsonSerializer implements KadSerializer String className = gson.fromJson(reader, String.class); /* Read and return the Content*/ - return gson.fromJson(reader, Class.forName(className)); + T ret = gson.fromJson(reader, Class.forName(className)); + + reader.endArray(); + + return ret; } } } diff --git a/src/kademlia/tests/SaveStateTest.java b/src/kademlia/tests/SaveStateTest.java index eb16b1a..e0ecd68 100644 --- a/src/kademlia/tests/SaveStateTest.java +++ b/src/kademlia/tests/SaveStateTest.java @@ -31,9 +31,9 @@ public class SaveStateTest System.out.println("\n\n\nShutting down Kad instance"); kad1.shutdown(); - //System.out.println("\n\n\nReloading down Kad instance from file"); - //Kademlia.loadFromFile("JoshuaK"); - //System.out.println(kad3); + System.out.println("\n\n\nReloading down Kad instance from file"); + Kademlia kad3 = Kademlia.loadFromFile("JoshuaK"); + System.out.println(kad3); } catch (Exception e) {