// Cross-system test client — verifies C++ ↔ JS HyperDHT wire interop.
//
// Usage: ./test_crossystem_client <test_name> <bootstrap> [args...]
//
// Test modes:
// bootstrap <bootstrap>
// - Bootstrap against JS testnet, report routing table size.
// - Outputs: ROUTING_TABLE_SIZE=<N> on success.
//
// immutable_put <bootstrap> <value_hex>
// - Put an immutable value on the DHT.
// - Outputs: IMMUTABLE_KEY=<hex> on success.
//
// immutable_get <bootstrap> <key_hex>
// - Get an immutable value from the DHT.
// - Outputs: IMMUTABLE_VALUE=<hex> on success.
//
// mutable_put <bootstrap> <pk_hex> <sk_hex> <value_hex> <seq>
// - Put a signed mutable value on the DHT.
// - Outputs: MUTABLE_PUT_OK on success.
//
// mutable_get <bootstrap> <pk_hex> [min_seq]
// - Get a mutable value from the DHT.
// - Outputs: MUTABLE_VALUE=<hex> MUTABLE_SEQ=<N> on success.
//
// Exit: 0=success, 1=failure, 2=timeout
#include "compact_encoding.h"
#include "dht_connector.h"
#include "dht_messages.h"
#include "dht_rpc.h"
#include "dht_storage.h"
#include "holepuncher.h"
#include "hyperdht.h"
#include "noise_ik.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sodium.h>
#include <string>
#include <vector>
extern "C" {
#include <udx.h>
#include <uv.h>
}
// ---- Global state ----
static uv_loop_t g_loop;
static udx_t g_udx;
static udx_socket_t g_socket;
static uv_timer_t g_tick_timer;
static uv_timer_t g_timeout_timer;
static DhtRpc *g_rpc = nullptr;
static HyperDht *g_dht = nullptr;
static int g_exit_code = 2; // Default: timeout
static bool g_done = false;
// ---- Hex encode/decode ----
static bool hex_decode(const char *hex, uint8_t *out, int len) {
if ((int)strlen(hex) < len * 2) return false;
for (int i = 0; i < len; i++) {
unsigned int byte;
if (sscanf(hex + i * 2, "%02x", &byte) != 1) return false;
out[i] = static_cast<uint8_t>(byte);
}
return true;
}
static std::string hex_encode(const uint8_t *data, int len) {
std::string result(len * 2, '0');
for (int i = 0; i < len; i++) {
sprintf(&result[i * 2], "%02x", data[i]);
}
return result;
}
// ---- Callbacks ----
static void on_socket_recv(udx_socket_t *socket, ssize_t read_len,
const uv_buf_t *buf, const struct sockaddr *from) {
if (read_len <= 0 || !g_rpc) return;
const uint8_t *data = (const uint8_t *)buf->base;
size_t len = (size_t)read_len;
uint8_t type_byte = data[0];
if (type_byte == DHT_REQUEST_ID || type_byte == DHT_RESPONSE_ID) {
g_rpc->on_message(data, len, (const struct sockaddr_in *)from);
}
}
static void on_tick(uv_timer_t *handle) {
if (!g_rpc) return;
uint64_t now = uv_now(&g_loop);
g_rpc->tick(now);
if (g_dht) g_dht->tick(now);
}
static void finish(int code) {
if (g_done) return;
g_done = true;
g_exit_code = code;
uv_timer_stop(&g_tick_timer);
uv_timer_stop(&g_timeout_timer);
uv_stop(&g_loop);
}
static void on_timeout(uv_timer_t *handle) {
fprintf(stderr, "TIMEOUT\n");
finish(2);
}
// ---- Parse bootstrap ----
static int parse_bootstrap(const char *str, const char **ptrs, int max) {
static char buf[512];
strncpy(buf, str, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
int count = 0;
char *tok = strtok(buf, ",");
while (tok && count < max) {
ptrs[count++] = tok;
tok = strtok(nullptr, ",");
}
return count;
}
// ---- Init common DHT infrastructure ----
static void init_dht(const char *bootstrap_str) {
uv_loop_init(&g_loop);
udx_init(&g_loop, &g_udx, nullptr);
udx_socket_init(&g_udx, &g_socket, nullptr);
g_socket.data = nullptr;
struct sockaddr_in bind_addr;
uv_ip4_addr("0.0.0.0", 0, &bind_addr);
udx_socket_bind(&g_socket, (struct sockaddr *)&bind_addr, 0);
struct sockaddr_in local_addr;
int addr_len = sizeof(local_addr);
udx_socket_getsockname(&g_socket, (struct sockaddr *)&local_addr, &addr_len);
int port = ntohs(local_addr.sin_port);
udx_socket_recv_start(&g_socket, on_socket_recv);
uv_timer_init(&g_loop, &g_tick_timer);
uv_timer_start(&g_tick_timer, on_tick, 0, 1);
uv_timer_init(&g_loop, &g_timeout_timer);
uv_timer_start(&g_timeout_timer, on_timeout, 15000, 0); // 15s timeout
Ed25519KeyPair kp;
crypto_sign_keypair(kp.public_key, kp.secret_key);
g_rpc = new DhtRpc();
g_rpc->init(&g_socket, &g_loop);
const char *boot_ptrs[8];
int boot_count = parse_bootstrap(bootstrap_str, boot_ptrs, 8);
g_rpc->set_bootstrap(boot_ptrs, boot_count);
IPv4Address local;
local.ip[0] = 127; local.ip[1] = 0; local.ip[2] = 0; local.ip[3] = 1;
local.port = port;
g_rpc->set_local_address(local);
g_dht = new HyperDht();
g_dht->init(g_rpc, kp);
}
static void cleanup_dht() {
delete g_dht;
delete g_rpc;
uv_close((uv_handle_t *)&g_tick_timer, nullptr);
uv_close((uv_handle_t *)&g_timeout_timer, nullptr);
udx_socket_close(&g_socket);
uv_run(&g_loop, UV_RUN_DEFAULT);
uv_loop_close(&g_loop);
}
// ---- Test: bootstrap ----
static void run_bootstrap(const char *bootstrap_str) {
init_dht(bootstrap_str);
g_rpc->on_bootstrapped = [&]() {
int table_size = g_rpc->table().size();
printf("ROUTING_TABLE_SIZE=%d\n", table_size);
printf("FIREWALL=%u\n", g_rpc->local_firewall());
if (table_size >= 1) {
finish(0);
} else {
fprintf(stderr, "ERROR: routing table empty after bootstrap\n");
finish(1);
}
};
g_rpc->bootstrap();
uv_run(&g_loop, UV_RUN_DEFAULT);
cleanup_dht();
}
// ---- Test: immutable_put ----
static void run_immutable_put(const char *bootstrap_str, const char *value_hex) {
std::vector<uint8_t> value(strlen(value_hex) / 2);
if (!hex_decode(value_hex, value.data(), value.size())) {
fprintf(stderr, "ERROR: invalid value hex\n");
finish(1);
return;
}
init_dht(bootstrap_str);
g_dht->on_immutable_put_complete = [](const uint8_t *key) {
printf("IMMUTABLE_KEY=%s\n", hex_encode(key, 32).c_str());
finish(0);
};
g_rpc->on_bootstrapped = [&]() {
printf("bootstrapped (table=%d)\n", g_rpc->table().size());
g_dht->immutable_put(value.data(), value.size());
};
g_rpc->bootstrap();
uv_run(&g_loop, UV_RUN_DEFAULT);
cleanup_dht();
}
// ---- Test: immutable_get ----
static void run_immutable_get(const char *bootstrap_str, const char *key_hex) {
uint8_t key[32];
if (!hex_decode(key_hex, key, 32)) {
fprintf(stderr, "ERROR: invalid key hex (need 64 chars)\n");
finish(1);
return;
}
init_dht(bootstrap_str);
g_dht->on_immutable_get_complete = [](const uint8_t *key,
const uint8_t *value, size_t len) {
if (value && len > 0) {
printf("IMMUTABLE_VALUE=%s\n", hex_encode(value, len).c_str());
finish(0);
} else {
fprintf(stderr, "ERROR: immutable value not found\n");
finish(1);
}
};
g_rpc->on_bootstrapped = [&]() {
printf("bootstrapped (table=%d)\n", g_rpc->table().size());
g_dht->immutable_get(key);
};
g_rpc->bootstrap();
uv_run(&g_loop, UV_RUN_DEFAULT);
cleanup_dht();
}
// ---- Test: mutable_put ----
static void run_mutable_put(const char *bootstrap_str, const char *pk_hex,
const char *sk_hex, const char *value_hex,
const char *seq_str) {
// For mutable_put, we need to init with the specific keypair.
uint8_t pk[32], sk[64];
if (!hex_decode(pk_hex, pk, 32) || !hex_decode(sk_hex, sk, 64)) {
fprintf(stderr, "ERROR: invalid pk/sk hex\n");
finish(1);
return;
}
std::vector<uint8_t> value(strlen(value_hex) / 2);
if (!hex_decode(value_hex, value.data(), value.size())) {
fprintf(stderr, "ERROR: invalid value hex\n");
finish(1);
return;
}
uint64_t seq = strtoull(seq_str, nullptr, 10);
// Custom init with specific keypair.
uv_loop_init(&g_loop);
udx_init(&g_loop, &g_udx, nullptr);
udx_socket_init(&g_udx, &g_socket, nullptr);
struct sockaddr_in bind_addr;
uv_ip4_addr("0.0.0.0", 0, &bind_addr);
udx_socket_bind(&g_socket, (struct sockaddr *)&bind_addr, 0);
struct sockaddr_in local_addr;
int addr_len = sizeof(local_addr);
udx_socket_getsockname(&g_socket, (struct sockaddr *)&local_addr, &addr_len);
int port = ntohs(local_addr.sin_port);
udx_socket_recv_start(&g_socket, on_socket_recv);
uv_timer_init(&g_loop, &g_tick_timer);
uv_timer_start(&g_tick_timer, on_tick, 0, 1);
uv_timer_init(&g_loop, &g_timeout_timer);
uv_timer_start(&g_timeout_timer, on_timeout, 15000, 0);
Ed25519KeyPair kp;
memcpy(kp.public_key, pk, 32);
memcpy(kp.secret_key, sk, 64);
g_rpc = new DhtRpc();
g_rpc->init(&g_socket, &g_loop);
const char *boot_ptrs[8];
int boot_count = parse_bootstrap(bootstrap_str, boot_ptrs, 8);
g_rpc->set_bootstrap(boot_ptrs, boot_count);
IPv4Address local = { {127, 0, 0, 1}, (uint16_t)port };
g_rpc->set_local_address(local);
g_dht = new HyperDht();
g_dht->init(g_rpc, kp);
g_dht->on_mutable_put_complete = [](bool success) {
if (success) {
printf("MUTABLE_PUT_OK\n");
finish(0);
} else {
fprintf(stderr, "ERROR: mutable put failed\n");
finish(1);
}
};
g_rpc->on_bootstrapped = [&]() {
printf("bootstrapped (table=%d)\n", g_rpc->table().size());
g_dht->mutable_put(value.data(), value.size(), seq);
};
g_rpc->bootstrap();
uv_run(&g_loop, UV_RUN_DEFAULT);
cleanup_dht();
}
// ---- Test: mutable_get ----
static void run_mutable_get(const char *bootstrap_str, const char *pk_hex,
const char *min_seq_str) {
uint8_t pk[32];
if (!hex_decode(pk_hex, pk, 32)) {
fprintf(stderr, "ERROR: invalid pk hex\n");
finish(1);
return;
}
uint64_t min_seq = 0;
if (min_seq_str) min_seq = strtoull(min_seq_str, nullptr, 10);
init_dht(bootstrap_str);
g_dht->on_mutable_get_complete = [](const uint8_t *public_key,
const uint8_t *value, size_t len,
uint64_t seq) {
if (value && len > 0) {
printf("MUTABLE_VALUE=%s\n", hex_encode(value, len).c_str());
printf("MUTABLE_SEQ=%llu\n", (unsigned long long)seq);
finish(0);
} else {
fprintf(stderr, "ERROR: mutable value not found\n");
finish(1);
}
};
g_rpc->on_bootstrapped = [&]() {
printf("bootstrapped (table=%d)\n", g_rpc->table().size());
g_dht->mutable_get(pk, min_seq);
};
g_rpc->bootstrap();
uv_run(&g_loop, UV_RUN_DEFAULT);
cleanup_dht();
}
// ---- Main ----
int main(int argc, char **argv) {
if (argc < 3) {
fprintf(stderr, "Usage: %s <test_name> <bootstrap> [args...]\n", argv[0]);
fprintf(stderr, "Tests: bootstrap, immutable_put, immutable_get, mutable_put, mutable_get\n");
return 1;
}
if (sodium_init() < 0) {
fprintf(stderr, "sodium_init failed\n");
return 1;
}
const char *test = argv[1];
const char *bootstrap = argv[2];
if (strcmp(test, "bootstrap") == 0) {
run_bootstrap(bootstrap);
} else if (strcmp(test, "immutable_put") == 0) {
if (argc < 4) { fprintf(stderr, "Need value_hex\n"); return 1; }
run_immutable_put(bootstrap, argv[3]);
} else if (strcmp(test, "immutable_get") == 0) {
if (argc < 4) { fprintf(stderr, "Need key_hex\n"); return 1; }
run_immutable_get(bootstrap, argv[3]);
} else if (strcmp(test, "mutable_put") == 0) {
if (argc < 7) { fprintf(stderr, "Need pk_hex sk_hex value_hex seq\n"); return 1; }
run_mutable_put(bootstrap, argv[3], argv[4], argv[5], argv[6]);
} else if (strcmp(test, "mutable_get") == 0) {
if (argc < 4) { fprintf(stderr, "Need pk_hex\n"); return 1; }
run_mutable_get(bootstrap, argv[3], argc > 4 ? argv[4] : nullptr);
} else {
fprintf(stderr, "Unknown test: %s\n", test);
return 1;
}
return g_exit_code;
}