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.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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<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;
|
||||
}
|
||||
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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -1,4 +1,4 @@
|
|||
package org.btcollider.cnc.keysrv;
|
||||
package org.btcollider.cnc.keysrv.impl.keytree;
|
||||
|
||||
public class KeyTree {
|
||||
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 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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue