// Gate: a REPEATED inbound handshake gets the SAME reply, not a second connection.
//
// hyperdht's server keys every inbound PEER_HANDSHAKE by its Noise message bytes
// (server.js _onpeerhandshake, k = hex(noise)) and answers a repeat — a retried request,
// or the same handshake arriving by a second path — with the cached reply of the first
// (`_connects.get(k)`). Ours allocated a new handshake per copy: a new Noise reply, a new
// udx stream id, a second connection that then "replaced" the first, leaving the client
// bound to a stream we had destroyed. keetd's M3d runs saw it on the public DHT (each JS
// dial arriving twice; header_recv=0 on every stream).
//
// 1 the same handshake twice, direct, same tid: ONE handshake, the listener told ONCE,
// and the two reply packets byte-identical
// 2 the same handshake again, now relayed (FROM_RELAY) by another node: still one
// handshake, still told once, and a reply goes to that relay
// 3 a DIFFERENT handshake: a new one is made (dedup must not swallow everything)
#include <cstdio>
#include <cstring>
#include <vector>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <poll.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sodium.h>
#include "dht_messages.h"
#include "dht_rpc.h"
#include "hyperdht.h"
#include "noise_ik.h"
extern "C" {
#include <udx.h>
#include <uv.h>
}
static int passed = 0, failed = 0;
static void check (bool ok, const char *what) { (ok ? passed : failed)++; printf(" %s %s\n", ok ? "PASS" : "FAIL", what); }
// a plain UDP socket standing in for the client (or a relay): where replies land
struct Catcher {
int fd;
uint16_t port;
Catcher () {
fd = socket(AF_INET, SOCK_DGRAM, 0);
sockaddr_in a{};
a.sin_family = AF_INET;
a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
bind(fd, (sockaddr *)&a, sizeof a);
socklen_t l = sizeof a;
getsockname(fd, (sockaddr *)&a, &l);
port = ntohs(a.sin_port);
}
~Catcher () { close(fd); }
// pump the loop until a packet arrives (or ~1s)
std::vector<uint8_t> next (uv_loop_t *loop) {
for (int i = 0; i < 100; i++) {
uv_run(loop, UV_RUN_NOWAIT);
pollfd p{ fd, POLLIN, 0 };
if (poll(&p, 1, 10) > 0) {
uint8_t b[2048];
const ssize_t n = recv(fd, b, sizeof b, 0);
if (n > 0) return std::vector<uint8_t>(b, b + n);
}
}
return {};
}
};
static std::vector<uint8_t> encode_hs (const HandshakeMessage &h) {
EncodeState s = { 0, 0, nullptr };
dht_msg::handshake_preencode(s, h);
std::vector<uint8_t> b(s.end);
s.buffer = b.data();
s.start = 0;
dht_msg::handshake_encode(s, h);
return b;
}
int main () {
if (sodium_init() < 0) return 2;
uv_loop_t loop;
uv_loop_init(&loop);
udx_t udx;
udx_init(&loop, &udx, nullptr);
udx_socket_t sock;
udx_socket_init(&udx, &sock, nullptr);
sockaddr_in addr;
uv_ip4_addr("127.0.0.1", 0, &addr);
udx_socket_bind(&sock, (sockaddr *)&addr, 0);
DhtRpc rpc;
rpc.init(&sock, &loop);
Ed25519KeyPair server_kp;
crypto_sign_keypair(server_kp.public_key, server_kp.secret_key);
HyperDht dht;
dht.init(&rpc, server_kp);
dht.listen();
dht.tick(1000);
int told = 0;
dht.on_handshake_request = [&] (uint32_t, const uint8_t *, const dht_msg::NoisePayload &) { told++; };
uint8_t ns[32]; // NS_PEER_HANDSHAKE, as test_handshake_pool derives it
{
const char *name = "hyperswarm/dht";
uint8_t base[32];
crypto_generichash(base, 32, (const uint8_t *)name, strlen(name), nullptr, 0);
const uint8_t id = 0;
crypto_generichash_state st;
crypto_generichash_init(&st, nullptr, 0, 32);
crypto_generichash_update(&st, base, 32);
crypto_generichash_update(&st, &id, 1);
crypto_generichash_final(&st, ns, 32);
}
auto make_noise = [&] (uint32_t udx_id) {
Ed25519KeyPair kp;
crypto_sign_keypair(kp.public_key, kp.secret_key);
NoiseIK ik(true, kp, ns, server_kp.public_key);
dht_msg::NoisePayload p;
memset(&p, 0, sizeof p);
p.version = 1;
p.firewall = 1;
p.has_udx = true;
p.udx.id = udx_id;
p.has_secret_stream = true;
EncodeState ps = { 0, 0, nullptr };
dht_msg::noise_payload_preencode(ps, p);
std::vector<uint8_t> pb(ps.end);
ps.buffer = pb.data();
ps.start = 0;
dht_msg::noise_payload_encode(ps, p);
return ik.send(pb.data(), pb.size());
};
auto deliver = [&] (const std::vector<uint8_t> &value, uint16_t from_port, uint16_t tid) {
DhtRequest req;
memset(&req, 0, sizeof req);
req.command = HYPERDHT_CMD_PEER_HANDSHAKE;
req.tid = tid;
req.from.ip[0] = 127; req.from.ip[3] = 1;
req.from.port = from_port;
req.value = value.data();
req.value_len = value.size();
if (rpc.on_request) rpc.on_request(req);
};
printf("\n--- 1: the same handshake twice, direct ---\n");
Catcher client;
const auto noise = make_noise(4242);
HandshakeMessage h;
memset(&h, 0, sizeof h);
h.mode = HS_MODE_FROM_CLIENT;
h.noise = const_cast<uint8_t *>(noise.data());
h.noise_len = noise.size();
const auto direct = encode_hs(h);
deliver(direct, client.port, 77);
const auto r1 = client.next(&loop);
deliver(direct, client.port, 77);
const auto r2 = client.next(&loop);
check(!r1.empty() && !r2.empty(), "both copies were answered");
check(r1 == r2, "the two replies are byte-identical (the cached reply)");
check(dht.handshake_count() == 1, "one handshake held, not two");
check(told == 1, "the listener was told once");
printf("\n--- 2: the same handshake, relayed by another node ---\n");
Catcher relay;
HandshakeMessage hr;
memset(&hr, 0, sizeof hr);
hr.mode = HS_MODE_FROM_RELAY;
hr.noise = const_cast<uint8_t *>(noise.data());
hr.noise_len = noise.size();
hr.has_peer_address = true;
hr.peer_address.ip[0] = 127; hr.peer_address.ip[3] = 1;
hr.peer_address.port = client.port;
deliver(encode_hs(hr), relay.port, 78);
check(!relay.next(&loop).empty(), "the relay got a reply");
check(dht.handshake_count() == 1, "still one handshake");
check(told == 1, "the listener was still told only once");
printf("\n--- 3: a different handshake ---\n");
Catcher other;
const auto noise2 = make_noise(4343);
HandshakeMessage h2;
memset(&h2, 0, sizeof h2);
h2.mode = HS_MODE_FROM_CLIENT;
h2.noise = const_cast<uint8_t *>(noise2.data());
h2.noise_len = noise2.size();
deliver(encode_hs(h2), other.port, 79);
check(!other.next(&loop).empty(), "answered");
check(dht.handshake_count() == 2 && told == 2, "a new handshake, the listener told");
udx_socket_close(&sock);
uv_run(&loop, UV_RUN_NOWAIT);
printf("\n%s test_handshake_dedup: %d/%d\n", failed ? "FAIL" : "PASS", passed, passed + failed);
return failed ? 1 : 0;
}