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>
227 lines
9.6 KiB
C++
227 lines
9.6 KiB
C++
#define _POSIX_C_SOURCE 200809L
|
|
#include "cable_hyperswarm.h"
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <io.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.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", "cable_hyperswarm.js"};
|
|
char module[MAX_PATH * 4], sibling[MAX_PATH * 4];
|
|
size_t i;
|
|
if (configured && *configured) {
|
|
if (strlen(configured) >= capacity) { errno = ENAMETOOLONG; return NULL; }
|
|
strcpy(path, configured);
|
|
return _access(path, 4) == 0 ? path : NULL;
|
|
}
|
|
for (i = 0; i < sizeof(candidates) / sizeof(candidates[0]); i++)
|
|
if (_access(candidates[i], 4) == 0) {
|
|
if (strlen(candidates[i]) >= capacity) { errno = ENAMETOOLONG; return NULL; }
|
|
strcpy(path, candidates[i]);
|
|
return path;
|
|
}
|
|
{
|
|
DWORD length = GetModuleFileNameA(NULL, module, (DWORD)sizeof(module));
|
|
char *separator;
|
|
int n;
|
|
if (length && length < sizeof(module) && (separator = strrchr(module, '\\')) != NULL) {
|
|
*separator = '\0';
|
|
n = snprintf(sibling, sizeof(sibling), "%s\\cable_hyperswarm.js", module);
|
|
if (n >= 0 && (size_t)n < sizeof(sibling) && _access(sibling, 4) == 0 &&
|
|
strlen(sibling) < capacity) { strcpy(path, sibling); 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, args[5];
|
|
const char *serialized;
|
|
const char *values[4] = {a,b,c,d};
|
|
size_t length, offset = 0;
|
|
int count = 1, 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);
|
|
for (i = 0; i < 4 && values[i]; i++) args[count++] = s7_make_string(sc, values[i]);
|
|
form = s7_nil(sc);
|
|
while (count) form = s7_cons(sc, args[--count], 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) {
|
|
unsigned int chunk = length - offset > 0x7fffffffu ? 0x7fffffffu : (unsigned int)(length - offset);
|
|
int written = _write(bridge->input_fd, serialized + offset, chunk);
|
|
if (written <= 0) { errno = EPIPE; 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;
|
|
char *terminated;
|
|
if (length > 8192) { errno = EMSGSIZE; return -1; }
|
|
terminated = (char *)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)
|
|
{
|
|
SECURITY_ATTRIBUTES security;
|
|
HANDLE child_in = NULL, parent_in = NULL, parent_out = NULL, child_out = NULL;
|
|
STARTUPINFOA startup;
|
|
PROCESS_INFORMATION process;
|
|
char script[2048], command[4096];
|
|
const char *node = getenv("CABLE_NODE");
|
|
const char *dir = getenv("DAT_SCHEME_DIR");
|
|
char scheme_file[2048];
|
|
char module[MAX_PATH * 4];
|
|
int n;
|
|
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; }
|
|
if (dir && *dir) n = snprintf(scheme_file, sizeof(scheme_file), "%s\\cable-room.scm", dir);
|
|
else if (_access("scheme/cable-room.scm", 4) == 0) n = snprintf(scheme_file, sizeof(scheme_file), "scheme\\cable-room.scm");
|
|
else {
|
|
DWORD length = GetModuleFileNameA(NULL, module, (DWORD)sizeof(module));
|
|
char *separator = length && length < sizeof(module) ? strrchr(module, '\\') : NULL;
|
|
if (separator) { *separator = '\0'; n = snprintf(scheme_file, sizeof(scheme_file), "%s\\scheme\\cable-room.scm", module); }
|
|
else n = snprintf(scheme_file, sizeof(scheme_file), "share\\datgpt\\scheme\\cable-room.scm");
|
|
}
|
|
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;
|
|
}
|
|
security.nLength = sizeof(security); security.lpSecurityDescriptor = NULL; security.bInheritHandle = TRUE;
|
|
if (!CreatePipe(&child_in, &parent_in, &security, 0) || !CreatePipe(&parent_out, &child_out, &security, 0)) goto fail;
|
|
SetHandleInformation(parent_in, HANDLE_FLAG_INHERIT, 0);
|
|
SetHandleInformation(parent_out, HANDLE_FLAG_INHERIT, 0);
|
|
memset(&startup, 0, sizeof(startup)); startup.cb = sizeof(startup);
|
|
startup.dwFlags = STARTF_USESTDHANDLES;
|
|
startup.hStdInput = child_in; startup.hStdOutput = child_out; startup.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
|
memset(&process, 0, sizeof(process));
|
|
n = snprintf(command, sizeof(command), "\"%s\" \"%s\"", node, script);
|
|
if (n < 0 || (size_t)n >= sizeof(command) ||
|
|
!CreateProcessA(NULL, command, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &startup, &process)) {
|
|
errno = ENOENT; goto fail;
|
|
}
|
|
CloseHandle(child_in); child_in = NULL;
|
|
CloseHandle(child_out); child_out = NULL;
|
|
CloseHandle(process.hThread);
|
|
bridge->process_handle = process.hProcess;
|
|
bridge->input_fd = _open_osfhandle((intptr_t)parent_in, _O_BINARY | _O_WRONLY); parent_in = NULL;
|
|
bridge->output_fd = _open_osfhandle((intptr_t)parent_out, _O_BINARY | _O_RDONLY); parent_out = NULL;
|
|
if (bridge->input_fd < 0 || bridge->output_fd < 0) { errno = EIO; goto fail; }
|
|
if (send_form(bridge, "start", room_key, storage, nick, channel)) goto fail;
|
|
return 0;
|
|
fail:
|
|
if (child_in) CloseHandle(child_in);
|
|
if (parent_in) CloseHandle(parent_in);
|
|
if (parent_out) CloseHandle(parent_out);
|
|
if (child_out) CloseHandle(child_out);
|
|
if (bridge->input_fd >= 0) _close(bridge->input_fd);
|
|
if (bridge->output_fd >= 0) _close(bridge->output_fd);
|
|
if (bridge->process_handle) { TerminateProcess(bridge->process_handle, 1); WaitForSingleObject(bridge->process_handle, INFINITE); CloseHandle(bridge->process_handle); }
|
|
bridge->input_fd = bridge->output_fd = -1; bridge->process_handle = NULL;
|
|
dat_scheme_runtime_close(&bridge->scheme);
|
|
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];
|
|
HANDLE pipe;
|
|
if (!bridge || bridge->output_fd < 0 || !callback) { errno = EINVAL; return -1; }
|
|
pipe = (HANDLE)_get_osfhandle(bridge->output_fd);
|
|
for (;;) {
|
|
DWORD available = 0, received = 0;
|
|
size_t i;
|
|
if (!PeekNamedPipe(pipe, NULL, 0, NULL, &available, NULL)) { errno = EPIPE; return -1; }
|
|
if (!available) break;
|
|
if (!ReadFile(pipe, incoming, available < sizeof(incoming) ? available : (DWORD)sizeof(incoming), &received, NULL)) { errno = EPIPE; return -1; }
|
|
for (i = 0; i < (size_t)received; 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 (bridge->process_handle && WaitForSingleObject(bridge->process_handle, 0) == WAIT_OBJECT_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->process_handle) {
|
|
if (WaitForSingleObject(bridge->process_handle, 200) == WAIT_TIMEOUT) {
|
|
TerminateProcess(bridge->process_handle, 1);
|
|
WaitForSingleObject(bridge->process_handle, INFINITE);
|
|
}
|
|
CloseHandle(bridge->process_handle);
|
|
}
|
|
dat_scheme_runtime_close(&bridge->scheme);
|
|
memset(bridge, 0, sizeof(*bridge)); bridge->input_fd = -1; bridge->output_fd = -1;
|
|
}
|