Abstracted KeyServer
CnC can now use different implementations of KeyServer(s)
This commit is contained in:
parent
f6ed227073
commit
182b23ba86
8 changed files with 314 additions and 287 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
private static KeyServer instance;
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
private KeyTree root;
|
/**
|
||||||
private int index;
|
* Sets a KeyRange as being searched, finalizing this range
|
||||||
private int depth;
|
* A searched KeyRange need not be processed any more
|
||||||
|
*
|
||||||
|
* @param keyRange The KeyRange to be set as searched, not null
|
||||||
|
*/
|
||||||
|
void setSearched(KeyRange keyRange);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue