Abstracted KeyServer

CnC can now use different implementations of KeyServer(s)
This commit is contained in:
armin 2018-04-26 12:00:04 +02:00
parent f6ed227073
commit 182b23ba86
8 changed files with 314 additions and 287 deletions

View file

@ -3,6 +3,7 @@ package org.btcollider.cnc;
import org.btcollider.cnc.comm.CommException; import org.btcollider.cnc.comm.CommException;
import org.btcollider.cnc.comm.CommServer; import org.btcollider.cnc.comm.CommServer;
import org.btcollider.cnc.keysrv.KeyServer; import org.btcollider.cnc.keysrv.KeyServer;
import org.btcollider.cnc.keysrv.impl.keytree.KeyTreeServer;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -18,8 +19,8 @@ public class CnC {
public static final int MAX_BITS = 62; public static final int MAX_BITS = 62;
public static void main(String[] args) { public static void main(String[] args) {
KeyServer.init(54, 27, 18000000000l); KeyServer kts = new KeyTreeServer(54, 27, 18000000000l);
CommServer server = new CommServer(PORT); CommServer server = new CommServer(PORT, kts);
try { try {
server.listen(); server.listen();
} catch (CommException e) { } catch (CommException e) {

View file

@ -18,11 +18,13 @@ public class ClientWorker implements Runnable {
final Logger log = LoggerFactory.getLogger(ClientWorker.class); final Logger log = LoggerFactory.getLogger(ClientWorker.class);
private Socket clientSocket; private Socket clientSocket;
private KeyServer keyServer;
private BufferedReader in; private BufferedReader in;
private PrintWriter out; private PrintWriter out;
public ClientWorker(Socket clientSocket) { public ClientWorker(Socket clientSocket, KeyServer keyServer) {
this.clientSocket = clientSocket; this.clientSocket = clientSocket;
this.keyServer = keyServer;
} }
@Override @Override
@ -80,12 +82,11 @@ public class ClientWorker implements Runnable {
} }
private void sendWork() { private void sendWork() {
KeyServer ks = KeyServer.getInstance();
KeyRange kr; KeyRange kr;
synchronized (ks) { synchronized (keyServer) {
kr = ks.getRange(); kr = keyServer.getRange();
ks.setInWork(kr); keyServer.setInWork(kr);
} }
if (kr == null) { if (kr == null) {
@ -104,8 +105,8 @@ public class ClientWorker implements Runnable {
String[] result = in.readLine().split(" "); String[] result = in.readLine().split(" ");
KeyRange kr = new KeyRange(Long.parseLong(result[0], 16), Long.parseLong(result[1], 16)); KeyRange kr = new KeyRange(Long.parseLong(result[0], 16), Long.parseLong(result[1], 16));
synchronized (KeyServer.getInstance()) { synchronized (keyServer) {
KeyServer.getInstance().setSearched(kr); keyServer.setSearched(kr);
} }
log.debug("Keyrange {} searched", kr); log.debug("Keyrange {} searched", kr);

View file

@ -5,6 +5,7 @@ import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import org.btcollider.cnc.keysrv.KeyServer;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -14,8 +15,9 @@ public class CommServer {
private int port; private int port;
private ServerSocket serverSocket; private ServerSocket serverSocket;
private KeyServer keyServer;
public CommServer(int port) { public CommServer(int port, KeyServer keyServer) {
this.port = port; this.port = port;
} }
@ -30,7 +32,7 @@ public class CommServer {
try { try {
Socket socket = serverSocket.accept(); Socket socket = serverSocket.accept();
log.debug("Got connection from " + socket.getInetAddress()); log.debug("Got connection from " + socket.getInetAddress());
es.execute(new ClientWorker(socket)); es.execute(new ClientWorker(socket, keyServer));
} catch (IOException e) { } catch (IOException e) {
log.error("Couldn't establish connections on socket", e); log.error("Couldn't establish connections on socket", e);

View file

@ -1,277 +1,36 @@
package org.btcollider.cnc.keysrv; 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.btcollider.cnc.dto.KeyRange;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class KeyServer { public interface KeyServer {
final Logger log = LoggerFactory.getLogger(KeyServer.class); /**
* 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);
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<KeyTree> 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;
}
} }

View file

@ -1,10 +1,10 @@
package org.btcollider.cnc.keysrv; package org.btcollider.cnc.keysrv.impl.keytree;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
public class KTConcierge implements Runnable { public class KTConcierge implements Runnable {
final Logger log = LoggerFactory.getLogger(KeyServer.class); final Logger log = LoggerFactory.getLogger(KeyTreeServer.class);
private KeyTree keyTree; private KeyTree keyTree;
private long maxWorkSpan; private long maxWorkSpan;

View file

@ -1,4 +1,4 @@
package org.btcollider.cnc.keysrv; package org.btcollider.cnc.keysrv.impl.keytree;
public class KeyTree { public class KeyTree {
private boolean value; private boolean value;

View file

@ -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<KeyTree> 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;
}
}

View file

@ -2,13 +2,14 @@ package org.btcollider.cnc.keysrv;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import org.btcollider.cnc.keysrv.impl.keytree.KeyTreeServer;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
class KeyServerTest { class KeyServerTest {
@Test @Test
void testInitEmptyTree() { void testInitEmptyTree() {
assertThrows(AssertionError.class, () -> KeyServer.init(0, 0, 0)); assertThrows(AssertionError.class, () -> KeyTreeServer.init(0, 0, 0));
} }
} }