diff --git a/cnc/src/main/java/org/btcollider/cnc/CnC.java b/cnc/src/main/java/org/btcollider/cnc/CnC.java index 2f50b0b..3a42ea9 100644 --- a/cnc/src/main/java/org/btcollider/cnc/CnC.java +++ b/cnc/src/main/java/org/btcollider/cnc/CnC.java @@ -3,6 +3,7 @@ package org.btcollider.cnc; import org.btcollider.cnc.comm.CommException; import org.btcollider.cnc.comm.CommServer; import org.btcollider.cnc.keysrv.KeyServer; +import org.btcollider.cnc.keysrv.impl.keytree.KeyTreeServer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,8 +19,8 @@ public class CnC { public static final int MAX_BITS = 62; public static void main(String[] args) { - KeyServer.init(54, 27, 18000000000l); - CommServer server = new CommServer(PORT); + KeyServer kts = new KeyTreeServer(54, 27, 18000000000l); + CommServer server = new CommServer(PORT, kts); try { server.listen(); } catch (CommException e) { diff --git a/cnc/src/main/java/org/btcollider/cnc/comm/ClientWorker.java b/cnc/src/main/java/org/btcollider/cnc/comm/ClientWorker.java index 7ca1804..6e59ac6 100644 --- a/cnc/src/main/java/org/btcollider/cnc/comm/ClientWorker.java +++ b/cnc/src/main/java/org/btcollider/cnc/comm/ClientWorker.java @@ -18,11 +18,13 @@ public class ClientWorker implements Runnable { final Logger log = LoggerFactory.getLogger(ClientWorker.class); private Socket clientSocket; + private KeyServer keyServer; private BufferedReader in; private PrintWriter out; - public ClientWorker(Socket clientSocket) { + public ClientWorker(Socket clientSocket, KeyServer keyServer) { this.clientSocket = clientSocket; + this.keyServer = keyServer; } @Override @@ -80,12 +82,11 @@ public class ClientWorker implements Runnable { } private void sendWork() { - KeyServer ks = KeyServer.getInstance(); KeyRange kr; - synchronized (ks) { - kr = ks.getRange(); - ks.setInWork(kr); + synchronized (keyServer) { + kr = keyServer.getRange(); + keyServer.setInWork(kr); } if (kr == null) { @@ -104,8 +105,8 @@ public class ClientWorker implements Runnable { String[] result = in.readLine().split(" "); KeyRange kr = new KeyRange(Long.parseLong(result[0], 16), Long.parseLong(result[1], 16)); - synchronized (KeyServer.getInstance()) { - KeyServer.getInstance().setSearched(kr); + synchronized (keyServer) { + keyServer.setSearched(kr); } log.debug("Keyrange {} searched", kr); diff --git a/cnc/src/main/java/org/btcollider/cnc/comm/CommServer.java b/cnc/src/main/java/org/btcollider/cnc/comm/CommServer.java index 823cc02..d40ed80 100644 --- a/cnc/src/main/java/org/btcollider/cnc/comm/CommServer.java +++ b/cnc/src/main/java/org/btcollider/cnc/comm/CommServer.java @@ -5,6 +5,7 @@ import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import org.btcollider.cnc.keysrv.KeyServer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,8 +15,9 @@ public class CommServer { private int port; private ServerSocket serverSocket; + private KeyServer keyServer; - public CommServer(int port) { + public CommServer(int port, KeyServer keyServer) { this.port = port; } @@ -30,7 +32,7 @@ public class CommServer { try { Socket socket = serverSocket.accept(); log.debug("Got connection from " + socket.getInetAddress()); - es.execute(new ClientWorker(socket)); + es.execute(new ClientWorker(socket, keyServer)); } catch (IOException e) { log.error("Couldn't establish connections on socket", e); diff --git a/cnc/src/main/java/org/btcollider/cnc/keysrv/KeyServer.java b/cnc/src/main/java/org/btcollider/cnc/keysrv/KeyServer.java index 9bfd1df..cef3e7d 100644 --- a/cnc/src/main/java/org/btcollider/cnc/keysrv/KeyServer.java +++ b/cnc/src/main/java/org/btcollider/cnc/keysrv/KeyServer.java @@ -1,277 +1,36 @@ package org.btcollider.cnc.keysrv; -import java.util.BitSet; -import java.util.LinkedList; -import java.util.Queue; -import java.util.Random; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import org.btcollider.cnc.CnC; import org.btcollider.cnc.dto.KeyRange; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -public class KeyServer { - final Logger log = LoggerFactory.getLogger(KeyServer.class); - - private static KeyServer instance; - - private KeyTree root; - private int index; - private int depth; - - protected KeyServer(int index, int depth, long maxWorkSpan) { - assert Math.pow(2, CnC.MAX_BITS) <= Long.MAX_VALUE; - assert index < CnC.MAX_BITS && depth <= CnC.MAX_BITS; - - this.depth = depth; - this.index = index; - this.root = generateKeyTree(depth); - - log.info("Starting KeyTree concierge"); - ScheduledExecutorService es = Executors.newSingleThreadScheduledExecutor(); - es.scheduleWithFixedDelay(new KTConcierge(root, maxWorkSpan), 0, 30, TimeUnit.SECONDS); - } - - public static void init(int index, int depth, long maxWorkSpan) { - if (instance == null) - instance = new KeyServer(index, depth, maxWorkSpan); - else - throw new IllegalAccessError("KeyServer already initialized"); - } - - public static KeyServer getInstance() { - if (instance == null) - throw new IllegalAccessError("KeyServer is not initialized"); - return instance; - } - - public KeyRange getRange() { - BitSet keyStart = new BitSet(CnC.MAX_BITS); - - if(!recCollectKey(root, keyStart, index)) return null; - - BitSet keyEnd = new BitSet(CnC.MAX_BITS); - keyEnd.or(keyStart); // set keyEnd to keyStart - keyEnd.flip(0, index + 1 - depth); // reverse last bits - - KeyRange kr = new KeyRange(keyStart, keyEnd); - - return kr; - } - - public void setSearched(Long from, Long to) { - assert from >= 0 && to >= 0 && from <= to; - - long step = (long) Math.pow(2, ((index + 1) - depth)); - - for (long i = from; i < to; i += step) { - BitSet searchedKey = BitSet.valueOf(new long[] { i }); - markSearched(searchedKey); - } - } - - public void setSearched(KeyRange keyRange) { - markSearched(BitSet.valueOf(new long[] { keyRange.getStart() })); - } - - public void setInWork(KeyRange keyRange) { - if (keyRange == null) return; - - markInWork(BitSet.valueOf(new long[] { keyRange.getStart() })); - } - - public String printDot() { - StringBuffer buffer = new StringBuffer(); - buffer.append("strict graph { \n"); - - Queue frontier = new LinkedList<>(); - frontier.add(root); - - while (!frontier.isEmpty()) { - KeyTree node = frontier.poll(); - buffer.append(dotNodeName(node) + ";\n"); - - if (node.getParent() != null) { - buffer.append(dotNodeName(node.getParent()) + " -- " + dotNodeName(node) + ";\n"); - } - - if (node.getLeft() != null) - frontier.add(node.getLeft()); - if (node.getRight() != null) - frontier.add(node.getRight()); - } - - buffer.append("\n}"); - return buffer.toString(); - } - - private String dotNodeName(KeyTree node) { - String nodeName = "\"" + node.hashCode() + "_" + node.getValue(); - - if (node.isSearched()) - nodeName += "_s"; - if (node.inWork()) - nodeName += "_w"; - - nodeName += "\""; - - return nodeName; - } - - private void markInWork(BitSet key) { - KeyTree keyTreePointer = root; - - int curIdx = key.length() - 2; // ignore root bit - while (curIdx >= 0) { - if (key.get(curIdx)) - keyTreePointer = keyTreePointer.getRight(); - else - keyTreePointer = keyTreePointer.getLeft(); - - if (keyTreePointer.inWork()) { - break; // path already marked as inWork - } - - if (keyTreePointer.isLeaf()) { // end of tree reached - keyTreePointer.setInWork(); - recMarkInWorkParents(keyTreePointer); - break; - } - - curIdx--; - } - } - - private void recMarkInWorkParents(KeyTree child) { - if (child.getParent() == null) - return; - - KeyTree parent = child.getParent(); - - if (!parent.getLeft().inWork() || !parent.getRight().inWork()) { - return; - } - - // Mark parent inWork too, check next level - parent.setInWork(Long.max(parent.getLeft().inWorkSince(), parent.getRight().inWorkSince())); - - recMarkInWorkParents(parent); - } - - private void markSearched(BitSet key) { - KeyTree keyTreePointer = root; - - int curIdx = key.length() - 2; // ignore root bit - while (curIdx >= 0) { - if (key.get(curIdx)) - keyTreePointer = keyTreePointer.getRight(); - else - keyTreePointer = keyTreePointer.getLeft(); - - if (keyTreePointer.isSearched()) - break; // path already marked as searched - - if (keyTreePointer.isLeaf()) { // end of tree reached - keyTreePointer.setSearched(true); - recMarkSearchedParents(keyTreePointer); - break; - } - - curIdx--; - } - } - - private void recMarkSearchedParents(KeyTree child) { - if (child.getParent() == null) - return; - - KeyTree parent = child.getParent(); - - if (!parent.getLeft().isSearched() || !parent.getRight().isSearched()) { - return; - } - - // Mark parent searched too, check next level - parent.setSearched(true); - recMarkSearchedParents(parent); - } - - private KeyTree generateKeyTree(int depth) { - assert depth > 0; - - KeyTree root = new KeyTree(true, null); - log.info("Starting recGenTree"); - recGenKeyTree(root, depth - 1); - log.info("Ending recGenTree"); - - return root; - } - - private void recGenKeyTree(KeyTree kt, int depth) { - assert depth >= 0; - - if (depth == 0) - return; - - KeyTree left = new KeyTree(false, kt); - KeyTree right = new KeyTree(true, kt); - - kt.setLeft(left); - kt.setRight(right); - - recGenKeyTree(left, depth - 1); - recGenKeyTree(right, depth - 1); - } - - private boolean recCollectKey(KeyTree kt, BitSet keyStart, int curIndex) { - assert curIndex > 0; - - keyStart.set(curIndex, kt.getValue()); - - KeyTree left = kt.getLeft(); - KeyTree right = kt.getRight(); - - // Finish criteria - if (left.isLeaf() || right.isLeaf()) { - assert left.isLeaf() && right.isLeaf(); // fully balanced binary tree by construction - - - if (!right.isFree() && ! left.isFree()) return false; - - // No choice finish - if (!right.isFree()) - keyStart.set(curIndex - 1, left.getValue()); - else if (!left.isFree()) - keyStart.set(curIndex - 1, right.getValue()); - else if (left.isFree() && right.isFree()) { // Randomized finish - Random r = new Random(); - boolean leftFirst = r.nextBoolean(); - if(leftFirst) keyStart.set(curIndex - 1, left.getValue()); - else keyStart.set(curIndex - 1, right.getValue()); - } - - return true; - } - - // No choice depth-first traversal - if (!left.isFree()) - return recCollectKey(right, keyStart, curIndex - 1); - else if (!right.isFree()) - return recCollectKey(left, keyStart, curIndex - 1); - else { // Randomized depth-first traversal - Random r = new Random(); - boolean leftFirst = r.nextBoolean(); - if (leftFirst) - return recCollectKey(left, keyStart, curIndex - 1); - else - return recCollectKey(right, keyStart, curIndex - 1); - } - } - - protected KeyTree getKeyTree() { - return this.root; - } +public interface KeyServer { + /** + * Gets a free (= not setInWork and not setSearched) + * KeyRange from the pool of possible key ranges + * + * @return A free KeyRange or null if none such exists + */ + KeyRange getRange(); + + /** + * Sets a KeyRange as being currently worked on. + * + * As long as a KeyRange is not setSearched, there + * is no guarantee that an inWork KeyRange will be finished + * in any specific time frame. + * The KeyServer has to clean up dangling inWorks in good + * faith. Unexpected setSearched of already cleaned-up inWorks + * must not be of any harm. + * + * @param keyRange The KeyRange to be set as being worked on, not null + */ + void setInWork(KeyRange keyRange); + + /** + * Sets a KeyRange as being searched, finalizing this range + * A searched KeyRange need not be processed any more + * + * @param keyRange The KeyRange to be set as searched, not null + */ + void setSearched(KeyRange keyRange); + } diff --git a/cnc/src/main/java/org/btcollider/cnc/keysrv/KTConcierge.java b/cnc/src/main/java/org/btcollider/cnc/keysrv/impl/keytree/KTConcierge.java similarity index 90% rename from cnc/src/main/java/org/btcollider/cnc/keysrv/KTConcierge.java rename to cnc/src/main/java/org/btcollider/cnc/keysrv/impl/keytree/KTConcierge.java index bf6ee51..822cd4a 100644 --- a/cnc/src/main/java/org/btcollider/cnc/keysrv/KTConcierge.java +++ b/cnc/src/main/java/org/btcollider/cnc/keysrv/impl/keytree/KTConcierge.java @@ -1,10 +1,10 @@ -package org.btcollider.cnc.keysrv; +package org.btcollider.cnc.keysrv.impl.keytree; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class KTConcierge implements Runnable { - final Logger log = LoggerFactory.getLogger(KeyServer.class); + final Logger log = LoggerFactory.getLogger(KeyTreeServer.class); private KeyTree keyTree; private long maxWorkSpan; diff --git a/cnc/src/main/java/org/btcollider/cnc/keysrv/KeyTree.java b/cnc/src/main/java/org/btcollider/cnc/keysrv/impl/keytree/KeyTree.java similarity index 96% rename from cnc/src/main/java/org/btcollider/cnc/keysrv/KeyTree.java rename to cnc/src/main/java/org/btcollider/cnc/keysrv/impl/keytree/KeyTree.java index e844067..6154c7e 100644 --- a/cnc/src/main/java/org/btcollider/cnc/keysrv/KeyTree.java +++ b/cnc/src/main/java/org/btcollider/cnc/keysrv/impl/keytree/KeyTree.java @@ -1,4 +1,4 @@ -package org.btcollider.cnc.keysrv; +package org.btcollider.cnc.keysrv.impl.keytree; public class KeyTree { private boolean value; diff --git a/cnc/src/main/java/org/btcollider/cnc/keysrv/impl/keytree/KeyTreeServer.java b/cnc/src/main/java/org/btcollider/cnc/keysrv/impl/keytree/KeyTreeServer.java new file mode 100644 index 0000000..f23f85c --- /dev/null +++ b/cnc/src/main/java/org/btcollider/cnc/keysrv/impl/keytree/KeyTreeServer.java @@ -0,0 +1,263 @@ +package org.btcollider.cnc.keysrv.impl.keytree; + +import java.util.BitSet; +import java.util.LinkedList; +import java.util.Queue; +import java.util.Random; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.btcollider.cnc.CnC; +import org.btcollider.cnc.dto.KeyRange; +import org.btcollider.cnc.keysrv.KeyServer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class KeyTreeServer implements KeyServer { + final Logger log = LoggerFactory.getLogger(KeyTreeServer.class); + + private KeyTree root; + private int index; + private int depth; + + public KeyTreeServer(int index, int depth, long maxWorkSpan) { + assert Math.pow(2, CnC.MAX_BITS) <= Long.MAX_VALUE; + assert index < CnC.MAX_BITS && depth <= CnC.MAX_BITS; + + this.depth = depth; + this.index = index; + this.root = generateKeyTree(depth); + + log.info("Starting KeyTree concierge"); + ScheduledExecutorService es = Executors.newSingleThreadScheduledExecutor(); + es.scheduleWithFixedDelay(new KTConcierge(root, maxWorkSpan), 0, 30, TimeUnit.SECONDS); + } + + public KeyRange getRange() { + BitSet keyStart = new BitSet(CnC.MAX_BITS); + + if(!recCollectKey(root, keyStart, index)) return null; + + BitSet keyEnd = new BitSet(CnC.MAX_BITS); + keyEnd.or(keyStart); // set keyEnd to keyStart + keyEnd.flip(0, index + 1 - depth); // reverse last bits + + KeyRange kr = new KeyRange(keyStart, keyEnd); + + return kr; + } + + public void setSearched(Long from, Long to) { + assert from >= 0 && to >= 0 && from <= to; + + long step = (long) Math.pow(2, ((index + 1) - depth)); + + for (long i = from; i < to; i += step) { + BitSet searchedKey = BitSet.valueOf(new long[] { i }); + markSearched(searchedKey); + } + } + + public void setSearched(KeyRange keyRange) { + markSearched(BitSet.valueOf(new long[] { keyRange.getStart() })); + } + + public void setInWork(KeyRange keyRange) { + if (keyRange == null) return; + + markInWork(BitSet.valueOf(new long[] { keyRange.getStart() })); + } + + public String printDot() { + StringBuffer buffer = new StringBuffer(); + buffer.append("strict graph { \n"); + + Queue frontier = new LinkedList<>(); + frontier.add(root); + + while (!frontier.isEmpty()) { + KeyTree node = frontier.poll(); + buffer.append(dotNodeName(node) + ";\n"); + + if (node.getParent() != null) { + buffer.append(dotNodeName(node.getParent()) + " -- " + dotNodeName(node) + ";\n"); + } + + if (node.getLeft() != null) + frontier.add(node.getLeft()); + if (node.getRight() != null) + frontier.add(node.getRight()); + } + + buffer.append("\n}"); + return buffer.toString(); + } + + private String dotNodeName(KeyTree node) { + String nodeName = "\"" + node.hashCode() + "_" + node.getValue(); + + if (node.isSearched()) + nodeName += "_s"; + if (node.inWork()) + nodeName += "_w"; + + nodeName += "\""; + + return nodeName; + } + + private void markInWork(BitSet key) { + KeyTree keyTreePointer = root; + + int curIdx = key.length() - 2; // ignore root bit + while (curIdx >= 0) { + if (key.get(curIdx)) + keyTreePointer = keyTreePointer.getRight(); + else + keyTreePointer = keyTreePointer.getLeft(); + + if (keyTreePointer.inWork()) { + break; // path already marked as inWork + } + + if (keyTreePointer.isLeaf()) { // end of tree reached + keyTreePointer.setInWork(); + recMarkInWorkParents(keyTreePointer); + break; + } + + curIdx--; + } + } + + private void recMarkInWorkParents(KeyTree child) { + if (child.getParent() == null) + return; + + KeyTree parent = child.getParent(); + + if (!parent.getLeft().inWork() || !parent.getRight().inWork()) { + return; + } + + // Mark parent inWork too, check next level + parent.setInWork(Long.max(parent.getLeft().inWorkSince(), parent.getRight().inWorkSince())); + + recMarkInWorkParents(parent); + } + + private void markSearched(BitSet key) { + KeyTree keyTreePointer = root; + + int curIdx = key.length() - 2; // ignore root bit + while (curIdx >= 0) { + if (key.get(curIdx)) + keyTreePointer = keyTreePointer.getRight(); + else + keyTreePointer = keyTreePointer.getLeft(); + + if (keyTreePointer.isSearched()) + break; // path already marked as searched + + if (keyTreePointer.isLeaf()) { // end of tree reached + keyTreePointer.setSearched(true); + recMarkSearchedParents(keyTreePointer); + break; + } + + curIdx--; + } + } + + private void recMarkSearchedParents(KeyTree child) { + if (child.getParent() == null) + return; + + KeyTree parent = child.getParent(); + + if (!parent.getLeft().isSearched() || !parent.getRight().isSearched()) { + return; + } + + // Mark parent searched too, check next level + parent.setSearched(true); + recMarkSearchedParents(parent); + } + + private KeyTree generateKeyTree(int depth) { + assert depth > 0; + + KeyTree root = new KeyTree(true, null); + log.info("Starting recGenTree"); + recGenKeyTree(root, depth - 1); + log.info("Ending recGenTree"); + + return root; + } + + private void recGenKeyTree(KeyTree kt, int depth) { + assert depth >= 0; + + if (depth == 0) + return; + + KeyTree left = new KeyTree(false, kt); + KeyTree right = new KeyTree(true, kt); + + kt.setLeft(left); + kt.setRight(right); + + recGenKeyTree(left, depth - 1); + recGenKeyTree(right, depth - 1); + } + + private boolean recCollectKey(KeyTree kt, BitSet keyStart, int curIndex) { + assert curIndex > 0; + + keyStart.set(curIndex, kt.getValue()); + + KeyTree left = kt.getLeft(); + KeyTree right = kt.getRight(); + + // Finish criteria + if (left.isLeaf() || right.isLeaf()) { + assert left.isLeaf() && right.isLeaf(); // fully balanced binary tree by construction + + + if (!right.isFree() && ! left.isFree()) return false; + + // No choice finish + if (!right.isFree()) + keyStart.set(curIndex - 1, left.getValue()); + else if (!left.isFree()) + keyStart.set(curIndex - 1, right.getValue()); + else if (left.isFree() && right.isFree()) { // Randomized finish + Random r = new Random(); + boolean leftFirst = r.nextBoolean(); + if(leftFirst) keyStart.set(curIndex - 1, left.getValue()); + else keyStart.set(curIndex - 1, right.getValue()); + } + + return true; + } + + // No choice depth-first traversal + if (!left.isFree()) + return recCollectKey(right, keyStart, curIndex - 1); + else if (!right.isFree()) + return recCollectKey(left, keyStart, curIndex - 1); + else { // Randomized depth-first traversal + Random r = new Random(); + boolean leftFirst = r.nextBoolean(); + if (leftFirst) + return recCollectKey(left, keyStart, curIndex - 1); + else + return recCollectKey(right, keyStart, curIndex - 1); + } + } + + protected KeyTree getKeyTree() { + return this.root; + } +} diff --git a/cnc/src/test/java/org/btcollider/cnc/keysrv/KeyServerTest.java b/cnc/src/test/java/org/btcollider/cnc/keysrv/KeyServerTest.java index 4910bbf..7f0a6fe 100644 --- a/cnc/src/test/java/org/btcollider/cnc/keysrv/KeyServerTest.java +++ b/cnc/src/test/java/org/btcollider/cnc/keysrv/KeyServerTest.java @@ -2,13 +2,14 @@ package org.btcollider.cnc.keysrv; import static org.junit.jupiter.api.Assertions.*; +import org.btcollider.cnc.keysrv.impl.keytree.KeyTreeServer; import org.junit.jupiter.api.Test; class KeyServerTest { @Test void testInitEmptyTree() { - assertThrows(AssertionError.class, () -> KeyServer.init(0, 0, 0)); + assertThrows(AssertionError.class, () -> KeyTreeServer.init(0, 0, 0)); } }