Files
dat-thang/tests/interop/cabal/cable_hyperswarm.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

227 lines
8.0 KiB
C

#ifdef _WIN32
#include "cable_hyperswarm_win.inc"
#else
#define _POSIX_C_SOURCE 200809L
#include "cable_hyperswarm.h"
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <sodium.h>
#include "cable_media.h"
static const char *find_bridge_script(char *path, size_t capacity)
{
const char *configured = getenv("CABLE_HYPERSWARM_BRIDGE");
static const char *candidates[] = {
"src/cable_hyperswarm.js", "./cable_hyperswarm.js",
"/usr/local/share/datgpt/cable_hyperswarm.js",
"/usr/share/datgpt/cable_hyperswarm.js"
};
size_t i;
if (configured && *configured) {
if (strlen(configured) >= capacity) { errno = ENAMETOOLONG; return NULL; }
strcpy(path, configured);
return access(path, R_OK) == 0 ? path : NULL;
}
for (i = 0; i < sizeof(candidates) / sizeof(candidates[0]); i++)
if (access(candidates[i], R_OK) == 0) {
if (strlen(candidates[i]) >= capacity) { errno = ENAMETOOLONG; return NULL; }
strcpy(path, candidates[i]);
return path;
}
errno = ENOENT;
return NULL;
}
static int send_form(cable_hyperswarm_t *bridge, const char *verb,
const char *a, const char *b, const char *c, const char *d)
{
s7_scheme *sc;
s7_pointer port, form;
const char *serialized;
size_t length, offset = 0;
const char *values[4] = {a, b, c, d};
s7_pointer args[5];
int i;
if (!bridge || !bridge->scheme.scheme || bridge->input_fd < 0 || !verb) { errno = EINVAL; return -1; }
sc = bridge->scheme.scheme;
args[0] = s7_make_symbol(sc, verb);
length = 1;
for (i = 0; i < 4 && values[i]; i++) args[length++] = s7_make_string(sc, values[i]);
form = s7_nil(sc);
while (length) form = s7_cons(sc, args[--length], form);
port = s7_open_output_string(sc);
if (!port) { errno = ENOMEM; return -1; }
(void)s7_write(sc, form, port);
serialized = s7_get_output_string(sc, port);
if (!serialized) { errno = EIO; return -1; }
length = strlen(serialized);
while (offset < length) {
ssize_t written = write(bridge->input_fd, serialized + offset, length - offset);
if (written < 0 && errno == EINTR) continue;
if (written <= 0) return -1;
offset += (size_t)written;
}
return write(bridge->input_fd, "\n", 1) == 1 ? 0 : -1;
}
static int parse_event_line(cable_hyperswarm_t *bridge, const char *line,
size_t length, cable_hyperswarm_event_fn callback,
void *context)
{
s7_scheme *sc = bridge->scheme.scheme;
s7_pointer parser, event;
if (length > 8192) { errno = EMSGSIZE; return -1; }
{
char *terminated = malloc(length + 1);
if (!terminated) return -1;
memcpy(terminated, line, length); terminated[length] = '\0';
parser = s7_name_to_value(sc, "cable:parse-bridge-event");
event = s7_call(sc, parser, s7_list(sc, 1, s7_make_string(sc, terminated)));
free(terminated);
}
if (!event || s7_is_eq(event, s7_f(sc))) { errno = EPROTO; return -1; }
callback(context, event);
return 0;
}
int cable_hyperswarm_start(cable_hyperswarm_t *bridge, const char *room_key,
const char *storage, const char *nick,
const char *channel)
{
int child_in[2] = {-1, -1}, child_out[2] = {-1, -1};
char script[2048];
const char *node = getenv("CABLE_NODE");
pid_t pid;
if (!bridge || !room_key || !storage || !nick || !channel) { errno = EINVAL; return -1; }
memset(bridge, 0, sizeof(*bridge)); bridge->input_fd = -1; bridge->output_fd = -1;
if (!find_bridge_script(script, sizeof(script))) return -1;
if (!node || !*node) node = "node";
if (dat_scheme_runtime_init(&bridge->scheme)) { errno = EINVAL; return -1; }
{
char scheme_file[2048];
const char *dir = getenv("DAT_SCHEME_DIR");
int n;
if (!dir || !*dir) dir = access("scheme/cable-room.scm", R_OK) == 0 ? "scheme" : "/usr/local/share/datgpt/scheme";
n = snprintf(scheme_file, sizeof(scheme_file), "%s/cable-room.scm", dir);
if (n < 0 || (size_t)n >= sizeof(scheme_file) || !s7_load(bridge->scheme.scheme, scheme_file)) {
dat_scheme_runtime_close(&bridge->scheme); errno = ENOENT; return -1;
}
}
if (pipe(child_in) || pipe(child_out)) goto fail;
pid = fork();
if (pid < 0) goto fail;
if (pid == 0) {
(void)signal(SIGPIPE, SIG_DFL);
(void)dup2(child_in[0], STDIN_FILENO);
(void)dup2(child_out[1], STDOUT_FILENO);
close(child_in[0]); close(child_in[1]); close(child_out[0]); close(child_out[1]);
execlp(node, node, script, (char *)NULL);
_exit(127);
}
/* A sidecar shutdown must become EPIPE here, not terminate SDL. */
(void)signal(SIGPIPE, SIG_IGN);
close(child_in[0]); child_in[0] = -1;
close(child_out[1]); child_out[1] = -1;
bridge->pid = pid; bridge->input_fd = child_in[1]; bridge->output_fd = child_out[0];
if (fcntl(bridge->output_fd, F_SETFL, fcntl(bridge->output_fd, F_GETFL) | O_NONBLOCK) < 0 ||
send_form(bridge, "start", room_key, storage, nick, channel)) goto fail;
return 0;
fail:
{
int saved = errno;
if (child_in[0] >= 0) close(child_in[0]);
if (child_in[1] >= 0) close(child_in[1]);
if (child_out[0] >= 0) close(child_out[0]);
if (child_out[1] >= 0) close(child_out[1]);
if (bridge->pid > 0) { kill(bridge->pid, SIGTERM); (void)waitpid(bridge->pid, NULL, 0); }
bridge->pid = 0; bridge->input_fd = -1; bridge->output_fd = -1;
dat_scheme_runtime_close(&bridge->scheme); errno = saved; return -1;
}
}
int cable_hyperswarm_send(cable_hyperswarm_t *bridge, const char *channel, const char *text)
{ return send_form(bridge, "send", channel, text, NULL, NULL); }
int cable_hyperswarm_nick(cable_hyperswarm_t *bridge, const char *nick)
{ return send_form(bridge, "nick", nick, NULL, NULL, NULL); }
int cable_hyperswarm_join(cable_hyperswarm_t *bridge, const char *channel)
{ return send_form(bridge, "join", channel, NULL, NULL, NULL); }
int cable_hyperswarm_live(cable_hyperswarm_t *bridge,
const uint8_t *packet, size_t packet_length)
{
size_t capacity;
char *encoded;
int result;
if (!packet || !packet_length || packet_length > CABLE_MEDIA_MAX_PACKET_BYTES) {
errno = EMSGSIZE; return -1;
}
capacity = sodium_base64_encoded_len(packet_length, sodium_base64_VARIANT_ORIGINAL);
encoded = (char *)malloc(capacity);
if (!encoded) return -1;
if (!sodium_bin2base64(encoded, capacity, packet, packet_length,
sodium_base64_VARIANT_ORIGINAL)) {
free(encoded); errno = EINVAL; return -1;
}
result = send_form(bridge, "live", encoded, NULL, NULL, NULL);
free(encoded);
return result;
}
int cable_hyperswarm_poll(cable_hyperswarm_t *bridge,
cable_hyperswarm_event_fn callback, void *context)
{
char incoming[4096];
ssize_t got;
if (!bridge || bridge->output_fd < 0 || !callback) { errno = EINVAL; return -1; }
while ((got = read(bridge->output_fd, incoming, sizeof(incoming))) > 0) {
size_t i;
for (i = 0; i < (size_t)got; i++) {
if (incoming[i] == '\n') {
if (parse_event_line(bridge, bridge->pending, bridge->pending_length, callback, context)) return -1;
bridge->pending_length = 0;
} else {
if (bridge->pending_length + 1 >= sizeof(bridge->pending)) { errno = EMSGSIZE; return -1; }
bridge->pending[bridge->pending_length++] = incoming[i];
}
}
}
if (got < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) return -1;
if (bridge->pid > 0) {
int status;
pid_t ended = waitpid(bridge->pid, &status, WNOHANG);
if (ended == bridge->pid) { bridge->pid = 0; errno = EPIPE; return -1; }
}
return 0;
}
void cable_hyperswarm_stop(cable_hyperswarm_t *bridge)
{
if (!bridge) return;
if (bridge->input_fd >= 0) { (void)send_form(bridge, "close", NULL, NULL, NULL, NULL); close(bridge->input_fd); }
if (bridge->output_fd >= 0) close(bridge->output_fd);
if (bridge->pid > 0) {
int status;
unsigned tries;
for (tries = 0; tries < 20; tries++) {
pid_t ended = waitpid(bridge->pid, &status, WNOHANG);
if (ended == bridge->pid || (ended < 0 && errno == ECHILD)) break;
{
struct timespec delay = {0, 10000000L};
(void)nanosleep(&delay, NULL);
}
}
if (tries == 20) { kill(bridge->pid, SIGTERM); (void)waitpid(bridge->pid, &status, 0); }
}
dat_scheme_runtime_close(&bridge->scheme);
memset(bridge, 0, sizeof(*bridge)); bridge->input_fd = -1; bridge->output_fd = -1;
}
#endif