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>
794 lines
34 KiB
C
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);}
|