diff --git a/arch.md b/arch.md index dfc85fd..ec81bc3 100644 --- a/arch.md +++ b/arch.md @@ -46,3 +46,9 @@ if SUC: ## REP ## TODO + +# TODO # + +[ ] Admin communication +[ ] Admin interface +[ ] Adapt brainflyer diff --git a/bfclient/hash/hash160.h b/bfclient/hash/hash160.h new file mode 100644 index 0000000..7f2ad37 --- /dev/null +++ b/bfclient/hash/hash160.h @@ -0,0 +1,14 @@ +/* Copyright (c) 2015 Ryan Castellucci, All Rights Reserved */ +#ifndef __BRAINFLAYER_HASH160_H_ +#define __BRAINFLAYER_HASH160_H_ + +#include +#include + +typedef union hash160_u { + unsigned char uc[RIPEMD160_DIGEST_LENGTH]; + uint32_t ul[RIPEMD160_DIGEST_LENGTH>>2]; +} hash160_t; + +/* vim: set ts=2 sw=2 et ai si: */ +#endif /* __BRAINFLAYER_HASH160_H_ */ diff --git a/bfclient/hash/ripemd160_256.c b/bfclient/hash/ripemd160_256.c new file mode 100644 index 0000000..b7a9126 --- /dev/null +++ b/bfclient/hash/ripemd160_256.c @@ -0,0 +1,286 @@ +#define _RIPEMD160_C_ 1 + +#include "ripemd160_256.h" + +// adapted by Pieter Wuille in 2012; all changes are in the public domain +// modified by Ryan Castellucci in 2015; all changes are in the public domain + +/* + * + * RIPEMD160.c : RIPEMD-160 implementation + * + * Written in 2008 by Dwayne C. Litzenberger + * + * =================================================================== + * The contents of this file are dedicated to the public domain. To + * the extent that dedication to the public domain is not available, + * everyone is granted a worldwide, perpetual, royalty-free, + * non-exclusive license to exercise all rights associated with the + * contents of this file for any purpose whatsoever. + * No rights are reserved. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * =================================================================== + * + * Country of origin: Canada + * + * This implementation (written in C) is based on an implementation the author + * wrote in Python. + * + * This implementation was written with reference to the RIPEMD-160 + * specification, which is available at: + * http://homes.esat.kuleuven.be/~cosicart/pdf/AB-9601/ + * + * It is also documented in the _Handbook of Applied Cryptography_, as + * Algorithm 9.55. It's on page 30 of the following PDF file: + * http://www.cacr.math.uwaterloo.ca/hac/about/chap9.pdf + * + * The RIPEMD-160 specification doesn't really tell us how to do padding, but + * since RIPEMD-160 is inspired by MD4, you can use the padding algorithm from + * RFC 1320. + * + * According to http://www.users.zetnet.co.uk/hopwood/crypto/scan/md.html: + * "RIPEMD-160 is big-bit-endian, little-byte-endian, and left-justified." + */ + +#include + +#include + +#define RIPEMD160_DIGEST_SIZE 20 +#define BLOCK_SIZE 64 + +/* cyclic left-shift the 32-bit word n left by s bits */ +#define ROL(s, n) (((n) << (s)) | ((n) >> (32-(s)))) + +/* Initial values for the chaining variables. + * This is just 0123456789ABCDEFFEDCBA9876543210F0E1D2C3 in little-endian. */ +static const uint32_t initial_h[5] = { 0x67452301u, 0xEFCDAB89u, 0x98BADCFEu, 0x10325476u, 0xC3D2E1F0u }; + +/* Ordering of message words. Based on the permutations rho(i) and pi(i), defined as follows: + * + * rho(i) := { 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8 }[i] 0 <= i <= 15 + * + * pi(i) := 9*i + 5 (mod 16) + * + * Line | Round 1 | Round 2 | Round 3 | Round 4 | Round 5 + * -------+-----------+-----------+-----------+-----------+----------- + * left | id | rho | rho^2 | rho^3 | rho^4 + * right | pi | rho pi | rho^2 pi | rho^3 pi | rho^4 pi + */ + +/* Left line */ +static const uint8_t RL[5][16] = { + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, /* Round 1: id */ + { 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8 }, /* Round 2: rho */ + { 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12 }, /* Round 3: rho^2 */ + { 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2 }, /* Round 4: rho^3 */ + { 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13 } /* Round 5: rho^4 */ +}; + +/* Right line */ +static const uint8_t RR[5][16] = { + { 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12 }, /* Round 1: pi */ + { 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2 }, /* Round 2: rho pi */ + { 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13 }, /* Round 3: rho^2 pi */ + { 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14 }, /* Round 4: rho^3 pi */ + { 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11 } /* Round 5: rho^4 pi */ +}; + +/* + * Shifts - Since we don't actually re-order the message words according to + * the permutations above (we could, but it would be slower), these tables + * come with the permutations pre-applied. + */ + +/* Shifts, left line */ +static const uint8_t SL[5][16] = { + { 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8 }, /* Round 1 */ + { 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12 }, /* Round 2 */ + { 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5 }, /* Round 3 */ + { 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12 }, /* Round 4 */ + { 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 } /* Round 5 */ +}; + +/* Shifts, right line */ +static const uint8_t SR[5][16] = { + { 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6 }, /* Round 1 */ + { 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11 }, /* Round 2 */ + { 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5 }, /* Round 3 */ + { 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8 }, /* Round 4 */ + { 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 } /* Round 5 */ +}; + +/* static padding for 256 bit input */ +static const uint8_t pad256[32] = { + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* length 256 bits, little endian uint64_t */ + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* Boolean functions */ + +#define F1(x, y, z) ((x) ^ (y) ^ (z)) +#define F2(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define F3(x, y, z) (((x) | ~(y)) ^ (z)) +#define F4(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define F5(x, y, z) ((x) ^ ((y) | ~(z))) + +/* Round constants, left line */ +static const uint32_t KL[5] = { + 0x00000000u, /* Round 1: 0 */ + 0x5A827999u, /* Round 2: floor(2**30 * sqrt(2)) */ + 0x6ED9EBA1u, /* Round 3: floor(2**30 * sqrt(3)) */ + 0x8F1BBCDCu, /* Round 4: floor(2**30 * sqrt(5)) */ + 0xA953FD4Eu /* Round 5: floor(2**30 * sqrt(7)) */ +}; + +/* Round constants, right line */ +static const uint32_t KR[5] = { + 0x50A28BE6u, /* Round 1: floor(2**30 * cubert(2)) */ + 0x5C4DD124u, /* Round 2: floor(2**30 * cubert(3)) */ + 0x6D703EF3u, /* Round 3: floor(2**30 * cubert(5)) */ + 0x7A6D76E9u, /* Round 4: floor(2**30 * cubert(7)) */ + 0x00000000u /* Round 5: 0 */ +}; + +static inline void byteswap32(uint32_t *v) +{ + union { uint32_t w; uint8_t b[4]; } x, y; + + x.w = *v; + y.b[0] = x.b[3]; + y.b[1] = x.b[2]; + y.b[2] = x.b[1]; + y.b[3] = x.b[0]; + *v = y.w; + + /* Wipe temporary variables */ + x.w = y.w = 0; +} + +static inline void byteswap_digest(uint32_t *p) +{ + unsigned int i; + + for (i = 0; i < 4; i++) { + byteswap32(p++); + byteswap32(p++); + byteswap32(p++); + byteswap32(p++); + } +} + +/* The RIPEMD160 compression function. */ +static inline void ripemd160_rawcompress(void *pbuf, void *ph) +{ + uint8_t w, round; + uint32_t T; + uint32_t AL, BL, CL, DL, EL; /* left line */ + uint32_t AR, BR, CR, DR, ER; /* right line */ + + uint32_t *buf = pbuf; + uint32_t *h = ph; + + /* Byte-swap the buffer if we're on a big-endian machine */ +#ifdef PCT_BIG_ENDIAN + byteswap_digest(buf); +#endif + + /* initialize state */ + memcpy(h, initial_h, RIPEMD160_DIGEST_SIZE); + + /* Load the left and right lines with the initial state */ + AL = AR = h[0]; + BL = BR = h[1]; + CL = CR = h[2]; + DL = DR = h[3]; + EL = ER = h[4]; + + /* Round 1 */ + round = 0; + for (w = 0; w < 16; w++) { /* left line */ + T = ROL(SL[round][w], AL + F1(BL, CL, DL) + buf[RL[round][w]] + KL[round]) + EL; + AL = EL; EL = DL; DL = ROL(10, CL); CL = BL; BL = T; + } + for (w = 0; w < 16; w++) { /* right line */ + T = ROL(SR[round][w], AR + F5(BR, CR, DR) + buf[RR[round][w]] + KR[round]) + ER; + AR = ER; ER = DR; DR = ROL(10, CR); CR = BR; BR = T; + } + + /* Round 2 */ + round++; + for (w = 0; w < 16; w++) { /* left line */ + T = ROL(SL[round][w], AL + F2(BL, CL, DL) + buf[RL[round][w]] + KL[round]) + EL; + AL = EL; EL = DL; DL = ROL(10, CL); CL = BL; BL = T; + } + for (w = 0; w < 16; w++) { /* right line */ + T = ROL(SR[round][w], AR + F4(BR, CR, DR) + buf[RR[round][w]] + KR[round]) + ER; + AR = ER; ER = DR; DR = ROL(10, CR); CR = BR; BR = T; + } + + /* Round 3 */ + round++; + for (w = 0; w < 16; w++) { /* left line */ + T = ROL(SL[round][w], AL + F3(BL, CL, DL) + buf[RL[round][w]] + KL[round]) + EL; + AL = EL; EL = DL; DL = ROL(10, CL); CL = BL; BL = T; + } + for (w = 0; w < 16; w++) { /* right line */ + T = ROL(SR[round][w], AR + F3(BR, CR, DR) + buf[RR[round][w]] + KR[round]) + ER; + AR = ER; ER = DR; DR = ROL(10, CR); CR = BR; BR = T; + } + + /* Round 4 */ + round++; + for (w = 0; w < 16; w++) { /* left line */ + T = ROL(SL[round][w], AL + F4(BL, CL, DL) + buf[RL[round][w]] + KL[round]) + EL; + AL = EL; EL = DL; DL = ROL(10, CL); CL = BL; BL = T; + } + for (w = 0; w < 16; w++) { /* right line */ + T = ROL(SR[round][w], AR + F2(BR, CR, DR) + buf[RR[round][w]] + KR[round]) + ER; + AR = ER; ER = DR; DR = ROL(10, CR); CR = BR; BR = T; + } + + /* Round 5 */ + round++; + for (w = 0; w < 16; w++) { /* left line */ + T = ROL(SL[round][w], AL + F5(BL, CL, DL) + buf[RL[round][w]] + KL[round]) + EL; + AL = EL; EL = DL; DL = ROL(10, CL); CL = BL; BL = T; + } + for (w = 0; w < 16; w++) { /* right line */ + T = ROL(SR[round][w], AR + F1(BR, CR, DR) + buf[RR[round][w]] + KR[round]) + ER; + AR = ER; ER = DR; DR = ROL(10, CR); CR = BR; BR = T; + } + + /* Final mixing stage */ + T = h[1] + CL + DR; + h[1] = h[2] + DL + ER; + h[2] = h[3] + EL + AR; + h[3] = h[4] + AL + BR; + h[4] = h[0] + BL + CR; + h[0] = T; + + /* Byte-swap the output if we're on a big-endian machine */ +#ifdef PCT_BIG_ENDIAN + byteswap_digest(h); +#endif +} + +void ripemd160_256(const void *in, void *out) { + unsigned char buf[64]; + /* copy input data */ + memcpy(buf + 0, in, 32); + /* append fixed padding */ + memcpy(buf + 32, pad256, 32); + /* compute and output hash */ + ripemd160_rawcompress(buf, out); +} diff --git a/bfclient/hash/ripemd160_256.h b/bfclient/hash/ripemd160_256.h new file mode 100644 index 0000000..84e51d8 --- /dev/null +++ b/bfclient/hash/ripemd160_256.h @@ -0,0 +1,8 @@ +#ifndef __RIPEMD160_256_H_ +#define __RIPEMD160_256_H_ + +#include + +void ripemd160_256(const void *in, void *out); + +#endif//__RIPEMD160_256_H_ diff --git a/client/src/client/ClientRunner.java b/client/src/client/ClientRunner.java new file mode 100644 index 0000000..4457177 --- /dev/null +++ b/client/src/client/ClientRunner.java @@ -0,0 +1,54 @@ +package client; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.net.Socket; + +public class ClientRunner implements Runnable { + long sleep; + private String host; + private Integer port; + private boolean skip; + + public ClientRunner(String host, Integer port, long sleep, boolean skip) { + this.sleep = sleep; + this.host = host; + this.port = port; + this.skip = skip; + } + + @Override + public void run() { + try { + Socket socket = new Socket(host, port); + + BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); + PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true); + + out.println("WRK"); + String work = in.readLine(); + System.out.println("Sleep: "+sleep+", skip: "+skip+", work: "+work); + + socket.close(); + if (!skip) { + Thread.sleep(sleep); + + socket = new Socket(host, port); + + in = new BufferedReader(new InputStreamReader(socket.getInputStream())); + out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true); + + out.println("RES"); + out.println(work); + + socket.close(); + } + } catch (Exception e) { + System.out.println(e); + } + + } + +} diff --git a/client/src/client/CommClient.java b/client/src/client/CommClient.java new file mode 100644 index 0000000..b8711e7 --- /dev/null +++ b/client/src/client/CommClient.java @@ -0,0 +1,28 @@ +package client; + +public class CommClient { + private static final String HOST = "localhost"; + private static final Integer PORT = 26765; + + public static void main(String[] args) throws Exception { + + for (int i = 0; i < 5; i++) { + new Thread(new ClientRunner(HOST, PORT, 5000, false)).start(); + new Thread(new ClientRunner(HOST, PORT, 15000, false)).start(); + new Thread(new ClientRunner(HOST, PORT, 0, true)).start(); + new Thread(new ClientRunner(HOST, PORT, 5000, false)).start(); + } + + Thread.sleep(20000); + System.out.println("******** BATCH 2 ********"); + + for (int i = 0; i < 5; i++) { + new Thread(new ClientRunner(HOST, PORT, 5000, false)).start(); + new Thread(new ClientRunner(HOST, PORT, 5000, false)).start(); + + new Thread(new ClientRunner(HOST, PORT, 15000, false)).start(); + new Thread(new ClientRunner(HOST, PORT, 0, true)).start(); + new Thread(new ClientRunner(HOST, PORT, 5000, false)).start(); + } + } +} diff --git a/cnc/src/main/java/org/btcollider/cnc/CnC.java b/cnc/src/main/java/org/btcollider/cnc/CnC.java index 066b8c9..9d20ba1 100644 --- a/cnc/src/main/java/org/btcollider/cnc/CnC.java +++ b/cnc/src/main/java/org/btcollider/cnc/CnC.java @@ -18,12 +18,14 @@ public class CnC { public static final int MAX_BITS = 62; public static void main(String[] args) { - KeyServer.init(55, 20); + KeyServer.init(10, 7, 10000); CommServer server = new CommServer(PORT); try { server.listen(); } catch (CommException e) { log.error("CommServer couldn't be started"); + } finally { + server.stop(); } } } 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 d4daab8..8c0a1b6 100644 --- a/cnc/src/main/java/org/btcollider/cnc/comm/ClientWorker.java +++ b/cnc/src/main/java/org/btcollider/cnc/comm/ClientWorker.java @@ -27,7 +27,7 @@ public class ClientWorker implements Runnable { public void run() { try { in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); - out = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream())); + out = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream()), true); } catch (IOException e) { log.error("Couldn't open communication channel", e); closeCommChannel(); @@ -53,6 +53,8 @@ public class ClientWorker implements Runnable { default: assert false; } + + shutDown(); } private void closeCommChannel() { @@ -71,12 +73,45 @@ public class ClientWorker implements Runnable { private void sendWork() { KeyServer ks = KeyServer.getInstance(); - KeyRange kr = ks.getRange(); - out.println(kr.getStart() + " " + kr.getEnd() + " " + kr.getTotal()); - out.flush(); + KeyRange kr; + + synchronized (ks) { + kr = ks.getRange(); + ks.setInWork(kr); + } + + String work = Long.toHexString(kr.getStart()) + " " + Long.toHexString(kr.getEnd()) + " " + kr.getTotal(); + + out.println(work); + + log.debug("Work <{}> sent to {}", work, clientSocket.getInetAddress()); } private void retrieveResult() { + String[] result; + try { + 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); + } + + log.debug("Got result for {}", kr); + + } catch (IOException e) { + log.error("Couldn't read result", e); + } + } + + private void shutDown() { + closeCommChannel(); + if (!clientSocket.isClosed()) { + try { + clientSocket.close(); + } catch (IOException e) { + log.error("Couldn't close client socket", e); + } + } } } 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 7f7e40a..823cc02 100644 --- a/cnc/src/main/java/org/btcollider/cnc/comm/CommServer.java +++ b/cnc/src/main/java/org/btcollider/cnc/comm/CommServer.java @@ -47,7 +47,7 @@ public class CommServer { log.debug("Opening socket on port {}", port); try { - this.serverSocket = new ServerSocket(); + this.serverSocket = new ServerSocket(port); } catch (IOException e) { log.error("Couldn't allocate server socket", e); diff --git a/cnc/src/main/java/org/btcollider/cnc/dto/KeyRange.java b/cnc/src/main/java/org/btcollider/cnc/dto/KeyRange.java index e6ba3b6..02e6345 100644 --- a/cnc/src/main/java/org/btcollider/cnc/dto/KeyRange.java +++ b/cnc/src/main/java/org/btcollider/cnc/dto/KeyRange.java @@ -5,28 +5,38 @@ import java.util.BitSet; import org.btcollider.cnc.CnC; public class KeyRange { - private BitSet start; - private BitSet end; + private Long start; + private Long end; public KeyRange(BitSet start, BitSet end) { assert start.length() <= CnC.MAX_BITS && end.length() <= CnC.MAX_BITS; + this.start = start.toLongArray()[0]; + this.end = end.toLongArray()[0]; + } + + public KeyRange(Long start, Long end) { this.start = start; this.end = end; } - - public BitSet getStart() { + + public Long getStart() { return start; } - public BitSet getEnd() { + public Long getEnd() { return end; } public Long getTotal() { // asserted that <= MAX_BITS, first long contains all significant bits - Long total = getStart().toLongArray()[0] - getEnd().toLongArray()[0]; + Long total = getEnd() - getStart() + 1; // +1 bc start is included too return total; } + + @Override + public String toString() { + return "KeyRange [start=" + start + ", end=" + end + ", total=" + getTotal() +"]"; + } } diff --git a/cnc/src/main/java/org/btcollider/cnc/keysrv/KTConcierge.java b/cnc/src/main/java/org/btcollider/cnc/keysrv/KTConcierge.java new file mode 100644 index 0000000..bf6ee51 --- /dev/null +++ b/cnc/src/main/java/org/btcollider/cnc/keysrv/KTConcierge.java @@ -0,0 +1,45 @@ +package org.btcollider.cnc.keysrv; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class KTConcierge implements Runnable { + final Logger log = LoggerFactory.getLogger(KeyServer.class); + + private KeyTree keyTree; + private long maxWorkSpan; + + public KTConcierge(KeyTree keyTree, long maxWorkSpan) { + this.keyTree = keyTree; + this.maxWorkSpan = maxWorkSpan; + } + + @Override + public void run() { + recCheckInWork(keyTree); + } + + private void recCheckInWork(KeyTree node) { + if (node == null) return; + + if (node.inWork()) { + long inWorkSpan = System.currentTimeMillis() - node.inWorkSince(); + if (inWorkSpan >= maxWorkSpan) { + if (node.isLeaf()) { + StringBuilder key = new StringBuilder(); + key.append(node.getValue() ? "1": "0"); + KeyTree parent = node.getParent(); + while (parent != null) { + key.insert(0, parent.getValue() ? "1": "0"); + parent = parent.getParent(); + } + log.debug("{}: Retracting Work for key {}, inWorkSince: {}", System.currentTimeMillis(), key, node.inWorkSince()); + } + node.clearInWork(); + } + } + + recCheckInWork(node.getLeft()); + recCheckInWork(node.getRight()); + } +} 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 e9c47e2..5db180b 100644 --- a/cnc/src/main/java/org/btcollider/cnc/keysrv/KeyServer.java +++ b/cnc/src/main/java/org/btcollider/cnc/keysrv/KeyServer.java @@ -1,6 +1,12 @@ 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; @@ -9,30 +15,36 @@ 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) { + 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) { - instance = new KeyServer(index, depth); + 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; } @@ -62,11 +74,49 @@ public class KeyServer { } public void setSearched(KeyRange keyRange) { - markSearched(keyRange.getStart()); + markSearched(BitSet.valueOf(new long[] { keyRange.getStart() })); } public void setInWork(KeyRange keyRange) { - markInWork(keyRange.getStart()); + 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) { @@ -103,10 +153,10 @@ public class KeyServer { return; } - // Mark parent searched too, check next level + // Mark parent inWork too, check next level parent.setInWork(Long.max(parent.getLeft().inWorkSince(), parent.getRight().inWorkSince())); - recMarkSearchedParents(parent); + recMarkInWorkParents(parent); } private void markSearched(BitSet key) { @@ -181,18 +231,40 @@ public class KeyServer { KeyTree right = kt.getRight(); // Finish criteria - if (left.isLeaf() && !left.isSearched()) { - keyStart.set(curIndex - 1, left.getValue()); - return; - } else if (right.isLeaf() && !right.isSearched()) { - keyStart.set(curIndex - 1, right.getValue()); + if (left.isLeaf() || right.isLeaf()) { + assert left.isLeaf() && right.isLeaf(); // fully balanced binary tree by construction + + // 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.isLeaf() && right.isLeaf()) { // 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; } - // Recursive depth-first traversal - if (!left.isSearched()) - recCollectKey(left, keyStart, curIndex - 1); - else + // No choice depth-first traversal + if (!left.isFree()) recCollectKey(right, keyStart, curIndex - 1); + else if (!right.isFree()) + recCollectKey(left, keyStart, curIndex - 1); + else { // Randomized depth-first traversal + Random r = new Random(); + boolean leftFirst = r.nextBoolean(); + if (leftFirst) + recCollectKey(left, keyStart, curIndex - 1); + else + recCollectKey(right, keyStart, curIndex - 1); + } + } + + protected KeyTree getKeyTree() { + return this.root; } } diff --git a/cnc/src/main/java/org/btcollider/cnc/keysrv/KeyTree.java b/cnc/src/main/java/org/btcollider/cnc/keysrv/KeyTree.java index 5b5d76e..e844067 100644 --- a/cnc/src/main/java/org/btcollider/cnc/keysrv/KeyTree.java +++ b/cnc/src/main/java/org/btcollider/cnc/keysrv/KeyTree.java @@ -17,6 +17,10 @@ public class KeyTree { this.searched = false; this.inWorkSince = -1; } + + public boolean isFree() { + return !isSearched() && !inWork(); + } public boolean isLeaf() { return left == null && right == null; @@ -34,13 +38,21 @@ public class KeyTree { return left; } + public boolean hasLeft() { + return left != null; + } + public void setLeft(KeyTree left) { this.left = left; } - + public KeyTree getRight() { return right; } + + public boolean hasRight() { + return right != null; + } public void setRight(KeyTree right) { this.right = right; @@ -55,12 +67,16 @@ public class KeyTree { } public void setInWork(long since) { - this.inWorkSince = System.currentTimeMillis(); + this.inWorkSince = since; } public void setInWork() { this.inWorkSince = System.currentTimeMillis(); } + + public void clearInWork() { + this.inWorkSince = -1; + } public boolean inWork() { return inWorkSince != -1; diff --git a/cnc/src/test/java/org/btcollider/cnc/keysrv/KeyServerTest.java b/cnc/src/test/java/org/btcollider/cnc/keysrv/KeyServerTest.java new file mode 100644 index 0000000..4910bbf --- /dev/null +++ b/cnc/src/test/java/org/btcollider/cnc/keysrv/KeyServerTest.java @@ -0,0 +1,14 @@ +package org.btcollider.cnc.keysrv; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class KeyServerTest { + + @Test + void testInitEmptyTree() { + assertThrows(AssertionError.class, () -> KeyServer.init(0, 0, 0)); + } + +}