Files
dat-thang/tests/test_discovery.c
T
divorce b735900f2e 0.0.002
ddat interop with dat js
cable interop with cabal js
ddat's frontend = dat-thang
cable fe = cable-thang
dillo support based on cel's dillo-dat
bootstrap node
xplatform support

Signed-off-by: divo <418@cryspace.space>
2026-10-19 00:17:02 -04:00

235 lines
9.3 KiB
C

#include "cesspool.h"
#include "dat_wire.h"
#include "sleep_crypto.h"
#include "sleep.h"
#include <assert.h>
#include <errno.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#include <netinet/in.h>
#endif
#include <sodium.h>
#include <string.h>
#ifndef _WIN32
#include <sys/socket.h>
#endif
#ifdef _WIN32
#include <windows.h>
typedef SOCKET test_socket_t;
#else
#include <unistd.h>
typedef int test_socket_t;
#endif
static uint16_t free_udp_port(void)
{
test_socket_t fd=socket(AF_INET,SOCK_DGRAM,0);struct sockaddr_in address;
uint16_t port;
#ifdef _WIN32
int length=sizeof(address);
assert(fd!=INVALID_SOCKET);
#else
socklen_t length=sizeof(address);
assert(fd>=0);
#endif
memset(&address,0,sizeof(address));address.sin_family=AF_INET;address.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
assert(bind(fd,(struct sockaddr*)&address,sizeof(address))==0);assert(getsockname(fd,(struct sockaddr*)&address,&length)==0);
port=ntohs(address.sin_port);
#ifdef _WIN32
closesocket(fd);
#else
close(fd);
#endif
return port;
}
int main(void)
{
static const uint8_t hypercore[] = "hypercore";
static const uint8_t namespace[] = DAT_NETWORK_PROTOCOL;
uint8_t key[32], expected[32], actual[32], nonce[24], input[513];
uint8_t whole[513], chunked[513];
memset(key, 0x42, sizeof(key));
#ifdef _WIN32
{
WSADATA winsock;
assert(WSAStartup(MAKEWORD(2, 2), &winsock) == 0);
}
#endif
assert(crypto_generichash(expected, sizeof(expected), hypercore,
sizeof(hypercore) - 1, key, sizeof(key)) == 0);
assert(sleep_compute_discovery_key(key, actual) == 0);
assert(!memcmp(expected, actual, sizeof(actual)));
/* Generated by extra/dillo-dat/node_modules/hypercore-crypto for a
* 32-byte feed key filled with 0x42 (historical Dat v6/v7 stack). */
{
static const uint8_t js_hypercore_discovery_key[32] = {
0xb0,0xfa,0xa6,0x16,0x21,0x2d,0x99,0x23,
0x9f,0x50,0x7a,0x68,0x8f,0x50,0xcd,0x60,
0xe6,0x51,0x4d,0xfb,0x62,0xae,0xf4,0xfc,
0xfc,0x82,0x59,0xd6,0x46,0x96,0x35,0x91
};
assert(!memcmp(actual, js_hypercore_discovery_key, sizeof(actual)));
}
memset(nonce, 0x19, sizeof(nonce));
for (size_t i = 0; i < sizeof(input); i++) input[i] = (uint8_t)(i * 37u + 11u);
assert(sleep_stream_xsalsa20_xor(whole, input, sizeof(input), nonce, key, 0) == 0);
{
size_t offset = 0;
static const size_t pieces[] = {1, 63, 64, 65, 127, 193};
for (size_t i = 0; i < sizeof(pieces) / sizeof(pieces[0]); i++) {
assert(sleep_stream_xsalsa20_xor(chunked + offset, input + offset,
pieces[i], nonce, key, offset) == 0);
offset += pieces[i];
}
assert(offset == sizeof(input));
}
assert(!memcmp(whole, chunked, sizeof(whole)));
{
cesspool_t transactions;
uint16_t id;
memset(&transactions, 0, sizeof(transactions));
assert(cesspool_transaction_begin(&transactions, CESSPOOL_LOOKUP, key,
100, &id) == 0);
assert(cesspool_transaction_finish(&transactions, id) == 0);
assert(cesspool_transaction_begin(&transactions, CESSPOOL_PING, key,
10, &id) == 0);
assert(cesspool_tick(&transactions, 10) == 0);
errno = 0;
assert(cesspool_transaction_finish(&transactions, id) != 0 && errno == ENOENT);
}
assert(crypto_generichash(expected, sizeof(expected), namespace,
sizeof(namespace) - 1, key, sizeof(key)) == 0);
assert(cesspool_discovery_key(key, actual) == 0);
assert(!memcmp(expected, actual, sizeof(actual)));
{
cesspool_t relay, publisher, lookup;
cesspool_peer_t *peers[4];
uint8_t relay_id[32] = {0x10}, publisher_id[32] = {0x11}, lookup_id[32] = {0x22};
uint8_t topic[32] = {0x33}, archive_topic_a[32] = {0x34};
uint8_t archive_topic_b[32] = {0x35};
uint8_t packet[80];
cesspool_message_t message, decoded;
size_t packet_len;
uint16_t relay_port = free_udp_port(), publisher_port = free_udp_port(), lookup_port = free_udp_port();
assert(cesspool_init(&relay, relay_id) == 0);
assert(cesspool_init(&publisher, publisher_id) == 0);
assert(cesspool_init(&lookup, lookup_id) == 0);
assert(cesspool_listen(&relay, "localhost", relay_port) == 0);
assert(cesspool_listen(&publisher, "localhost", publisher_port) == 0);
assert(cesspool_listen(&lookup, "localhost", lookup_port) == 0);
assert(relay_port && publisher_port && lookup_port && relay_port != publisher_port && relay_port != lookup_port && publisher_port != lookup_port);
assert(cesspool_set_bootstrap_server(&relay, 1) == 0);
assert(cesspool_add_bootstrap(&publisher, "localhost", relay_port) == 0);
assert(cesspool_add_bootstrap(&lookup, "127.0.0.1", relay_port) == 0);
/* A third party's REPLY packet must not mark the configured node
* online or inject forged rendezvous entries. */
{
cesspool_message_t forged;
uint8_t forged_packet[80];
size_t forged_length;
struct sockaddr_in *target;
dat_peer_t forged_target;
memset(&forged, 0, sizeof(forged));
forged.type = CESSPOOL_REPLY;
memcpy(forged.peer_id, publisher_id, sizeof(publisher_id));
memcpy(forged.topic, topic, sizeof(topic));
forged.payload[0] = 0x1a; /* malformed one-byte peer record */
forged.payload[1] = 0x01;
forged.payload[2] = 0xff;
forged.payload_len = 3;
assert(cesspool_encode(&forged, forged_packet, sizeof(forged_packet), &forged_length) == 0);
memset(&forged_target, 0, sizeof(forged_target));
target = (struct sockaddr_in *)&forged_target.addr;
target->sin_family = AF_INET;
target->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
target->sin_port = htons(lookup_port);
forged_target.addrlen = sizeof(*target);
assert(datnet_send(&publisher.net, &forged_target, forged_packet, forged_length) == 0);
assert(cesspool_poll(&lookup, 100) >= 0);
assert(!lookup.bootstrap_reachable && lookup.announcement_count == 0);
}
assert(cesspool_join(&publisher, topic, publisher_port, 1, 0) == 0);
assert(cesspool_poll(&relay, 100) >= 0);
/* One client identity may announce a room and multiple archives. The
* rendezvous counters must report one peer across three distinct topics. */
assert(cesspool_join(&publisher, archive_topic_a, publisher_port, 1, 0) == 0);
assert(cesspool_poll(&relay, 100) >= 0);
assert(cesspool_join(&publisher, archive_topic_b, publisher_port, 1, 0) == 0);
assert(cesspool_poll(&relay, 100) >= 0);
assert(cesspool_announced_peer_count(&relay) == 1);
assert(cesspool_announced_topic_count(&relay) == 3);
assert(cesspool_join(&lookup, topic, lookup_port, 0, 1) == 0);
assert(cesspool_poll(&relay, 100) >= 0);
assert(cesspool_poll(&lookup, 100) >= 0);
assert(lookup.bootstrap_reachable);
assert(cesspool_topic_peers(&lookup, topic, peers, 4) >= 1);
assert(!memcmp(peers[0]->public_key, publisher_id, 32));
/* Expiring the lookup record must clear the client's Discovered row. */
for (size_t a = 0; a < lookup.announcement_count; a++)
lookup.announcements[a].last_seen = 0;
lookup.bootstrap_count = 0;
for (size_t t = 0; t < lookup.topic_count; t++)
lookup.topics[t].refresh_at = UINT64_MAX;
assert(cesspool_tick(&lookup, 0) == 0);
assert(cesspool_topic_peers(&lookup, topic, peers, 4) == 0);
assert(!cesspool_find_peer(&lookup, publisher_id)->discovered);
assert(cesspool_leave(&publisher, archive_topic_a) == 0);
assert(cesspool_poll(&relay, 100) >= 0);
assert(cesspool_announced_peer_count(&relay) == 1);
assert(cesspool_announced_topic_count(&relay) == 2);
assert(cesspool_tick(&relay, UINT64_MAX) == 0);
assert(cesspool_announced_peer_count(&relay) == 1); /* Fresh announcements keep it live. */
memset(&message, 0, sizeof(message));
message.type = CESSPOOL_PING;
assert(cesspool_encode(&message, packet, sizeof(packet), &packet_len) == 0);
assert(packet_len == 72);
assert(cesspool_decode(&decoded, packet, packet_len) == 0);
assert(decoded.type == CESSPOOL_PING);
assert(cesspool_decode(&decoded, packet, packet_len + 1) != 0);
memset(&message, 0, sizeof(message));
message.type = CESSPOOL_REPLY;
message.payload_len = sizeof(message.payload);
assert(cesspool_encode(&message, packet, sizeof(packet), &packet_len) != 0);
assert(cesspool_leave(&publisher, topic) == 0);
assert(cesspool_leave(&publisher, archive_topic_b) == 0);
assert(cesspool_leave(&lookup, topic) == 0);
assert(cesspool_destroy(&relay) == 0);
assert(cesspool_destroy(&publisher) == 0);
assert(cesspool_destroy(&lookup) == 0);
}
{
cesspool_t routing;
cesspool_response_t response;
cesspool_peer_t *closest[4];
uint8_t local_prefix[2] = {192, 168}, local_id[32] = {0};
struct sockaddr_in *address;
memset(&response, 0, sizeof(response));
response.peers_len = 6;
response.peers[0] = 127; response.peers[3] = 1;
response.peers[4] = 0x0c; response.peers[5] = 0xd2;
response.local_peers_len = 4;
response.local_peers[0] = 10; response.local_peers[1] = 1;
response.local_peers[2] = 0x0c; response.local_peers[3] = 0xd2;
assert(cesspool_init(&routing, local_id) == 0);
assert(cesspool_process_response(&routing, &response, local_prefix) == 0);
assert(routing.peer_count == 2 && routing.peers[1].local);
address = (struct sockaddr_in *)&routing.peers[1].address.addr;
assert(ntohs(address->sin_port) == 3282);
assert(((uint8_t *)&address->sin_addr)[0] == 192 &&
((uint8_t *)&address->sin_addr)[1] == 168 &&
((uint8_t *)&address->sin_addr)[2] == 10 &&
((uint8_t *)&address->sin_addr)[3] == 1);
assert(cesspool_closest(&routing, local_id, closest, 4) == 2);
assert(cesspool_destroy(&routing) == 0);
}
#ifdef _WIN32
WSACleanup();
#endif
return 0;
}