Files
dat-thang/libcess/src/cesspool.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

794 lines
34 KiB
C

#define _POSIX_C_SOURCE 200112L
#include "cess_compat.h"
#include "cesspool.h"
#include <errno.h>
#include <string.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#else
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#endif
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#ifndef _WIN32
#include <unistd.h>
#endif
#ifdef CESSPOOL_HAVE_SODIUM
#include <sodium.h>
#endif
#define CESSPOOL_PACKET_BYTES 72u
#ifndef CESSPOOL_CONFIG_DIRECTORY
#define CESSPOOL_CONFIG_DIRECTORY "libcess"
#endif
#define CESSPOOL_MAX_RENDEZVOUS_PEERS 34u
#define CESSPOOL_PEER_TTL_MS 600000u
#define CESSPOOL_BOOTSTRAP_PROBE_MS 5000u
#define CESSPOOL_BOOTSTRAP_TIMEOUT_MS 15000u
/* Cesspool owns its compact protobuf-style varint framing; it has no SLEEP
* dependency even though Dat also uses the same wire primitive. */
static int cess_varint_encode(uint64_t value, uint8_t *out, size_t capacity,
size_t *written)
{
size_t n = 0;
if (!out) { errno = EINVAL; return -1; }
do {
if (n >= capacity) { errno = ENOSPC; return -1; }
out[n] = (uint8_t)(value & 0x7fu);
value >>= 7;
if (value) out[n] |= 0x80u;
n++;
} while (value);
if (written) *written = n;
return 0;
}
static int cess_varint_decode(const uint8_t *in, size_t length,
uint64_t *value, size_t *used)
{
uint64_t result = 0;
size_t i;
if (!in || !value) { errno = EINVAL; return -1; }
for (i = 0; i < length && i < 10; i++) {
uint8_t byte = in[i];
if (i == 9 && byte > 1) { errno = EOVERFLOW; return -1; }
result |= (uint64_t)(byte & 0x7fu) << (i * 7);
if (!(byte & 0x80u)) {
*value = result;
if (used) *used = i + 1;
return 0;
}
}
errno = length < 10 ? EAGAIN : EOVERFLOW;
return -1;
}
static uint64_t monotonic_ms(void)
{
struct timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) return 0;
return (uint64_t)now.tv_sec * 1000u + (uint64_t)now.tv_nsec / 1000000u;
}
static int pb_var(uint8_t tag,uint64_t value,uint8_t*out,size_t cap,size_t*off){size_t n;if(*off>=cap){errno=ENOSPC;return-1;}out[(*off)++]=tag;if(cess_varint_encode(value,out+*off,cap-*off,&n))return-1;*off+=n;return 0;}
static int pb_bytes(uint8_t tag,const uint8_t*data,size_t len,uint8_t*out,size_t cap,size_t*off){size_t n;if(*off>=cap){errno=ENOSPC;return-1;}out[(*off)++]=tag;if(cess_varint_encode(len,out+*off,cap-*off,&n))return-1;*off+=n;if(len>cap-*off){errno=ENOSPC;return-1;}memcpy(out+*off,data,len);*off+=len;return 0;}
int cesspool_request_encode(const cesspool_request_t*r,uint8_t*out,size_t cap,size_t*written){size_t off=0;if(!r||!out){errno=EINVAL;return-1;}if(pb_var(0x08,r->type,out,cap,&off))return-1;if(r->port&&pb_var(0x10,r->port,out,cap,&off))return-1;if(r->local_address_len&&pb_bytes(0x1a,r->local_address,r->local_address_len,out,cap,&off))return-1;if(written)*written=off;return 0;}
int cesspool_request_decode(cesspool_request_t*r,const uint8_t*in,size_t len){size_t off=0,n;uint64_t v;if(!r||!in){errno=EINVAL;return-1;}memset(r,0,sizeof(*r));while(off<len){uint8_t tag=in[off++];if(tag==0x08||tag==0x10){if(cess_varint_decode(in+off,len-off,&v,&n))return-1;off+=n;if(tag==0x08)r->type=(uint32_t)v;else r->port=(uint16_t)v;}else if(tag==0x1a){if(cess_varint_decode(in+off,len-off,&v,&n))return-1;off+=n;if(v>sizeof(r->local_address)||v>len-off){errno=EPROTO;return-1;}memcpy(r->local_address,in+off,(size_t)v);r->local_address_len=(size_t)v;off+=(size_t)v;}else{errno=EPROTO;return-1;}}return 0;}
int cesspool_response_encode(const cesspool_response_t *r, uint8_t *out,
size_t cap, size_t *written)
{
size_t off = 0;
if (!r || !out) { errno = EINVAL; return -1; }
if (r->peers_len && pb_bytes(0x0a, r->peers, r->peers_len, out, cap, &off)) return -1;
if (r->local_peers_len && pb_bytes(0x12, r->local_peers, r->local_peers_len, out, cap, &off)) return -1;
if (r->peer_info_len && pb_bytes(0x1a, r->peer_info, r->peer_info_len, out, cap, &off)) return -1;
if (written) *written = off;
return 0;
}
int cesspool_response_decode(cesspool_response_t *r, const uint8_t *in, size_t len)
{
size_t off = 0, n;
uint64_t v;
if (!r || !in) { errno = EINVAL; return -1; }
memset(r, 0, sizeof(*r));
while (off < len) {
uint8_t tag = in[off++], *dst;
size_t *dstlen, capacity;
if (cess_varint_decode(in + off, len - off, &v, &n)) return -1;
off += n;
if (v > len - off) { errno = EPROTO; return -1; }
if (tag == 0x0a) { dst = r->peers; dstlen = &r->peers_len; capacity = sizeof(r->peers); }
else if (tag == 0x12) { dst = r->local_peers; dstlen = &r->local_peers_len; capacity = sizeof(r->local_peers); }
else if (tag == 0x1a) { dst = r->peer_info; dstlen = &r->peer_info_len; capacity = sizeof(r->peer_info); }
else { off += (size_t)v; continue; }
if (v > capacity) { errno = EOVERFLOW; return -1; }
memcpy(dst, in + off, (size_t)v);
*dstlen = (size_t)v;
off += (size_t)v;
}
return 0;
}
int cesspool_process_response(cesspool_t*p,const cesspool_response_t*r,const uint8_t prefix[2]){size_t off;cesspool_peer_t peer;struct sockaddr_in*address;if(!p||!r){errno=EINVAL;return-1;}if(r->peers_len%6||r->local_peers_len%4){errno=EPROTO;return-1;}for(off=0;off<r->peers_len;off+=6){memset(&peer,0,sizeof(peer));address=(struct sockaddr_in*)&peer.address.addr;address->sin_family=AF_INET;memcpy(&address->sin_addr,r->peers+off,4);memcpy(&address->sin_port,r->peers+off+4,2);peer.address.addrlen=sizeof(*address);memcpy(peer.public_key,r->peers+off,6);cesspool_add_peer(p,&peer);}if(prefix)for(off=0;off<r->local_peers_len;off+=4){memset(&peer,0,sizeof(peer));address=(struct sockaddr_in*)&peer.address.addr;address->sin_family=AF_INET;((uint8_t*)&address->sin_addr)[0]=prefix[0];((uint8_t*)&address->sin_addr)[1]=prefix[1];((uint8_t*)&address->sin_addr)[2]=r->local_peers[off];((uint8_t*)&address->sin_addr)[3]=r->local_peers[off+1];memcpy(&address->sin_port,r->local_peers+off+2,2);peer.address.addrlen=sizeof(*address);peer.local=1;memcpy(peer.public_key,&address->sin_addr,4);memcpy(peer.public_key+4,&address->sin_port,2);cesspool_add_peer(p,&peer);}return 0;}
int cesspool_discovery_key(const uint8_t key[32], uint8_t topic[32])
{
if (!key || !topic) { errno = EINVAL; return -1; }
#ifdef CESSPOOL_HAVE_SODIUM
/* The root NETWORK_PROTOCOL token selects the discovery namespace.
* With "hypercore", this matches legacy Dat's discoveryKey derivation. */
return crypto_generichash(topic, 32,
(const unsigned char *)CESSPOOL_NETWORK_PROTOCOL,
strlen(CESSPOOL_NETWORK_PROTOCOL), key, 32);
#else
memcpy(topic, key, 32);
return 0;
#endif
}
int cesspool_encode(const cesspool_message_t *m, uint8_t *out,
size_t capacity, size_t *written)
{
size_t total = CESSPOOL_PACKET_BYTES;
if (!m || !out) { errno = EINVAL; return -1; }
if (m->type > CESSPOOL_HOLEPUNCH) { errno = EINVAL; return -1; }
if (m->type == CESSPOOL_REPLY) {
if (capacity < CESSPOOL_PACKET_BYTES + 2 ||
m->payload_len > UINT16_MAX ||
m->payload_len > capacity - CESSPOOL_PACKET_BYTES - 2) {
errno = ENOSPC; return -1;
}
total += 2 + m->payload_len;
}
if (capacity < total) { errno = ENOSPC; return -1; }
memcpy(out, "CSP0", 4);
out[4] = m->type;
out[5] = m->type == CESSPOOL_REPLY ? 1 : 0;
out[6] = (uint8_t)(m->port >> 8);
out[7] = (uint8_t)m->port;
memcpy(out + 8, m->topic, 32);
memcpy(out + 40, m->peer_id, 32);
if (m->type == CESSPOOL_REPLY) {
out[72] = (uint8_t)(m->payload_len >> 8);
out[73] = (uint8_t)m->payload_len;
memcpy(out + 74, m->payload, m->payload_len);
}
if (written) *written = total;
return 0;
}
int cesspool_decode(cesspool_message_t *m, const uint8_t *in, size_t len)
{
if (!m || !in) { errno = EINVAL; return -1; }
if (len < CESSPOOL_PACKET_BYTES || memcmp(in, "CSP0", 4) ||
in[4] > CESSPOOL_HOLEPUNCH) {
errno = EPROTO;
return -1;
}
memset(m, 0, sizeof(*m));
m->type = in[4];
m->port = ((uint16_t)in[6] << 8) | in[7];
memcpy(m->topic, in + 8, 32);
memcpy(m->peer_id, in + 40, 32);
if (m->type == CESSPOOL_REPLY) {
if (in[5] != 1 || len < CESSPOOL_PACKET_BYTES + 2) { errno = EPROTO; return -1; }
m->payload_len = ((size_t)in[72] << 8) | in[73];
if (m->payload_len > sizeof(m->payload) || len != CESSPOOL_PACKET_BYTES + 2 + m->payload_len) { errno = EPROTO; return -1; }
memcpy(m->payload, in + 74, m->payload_len);
} else if (in[5] != 0 || len != CESSPOOL_PACKET_BYTES) { errno = EPROTO; return -1; }
return 0;
}
static int send_message(cesspool_t *pool,const cess_peer_t *peer,uint8_t type,const uint8_t topic[32],uint16_t port)
{
cesspool_message_t message;uint8_t packet[CESSNET_MAX_PACKET];size_t len;
memset(&message,0,sizeof(message));message.type=type;message.port=port;if(topic)memcpy(message.topic,topic,32);memcpy(message.peer_id,pool->net.peerid,32);
if(cesspool_encode(&message,packet,sizeof(packet),&len))return-1;
return cessnet_send(&pool->net,peer,packet,len);
}
static int send_reply(cesspool_t *pool, const cess_peer_t *peer,
const uint8_t topic[32], const cesspool_response_t *response)
{
cesspool_message_t message;
uint8_t packet[CESSNET_MAX_PACKET];
size_t payload_len, packet_len;
memset(&message, 0, sizeof(message));
message.type = CESSPOOL_REPLY;
memcpy(message.topic, topic, CESSPOOL_TOPIC_BYTES);
memcpy(message.peer_id, pool->net.peerid, CESSPOOL_PEER_BYTES);
if (cesspool_response_encode(response, message.payload,
sizeof(message.payload), &payload_len)) return -1;
message.payload_len = payload_len;
if (cesspool_encode(&message, packet, sizeof(packet), &packet_len)) return -1;
return cessnet_send(&pool->net, peer, packet, packet_len);
}
static int send_bootstrap(cesspool_t *pool,uint8_t type,const uint8_t topic[32],uint16_t port)
{
static const uint16_t lan_ports[] = {CESSNET_PORT,3000,3002,3004,2001,2003,2005};
size_t i;int sent=0,lan_sent=0;
if (!pool) { errno = EINVAL; return -1; }
for(i=0;i<pool->bootstrap_count;i++)
if(send_message(pool,&pool->bootstrap_nodes[i],type,topic,port)==0)sent++;
/* Always keep zero-configuration LAN discovery active, even when a
* rendezvous node is configured. Routers do not forward these broadcasts;
* this complements Internet discovery and lets same-LAN peers connect
* without requiring them to share bootstrap configuration. */
for (i = 0; i < sizeof(lan_ports) / sizeof(lan_ports[0]); i++) {
cess_peer_t target;
struct sockaddr_in *address;
memset(&target, 0, sizeof(target));
address = (struct sockaddr_in *)&target.addr;
address->sin_family = AF_INET;
address->sin_addr.s_addr = htonl(INADDR_BROADCAST);
address->sin_port = htons(lan_ports[i]);
target.addrlen = sizeof(*address);
if (send_message(pool, &target, type, topic, port) == 0) lan_sent++;
}
if (pool->bootstrap_count) return (sent || lan_sent) ? 0 : -1;
return 0; /* LAN broadcasts are best-effort, never a join failure. */
}
static int has_topic(const cesspool_t *pool,const uint8_t topic[32],int announcing)
{
size_t i;
for(i=0;i<pool->topic_count;i++)if(!memcmp(pool->topics[i].key,topic,32)&&(!announcing||pool->topics[i].announcing))return 1;
return 0;
}
static int same_peer_address(const cess_peer_t *a, const cess_peer_t *b)
{
const struct sockaddr_in *aa, *bb;
if (!a || !b || a->addrlen < (socklen_t)sizeof(struct sockaddr_in) ||
b->addrlen < (socklen_t)sizeof(struct sockaddr_in)) return 0;
aa = (const struct sockaddr_in *)&a->addr;
bb = (const struct sockaddr_in *)&b->addr;
return aa->sin_family == AF_INET && bb->sin_family == AF_INET &&
aa->sin_addr.s_addr == bb->sin_addr.s_addr && aa->sin_port == bb->sin_port;
}
static int announcement_add(cesspool_t *pool, const uint8_t topic[32],
const uint8_t peer_id[32], const cess_peer_t *address,
uint64_t now)
{
size_t i;
for (i = 0; i < pool->announcement_count; i++) {
cesspool_announcement_t *entry = &pool->announcements[i];
if (!memcmp(entry->topic, topic, 32) && !memcmp(entry->peer_id, peer_id, 32)) {
entry->address = *address;
entry->last_seen = now;
return 0;
}
}
if (pool->announcement_count >= CESSPOOL_MAX_ANNOUNCEMENTS) {
errno = ENOSPC;
return -1;
}
{
cesspool_announcement_t *entry = &pool->announcements[pool->announcement_count++];
memcpy(entry->topic, topic, 32);
memcpy(entry->peer_id, peer_id, 32);
entry->address = *address;
entry->last_seen = now;
}
return 0;
}
static void announcement_remove(cesspool_t *pool, const uint8_t topic[32],
const uint8_t peer_id[32])
{
size_t i = 0;
while (i < pool->announcement_count) {
cesspool_announcement_t *entry = &pool->announcements[i];
if (!memcmp(entry->topic, topic, 32) && !memcmp(entry->peer_id, peer_id, 32)) {
pool->announcements[i] = pool->announcements[--pool->announcement_count];
continue;
}
i++;
}
}
static int peer_has_announcement(const cesspool_t *pool, const uint8_t peer_id[32])
{
size_t i;
for (i = 0; i < pool->announcement_count; i++)
if (!memcmp(pool->announcements[i].peer_id, peer_id, 32)) return 1;
return 0;
}
static int send_rendezvous_reply(cesspool_t *pool, cess_peer_t *requester,
const uint8_t topic[32])
{
cesspool_response_t response;
size_t i;
memset(&response, 0, sizeof(response));
for (i = 0; i < pool->announcement_count; i++) {
const cesspool_announcement_t *peer = &pool->announcements[i];
const struct sockaddr_in *address;
uint8_t *record;
if (memcmp(peer->topic, topic, 32) || same_peer_address(&peer->address, requester) ||
peer->address.addrlen < (socklen_t)sizeof(struct sockaddr_in)) continue;
address = (const struct sockaddr_in *)&peer->address.addr;
if (address->sin_family != AF_INET ||
response.peer_info_len / 38 >= CESSPOOL_MAX_RENDEZVOUS_PEERS ||
response.peer_info_len + 38 > sizeof(response.peer_info)) continue;
record = response.peer_info + response.peer_info_len;
memcpy(record, &address->sin_addr, 4);
memcpy(record + 4, &address->sin_port, 2);
memcpy(record + 6, peer->peer_id, 32);
response.peer_info_len += 38;
}
return send_reply(pool, requester, topic, &response);
}
static int receive_rendezvous_reply(cesspool_t *pool,
const cesspool_message_t *message)
{
cesspool_response_t response;
size_t i;
if (cesspool_response_decode(&response, message->payload, message->payload_len)) return -1;
if (response.peer_info_len % 38) { errno = EPROTO; return -1; }
for (i = 0; i < response.peer_info_len; i += 38) {
const uint8_t *record = response.peer_info + i;
cesspool_peer_t peer;
struct sockaddr_in *address;
memset(&peer, 0, sizeof(peer));
address = (struct sockaddr_in *)&peer.address.addr;
address->sin_family = AF_INET;
memcpy(&address->sin_addr, record, 4);
memcpy(&address->sin_port, record + 4, 2);
peer.address.addrlen = sizeof(*address);
memcpy(peer.public_key, record + 6, 32);
memcpy(peer.discovery_topic, message->topic, 32);
peer.discovered = 1;
peer.last_seen = monotonic_ms();
if (announcement_add(pool, message->topic, peer.public_key,
&peer.address, peer.last_seen)) continue;
if (cesspool_add_peer(pool, &peer)) return -1;
}
return 0;
}
static void cesspool_on_message(cessnet_t *net,cess_peer_t *source,const uint8_t *packet,size_t length)
{
cesspool_t *pool=(cesspool_t *)net->context;
cesspool_message_t message;
cesspool_peer_t peer;
if(!pool)return;
if(cesspool_decode(&message,packet,length)){
if(pool->on_transport_packet)pool->on_transport_packet(pool,source,packet,length);
return;
}
if(message.type>CESSPOOL_HOLEPUNCH||!memcmp(message.peer_id,net->peerid,32))return;
if (message.type == CESSPOOL_REPLY) {
size_t i;
int from_bootstrap = 0;
for (i = 0; i < pool->bootstrap_count; i++)
if (same_peer_address(&pool->bootstrap_nodes[i], source)) {
from_bootstrap = 1;
break;
}
if (!from_bootstrap) return;
/* A malformed datagram is not proof that the configured rendezvous
* endpoint is speaking Cesspool, so validate before refreshing online. */
if (receive_rendezvous_reply(pool, &message)) return;
pool->bootstrap_last_seen = monotonic_ms();
pool->bootstrap_reachable = 1;
return;
}
if (message.type == CESSPOOL_PONG) {
size_t i;
for (i = 0; i < pool->bootstrap_count; i++) {
if (same_peer_address(&pool->bootstrap_nodes[i], source)) {
pool->bootstrap_last_seen = monotonic_ms();
pool->bootstrap_reachable = 1;
break;
}
}
return;
}
memset(&peer,0,sizeof(peer));peer.address=*source;memcpy(peer.public_key,message.peer_id,32);
peer.last_seen = monotonic_ms();
if (message.type == CESSPOOL_ANNOUNCE) {
peer.discovered = 1;
memcpy(peer.discovery_topic, message.topic, sizeof(peer.discovery_topic));
if (announcement_add(pool, message.topic, message.peer_id, source,
peer.last_seen)) return;
}
cesspool_add_peer(pool,&peer);
if (message.type == CESSPOOL_UNANNOUNCE) {
cesspool_peer_t *known = cesspool_find_peer(pool, message.peer_id);
announcement_remove(pool, message.topic, message.peer_id);
if (known) known->discovered = peer_has_announcement(pool, message.peer_id);
} else if(message.type==CESSPOOL_PING)send_message(pool,source,CESSPOOL_PONG,NULL,pool->port);
else if(message.type==CESSPOOL_LOOKUP&&pool->bootstrap_server)
(void)send_rendezvous_reply(pool, source, message.topic);
else if(message.type==CESSPOOL_LOOKUP&&has_topic(pool,message.topic,1))
send_message(pool,source,CESSPOOL_ANNOUNCE,message.topic,pool->port);
else if(message.type==CESSPOOL_HOLEPUNCH&&pool->on_holepunch)pool->on_holepunch(pool,source);
}
int cesspool_init(cesspool_t *pool,const uint8_t peer_id[32])
{
uint8_t empty[32]={0};if(!pool||!peer_id){errno=EINVAL;return-1;}memset(pool,0,sizeof(*pool));if(cessnet_init(&pool->net,empty,peer_id))return-1;pool->net.context=pool;pool->net.on_message=cesspool_on_message;return 0;
}
int cesspool_add_bootstrap(cesspool_t *pool,const char *host,uint16_t port)
{
struct addrinfo hints, *resolved = NULL;
struct sockaddr_in *address;
cess_peer_t *peer;
if(!pool||!host||!port||pool->bootstrap_count>=CESSPOOL_MAX_BOOTSTRAP){errno=EINVAL;return-1;}
memset(&hints,0,sizeof(hints));hints.ai_family=AF_INET;hints.ai_socktype=SOCK_DGRAM;
if(getaddrinfo(host,NULL,&hints,&resolved)!=0||!resolved){errno=EHOSTUNREACH;return-1;}
peer=&pool->bootstrap_nodes[pool->bootstrap_count];memset(peer,0,sizeof(*peer));
memcpy(&peer->addr,resolved->ai_addr,resolved->ai_addrlen);peer->addrlen=(socklen_t)resolved->ai_addrlen;
address=(struct sockaddr_in*)&peer->addr;address->sin_port=htons(port);
freeaddrinfo(resolved);
{
size_t i;
for (i = 0; i < pool->bootstrap_count; i++)
if (same_peer_address(&pool->bootstrap_nodes[i], peer)) return 0;
}
if (pool->bootstrap_count >= CESSPOOL_MAX_BOOTSTRAP) { errno = ENOSPC; return -1; }
pool->bootstrap_count++;return 0;
}
static int add_endpoint_list(cesspool_t *pool, const char *setting)
{
char list[2048], *item, *next;
int added = 0;
if (!setting || !*setting) return 0;
if (strlen(setting) >= sizeof(list)) { errno = EOVERFLOW; return -1; }
strcpy(list, setting);
for (item = list; item && *item; item = next) {
char *separator, *end;
unsigned long port;
next = strpbrk(item, ",;\r\n");
if (next) *next++ = '\0';
separator = strrchr(item, ':');
if (!separator || separator == item || !separator[1]) continue;
*separator++ = '\0';
port = strtoul(separator, &end, 10);
if (*end || port == 0 || port > 65535) continue;
if (cesspool_add_bootstrap(pool, item, (uint16_t)port) != 0) return -1;
added++;
}
return added;
}
static int bootstrap_config_path(char *path, size_t capacity, int make_dirs)
{
const char *home = getenv("HOME");
const char *config = getenv("XDG_CONFIG_HOME");
char directory[1024], config_directory[1024];
if (!home || !*home) home = getenv("USERPROFILE");
#ifdef _WIN32
if ((!home || !*home) && getenv("HOMEDRIVE") && getenv("HOMEPATH")) {
static char combined[1024];
if (snprintf(combined, sizeof(combined), "%s%s", getenv("HOMEDRIVE"), getenv("HOMEPATH"))
< (int)sizeof(combined)) home = combined;
}
#endif
if (!home || !*home) home = ".";
if (!config || !*config) {
if (snprintf(config_directory, sizeof(config_directory), "%s/.config", home) >= (int)sizeof(config_directory)) {
errno = ENAMETOOLONG; return -1;
}
config = config_directory;
}
if (snprintf(directory, sizeof(directory), "%s/%s", config,
CESSPOOL_CONFIG_DIRECTORY) >= (int)sizeof(directory) ||
snprintf(path, capacity, "%s/bootstrap", directory) >= (int)capacity) {
errno = ENAMETOOLONG; return -1;
}
if (make_dirs) {
char parent[1024];
if (snprintf(parent, sizeof(parent), "%s", config) >= (int)sizeof(parent)) { errno = ENAMETOOLONG; return -1; }
if (mkdir(parent, 0700) && errno != EEXIST) return -1;
if (mkdir(directory, 0700) && errno != EEXIST) return -1;
}
return 0;
}
int cesspool_configure_bootstraps(cesspool_t *pool)
{
char path[1200], line[512];
FILE *file;
int count = 0, loaded;
if (!pool) { errno = EINVAL; return -1; }
loaded = add_endpoint_list(pool, getenv("CESSPOOL_BOOTSTRAP"));
if (loaded < 0) return -1;
count += loaded;
loaded = add_endpoint_list(pool, getenv("DDAT_BOOTSTRAP"));
if (loaded < 0) return -1;
count += loaded;
loaded = add_endpoint_list(pool, getenv("CABLE_BOOTSTRAP"));
if (loaded < 0) return -1;
count += loaded;
if (bootstrap_config_path(path, sizeof(path), 0)) return count;
file = fopen(path, "r");
if (!file) return errno == ENOENT ? count : -1;
while (fgets(line, sizeof(line), file)) {
line[strcspn(line, "\r\n#")] = '\0';
loaded = add_endpoint_list(pool, line);
if (loaded < 0) { fclose(file); return -1; }
count += loaded;
}
if (ferror(file)) { fclose(file); return -1; }
fclose(file);
return count;
}
int cesspool_bootstrap_config_add(const char *endpoint)
{
char path[1200], copy[512], *separator, *end;
unsigned long port;
FILE *file;
if (!endpoint || strlen(endpoint) >= sizeof(copy)) { errno = EINVAL; return -1; }
strcpy(copy, endpoint);
separator = strrchr(copy, ':');
if (!separator || separator == copy || !separator[1]) { errno = EINVAL; return -1; }
*separator++ = '\0'; port = strtoul(separator, &end, 10);
if (*end || port == 0 || port > 65535) { errno = EINVAL; return -1; }
if (bootstrap_config_path(path, sizeof(path), 1)) return -1;
file = fopen(path, "a");
if (!file) return -1;
{
int failed = fprintf(file, "%s:%lu\n", copy, port) < 0;
if (fclose(file)) failed = 1;
return failed ? -1 : 0;
}
}
int cesspool_bootstrap_config_set(const char *endpoint)
{
char path[1200], temporary[1240], copy[512], *separator, *end;
unsigned long port;
FILE *file;
int n, failed;
if (!endpoint || strlen(endpoint) >= sizeof(copy)) { errno = EINVAL; return -1; }
strcpy(copy, endpoint);
separator = strrchr(copy, ':');
if (!separator || separator == copy || !separator[1]) { errno = EINVAL; return -1; }
*separator++ = '\0';
port = strtoul(separator, &end, 10);
if (*end || port == 0 || port > 65535) { errno = EINVAL; return -1; }
if (bootstrap_config_path(path, sizeof(path), 1)) return -1;
n = snprintf(temporary, sizeof(temporary), "%s.tmp", path);
if (n < 0 || (size_t)n >= sizeof(temporary)) { errno = ENAMETOOLONG; return -1; }
file = fopen(temporary, "w");
if (!file) return -1;
failed = fprintf(file, "%s:%lu\n", copy, port) < 0;
if (fclose(file)) failed = 1;
if (failed || chmod(temporary, S_IRUSR | S_IWUSR)) {
int saved = errno;
(void)remove(temporary);
errno = saved;
return -1;
}
#ifdef _WIN32
if (!MoveFileExA(temporary, path, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) {
(void)remove(temporary);
errno = EIO;
return -1;
}
#else
if (rename(temporary, path)) {
int saved = errno;
(void)remove(temporary);
errno = saved;
return -1;
}
#endif
return 0;
}
int cesspool_bootstrap_config_get(char *endpoint, size_t capacity)
{
char path[1200], line[512];
FILE *file;
size_t length;
if (!endpoint || !capacity) { errno = EINVAL; return -1; }
endpoint[0] = '\0';
if (bootstrap_config_path(path, sizeof(path), 0)) return -1;
file = fopen(path, "r");
if (!file) return -1;
while (fgets(line, sizeof(line), file)) {
line[strcspn(line, "\r\n#")] = '\0';
if (!line[0]) continue;
length = strlen(line);
if (length >= capacity) { fclose(file); errno = ENOSPC; return -1; }
memcpy(endpoint, line, length + 1);
fclose(file);
return 0;
}
fclose(file);
errno = ENOENT;
return -1;
}
int cesspool_bootstrap_config_list(char *contents, size_t capacity)
{
char path[1200];
FILE *file;
size_t length;
if (!contents || !capacity) { errno = EINVAL; return -1; }
contents[0] = '\0';
if (bootstrap_config_path(path, sizeof(path), 0)) return -1;
file = fopen(path, "r");
if (!file) return -1;
length = fread(contents, 1, capacity - 1, file);
if (ferror(file)) { int saved = errno; fclose(file); errno = saved; return -1; }
if (!feof(file)) { fclose(file); errno = ENOSPC; return -1; }
contents[length] = '\0';
return fclose(file) == 0 ? 0 : -1;
}
int cesspool_bootstrap(cesspool_t *pool){if(!pool||!pool->bootstrap_count){errno=EINVAL;return-1;}return send_bootstrap(pool,CESSPOOL_PING,NULL,pool->port);}
int cesspool_listen(cesspool_t *pool,const char *host,uint16_t port){
static const uint16_t defaults[]={CESSNET_PORT,3000,3002,3004,2001,2003,2005};
struct sockaddr_in bound;socklen_t length=sizeof(bound);size_t i,count;
if(!pool){errno=EINVAL;return-1;}
count=port?1:sizeof(defaults)/sizeof(defaults[0]);
for(i=0;i<count;i++){
uint16_t candidate=port?port:defaults[i];
if(cessnet_bind(&pool->net,host,candidate)==0)break;
if(port||errno!=EADDRINUSE)return-1;
}
if(i==count){errno=EADDRINUSE;return-1;}
if(getsockname(pool->net.sock,(struct sockaddr*)&bound,&length)){cessnet_close(&pool->net);return-1;}
pool->port=ntohs(bound.sin_port);pool->ready=1;return 0;
}
int cesspool_set_bootstrap_server(cesspool_t *pool, int enabled)
{
if (!pool || !pool->ready) { errno = EINVAL; return -1; }
pool->bootstrap_server = !!enabled;
return 0;
}
int cesspool_announce(cesspool_t *pool,const uint8_t topic[32],uint16_t port){if(!pool||!topic){errno=EINVAL;return-1;}return send_bootstrap(pool,CESSPOOL_ANNOUNCE,topic,port?port:pool->port);}
int cesspool_lookup(cesspool_t *pool,const uint8_t topic[32]){if(!pool||!topic){errno=EINVAL;return-1;}return send_bootstrap(pool,CESSPOOL_LOOKUP,topic,0);}
int cesspool_unannounce(cesspool_t *pool,const uint8_t topic[32],uint16_t port){if(!pool||!topic){errno=EINVAL;return-1;}return send_bootstrap(pool,CESSPOOL_UNANNOUNCE,topic,port);}
int cesspool_ping(cesspool_t *pool,const cess_peer_t *peer){if(!pool||!peer){errno=EINVAL;return-1;}return send_message(pool,peer,CESSPOOL_PING,NULL,pool->port);}
int cesspool_holepunch(cesspool_t *pool,const cess_peer_t *peer){if(!pool||!peer){errno=EINVAL;return-1;}return send_message(pool,peer,CESSPOOL_HOLEPUNCH,NULL,pool->port);}
int cesspool_poll(cesspool_t *pool,int timeout_ms){if(!pool){errno=EINVAL;return-1;}return cessnet_poll(&pool->net,timeout_ms);}
cesspool_peer_t *cesspool_find_peer(cesspool_t*p,const uint8_t key[32]){size_t i;if(!p||!key)return NULL;for(i=0;i<p->peer_count;i++)if(!memcmp(p->peers[i].public_key,key,32))return&p->peers[i];return NULL;}
int cesspool_add_peer(cesspool_t*p,const cesspool_peer_t*peer){cesspool_peer_t*old;int newly_discovered;if(!p||!peer){errno=EINVAL;return-1;}old=cesspool_find_peer(p,peer->public_key);if(old){newly_discovered=peer->discovered&&(!old->discovered||memcmp(peer->discovery_topic,old->discovery_topic,32));{cesspool_peer_t merged=*peer;if(!peer->discovered&&old->discovered){merged.discovered=1;memcpy(merged.discovery_topic,old->discovery_topic,32);}*old=merged;}if(newly_discovered&&p->on_peer)p->on_peer(p,old);return 0;}if(p->peer_count>=CESSPOOL_MAX_PEERS){errno=ENOSPC;return-1;}p->peers[p->peer_count++]=*peer;if(peer->discovered&&p->on_peer)p->on_peer(p,peer);return 0;}
int cesspool_remove_peer(cesspool_t*p,const uint8_t key[32]){size_t i;if(!p||!key){errno=EINVAL;return-1;}for(i=0;i<p->peer_count;i++)if(!memcmp(p->peers[i].public_key,key,32)){p->peers[i]=p->peers[--p->peer_count];return 0;}errno=ENOENT;return-1;}
int cesspool_join(cesspool_t *p, const uint8_t topic[32], uint16_t port,
int announce, int lookup)
{
cesspool_topic_t entry;
int announced_now = 0;
if (!p || !topic || (!announce && !lookup)) { errno = EINVAL; return -1; }
if (p->topic_count >= CESSPOOL_MAX_TOPICS) { errno = ENOSPC; return -1; }
memset(&entry, 0, sizeof(entry));
memcpy(entry.key, topic, CESSPOOL_TOPIC_BYTES);
entry.port = port ? port : p->port;
entry.announcing = !!announce;
entry.looking_up = !!lookup;
/* No configured rendezvous falls back to best-effort LAN broadcast. */
if (announce) {
if (cesspool_announce(p, topic, entry.port)) return -1;
announced_now = 1;
}
if (lookup && cesspool_lookup(p, topic)) {
if (announced_now) (void)cesspool_unannounce(p, topic, entry.port);
return -1;
}
p->topics[p->topic_count++] = entry;
return 0;
}
int cesspool_leave(cesspool_t*p,const uint8_t topic[32]){size_t i;if(!p||!topic){errno=EINVAL;return-1;}for(i=0;i<p->topic_count;i++)if(!memcmp(p->topics[i].key,topic,32)){if(p->topics[i].announcing)cesspool_unannounce(p,topic,p->topics[i].port);p->topics[i]=p->topics[--p->topic_count];return 0;}errno=ENOENT;return-1;}
int cesspool_tick(cesspool_t *p, uint64_t now)
{
size_t i = 0;
uint64_t wall_now;
if (!p) { errno = EINVAL; return -1; }
for (i = 0; i < p->topic_count; i++) if (now >= p->topics[i].refresh_at) {
if (p->topics[i].announcing) (void)cesspool_announce(p, p->topics[i].key, p->topics[i].port);
if (p->topics[i].looking_up) (void)cesspool_lookup(p, p->topics[i].key);
p->topics[i].refresh_at = now + 240000u;
}
if (!p->bootstrap_server && p->bootstrap_count && now >= p->bootstrap_probe_at) {
int probed = 0;
for (i = 0; i < p->topic_count; i++) if (p->topics[i].looking_up) {
(void)cesspool_lookup(p, p->topics[i].key);
probed = 1;
}
if (!probed) (void)cesspool_bootstrap(p);
p->bootstrap_probe_at = now + CESSPOOL_BOOTSTRAP_PROBE_MS;
}
for (i = 0; i < CESSPOOL_MAX_TRANSACTIONS; i++)
if (p->transactions[i].active && now >= p->transactions[i].expires_at)
p->transactions[i].active = 0;
i = 0;
wall_now = monotonic_ms();
if (p->bootstrap_count && p->bootstrap_last_seen && wall_now >= p->bootstrap_last_seen)
p->bootstrap_reachable = wall_now - p->bootstrap_last_seen < CESSPOOL_BOOTSTRAP_TIMEOUT_MS;
else if (p->bootstrap_count) p->bootstrap_reachable = 0;
while (i < p->announcement_count) {
cesspool_announcement_t *entry = &p->announcements[i];
if (!entry->last_seen || (wall_now >= entry->last_seen &&
wall_now - entry->last_seen >= CESSPOOL_PEER_TTL_MS)) {
p->announcements[i] = p->announcements[--p->announcement_count];
continue;
}
i++;
}
/* Client peer rows are a view of live topic announcements. Without
* reconciling this bit after pruning, a peer remains "discovered" forever
* even though its rendezvous record has expired. */
for (i = 0; i < p->peer_count; i++) {
cesspool_peer_t *peer = &p->peers[i];
size_t announcement;
peer->discovered = 0;
for (announcement = 0; announcement < p->announcement_count; announcement++) {
const cesspool_announcement_t *entry = &p->announcements[announcement];
if (memcmp(peer->public_key, entry->peer_id, CESSPOOL_PEER_BYTES)) continue;
peer->discovered = 1;
memcpy(peer->discovery_topic, entry->topic, CESSPOOL_TOPIC_BYTES);
if (entry->last_seen > peer->last_seen) peer->last_seen = entry->last_seen;
break;
}
}
if (p->bootstrap_server) {
i = 0;
while (i < p->peer_count) {
cesspool_peer_t *peer = &p->peers[i];
if (!peer->last_seen || (wall_now >= peer->last_seen &&
wall_now - peer->last_seen >= CESSPOOL_PEER_TTL_MS)) {
p->peers[i] = p->peers[--p->peer_count];
continue;
}
i++;
}
}
return 0;
}
static int distance_less(const uint8_t*a,const uint8_t*b,const uint8_t target[32]){size_t i;for(i=0;i<32;i++){uint8_t da=a[i]^target[i],db=b[i]^target[i];if(da!=db)return da<db;}return 0;}
size_t cesspool_closest(cesspool_t*p,const uint8_t target[32],cesspool_peer_t**out,size_t cap){size_t i,j,n=0;if(!p||!target||(!out&&cap))return 0;for(i=0;i<p->peer_count;i++){if(n<cap)out[n++]=&p->peers[i];else if(cap&&distance_less(p->peers[i].public_key,out[n-1]->public_key,target))out[n-1]=&p->peers[i];for(j=n;j>1&&distance_less(out[j-1]->public_key,out[j-2]->public_key,target);j--){cesspool_peer_t*t=out[j-1];out[j-1]=out[j-2];out[j-2]=t;}}return n;}
size_t cesspool_topic_peers(cesspool_t *p, const uint8_t topic[32],
cesspool_peer_t **out, size_t cap)
{
size_t i, n = 0;
if (!p || !topic || (!out && cap)) return 0;
for (i = 0; i < p->announcement_count && n < cap; i++) {
cesspool_announcement_t *entry = &p->announcements[i];
cesspool_peer_t *peer;
size_t j;
if (memcmp(entry->topic, topic, 32)) continue;
for (j = 0; j < n; j++) if (!memcmp(out[j]->public_key, entry->peer_id, 32)) break;
if (j < n) continue;
peer = cesspool_find_peer(p, entry->peer_id);
if (peer) out[n++] = peer;
}
return n;
}
size_t cesspool_announced_peer_count(const cesspool_t *p)
{
size_t i, j, count = 0;
if (!p) return 0;
for (i = 0; i < p->announcement_count; i++) {
for (j = 0; j < i; j++)
if (!memcmp(p->announcements[i].peer_id, p->announcements[j].peer_id, 32)) break;
if (j == i) count++;
}
return count;
}
size_t cesspool_announced_topic_count(const cesspool_t *p)
{
size_t i, j, count = 0;
if (!p) return 0;
for (i = 0; i < p->announcement_count; i++) {
for (j = 0; j < i; j++)
if (!memcmp(p->announcements[i].topic, p->announcements[j].topic, 32)) break;
if (j == i) count++;
}
return count;
}
int cesspool_transaction_begin(cesspool_t*p,uint8_t type,const uint8_t target[32],uint64_t expires,uint16_t*out){size_t i,j,attempt;uint16_t candidate;if(!p||!target||!out){errno=EINVAL;return-1;}for(i=0;i<CESSPOOL_MAX_TRANSACTIONS;i++)if(!p->transactions[i].active){for(attempt=0;attempt<UINT16_MAX;attempt++){candidate=++p->next_transaction;if(!candidate)candidate=++p->next_transaction;for(j=0;j<CESSPOOL_MAX_TRANSACTIONS;j++)if(p->transactions[j].active&&p->transactions[j].id==candidate)break;if(j==CESSPOOL_MAX_TRANSACTIONS){cesspool_transaction_t*t=&p->transactions[i];memset(t,0,sizeof(*t));t->active=1;t->type=type;t->id=candidate;t->expires_at=expires;memcpy(t->target,target,32);*out=t->id;return 0;}}errno=ENOSPC;return-1;}errno=ENOSPC;return-1;}
int cesspool_transaction_finish(cesspool_t*p,uint16_t id){size_t i;if(!p){errno=EINVAL;return-1;}for(i=0;i<CESSPOOL_MAX_TRANSACTIONS;i++)if(p->transactions[i].active&&p->transactions[i].id==id){p->transactions[i].active=0;return 0;}errno=ENOENT;return-1;}
int cesspool_destroy(cesspool_t *pool){if(!pool){errno=EINVAL;return-1;}pool->ready=0;return cessnet_close(&pool->net);}