#include "cesspool.h" #include "dat_wire.h" #include "sleep_crypto.h" #include "sleep.h" #include #include #ifdef _WIN32 #include #include #else #include #include #endif #include #include #ifndef _WIN32 #include #endif #ifdef _WIN32 #include typedef SOCKET test_socket_t; #else #include 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; }