]> git.plutz.net Git - quickjs_net/commitdiff
initial commit
authorPaul Hänsch <paul@plutz.net>
Tue, 10 Mar 2026 20:20:57 +0000 (21:20 +0100)
committerPaul Hänsch <paul@plutz.net>
Tue, 10 Mar 2026 20:20:57 +0000 (21:20 +0100)
socket.c [new file with mode: 0644]

diff --git a/socket.c b/socket.c
new file mode 100644 (file)
index 0000000..9c3d58e
--- /dev/null
+++ b/socket.c
@@ -0,0 +1,327 @@
+#include <quickjs/quickjs.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <sys/ioctl.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#define countof(x) ( sizeof(x) / sizeof(x[0]) )
+#define libc_error(ctx) ( JS_ThrowInternalError(ctx, "%s", strerror(errno)) )
+
+static JSValue js_os_socket( JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv) {
+  int family, type, protocol, fd;
+
+  if ( argc >= 3
+    && ! JS_ToInt32(ctx, &family,   argv[0])
+    && ! JS_ToInt32(ctx, &type,     argv[1])
+    && ! JS_ToInt32(ctx, &protocol, argv[2])
+    && ( fd = socket(family, type, protocol))
+  ) {
+    return JS_NewInt32(ctx, fd);
+  } else {
+    return JS_EXCEPTION;
+  }
+}
+
+static JSValue js_os_bind(   JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv, int con) {
+  struct addrinfo *addr, *waddr;
+  struct sockaddr_un addr_un = { .sun_family = AF_UNIX };
+  int fd, b = -1; const char *host, *port, *path;
+
+  if ( argc < 2 ) return JS_EXCEPTION;
+  if ( JS_ToInt32(ctx, &fd, argv[0]) ) return JS_EXCEPTION;
+
+  if        ( argc == 2 ) {
+    path = JS_ToCString(ctx, argv[1]);
+    if (strlen(path) < sizeof(addr_un.sun_path)) {
+      strcpy((char *) &(addr_un.sun_path), path);
+      if (!con) b = bind(fd, (struct sockaddr *) &addr_un, sizeof(addr_un));
+      else   b = connect(fd, (struct sockaddr *) &addr_un, sizeof(addr_un));
+    }
+    JS_FreeCString(ctx, path);
+    if (b) return libc_error(ctx); else return JS_UNDEFINED;
+
+  } else {  // argc >= 3
+    host = JS_ToCString(ctx, argv[1]);
+    port = JS_ToCString(ctx, argv[2]);
+    if (! getaddrinfo(host, port, NULL, &addr))
+      waddr = addr;
+    JS_FreeCString(ctx, host); JS_FreeCString(ctx, port);
+
+    while (waddr) {
+      if (!con) b = bind(fd, addr->ai_addr, addr->ai_addrlen);
+      else   b = connect(fd, addr->ai_addr, addr->ai_addrlen);
+      if (!b) break;
+      waddr = waddr->ai_next;
+    }
+    freeaddrinfo(addr);
+    if (b) return libc_error(ctx); else return JS_UNDEFINED;
+  }
+}
+
+static JSValue js_os_listen( JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv) {
+  int fd, backlog = 1;
+
+  if (argc < 1 ) return JS_EXCEPTION;
+  if ( JS_ToInt32(ctx, &fd, argv[0]) )
+    return JS_EXCEPTION;
+  if ( argc >= 2 && JS_ToInt32(ctx, &backlog, argv[1]) )
+    return JS_EXCEPTION;
+
+  if (! listen(fd, backlog)) return JS_UNDEFINED;
+  else return libc_error(ctx);
+}
+
+static JSValue js_os_accept( JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv) {
+  struct sockaddr * peer; socklen_t ps = sizeof(struct sockaddr);
+  char host[40], port[6];
+  int fd, new;
+
+  if ( argc < 1 ) return JS_EXCEPTION;
+  if ( JS_ToInt32(ctx, &fd, argv[0]) ) return JS_EXCEPTION;
+
+  if ( argc >= 2 ) {
+    peer = malloc(ps);
+    if ((new = accept(fd, peer, &ps)) != -1) {
+      JS_SetPropertyStr(ctx, argv[1], "protocol", JS_NewInt32(ctx, peer->sa_family));
+      if ( peer->sa_family != AF_UNIX ) {
+        getnameinfo(peer, ps, host, 40, port, 6, NI_NUMERICHOST | NI_NUMERICSERV);
+        JS_SetPropertyStr(ctx, argv[1], "host", JS_NewString(ctx, host));
+        JS_SetPropertyStr(ctx, argv[1], "port", JS_NewString(ctx, port));
+      }
+    }
+    free(peer);
+  } else {
+    new = accept(fd, NULL, 0);
+  }
+  if (new != -1)
+    return JS_NewInt32(ctx, new);
+  else return JS_EXCEPTION;
+}
+
+static const JSCFunctionListEntry os_socket_funcs[] = {
+  JS_CFUNC_DEF("socket",  3, js_os_socket),
+  JS_CFUNC_DEF("listen",  1, js_os_listen),
+  JS_CFUNC_DEF("accept",  1, js_os_accept),
+  JS_CFUNC_MAGIC_DEF("bind",    2, js_os_bind, 0),
+  JS_CFUNC_MAGIC_DEF("connect", 2, js_os_bind, 1),
+  // JS_CFUNC_DEF("send",  2, js_os_send),   // would be same as os.write
+  // JS_CFUNC_DEF("recv",  2, js_os_recv),   // would be same as os.read
+  // JS_CFUNC_DEF("close", 1, js_os_close),  // would be same as os.close
+};
+
+static JSClassID socket_cid;
+
+typedef struct {
+  int fd;
+  struct sockaddr bind;
+  struct sockaddr peer;
+} SocketData;
+
+static JSValue sock_accept(JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv) {
+  SocketData * data = JS_GetOpaque2(ctx, this, socket_cid);
+  SocketData * newdata = js_mallocz(ctx, sizeof(SocketData));
+  JSValue new = JS_NewObjectClass(ctx, socket_cid);
+  socklen_t ps = sizeof(struct sockaddr);
+
+  if ( newdata
+    && (newdata->fd = accept(data->fd, &(newdata->peer), &ps)) >= 0
+  ) {
+    memcpy(&(newdata->bind), &(data->bind), sizeof(struct sockaddr));
+    JS_SetOpaque(new, newdata);
+  } else {
+    new = libc_error(ctx);
+    js_free(ctx, newdata);
+    JS_FreeValue(ctx, new);
+  }
+
+  return new;
+}
+static JSValue sock_send(  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv) {
+  SocketData * data = JS_GetOpaque2(ctx, this, socket_cid);
+  uint8_t *buf; size_t len;
+
+  if ( ! data->peer.sa_family ) {
+    return JS_ThrowInternalError(ctx, "Socket not connected");
+  } else if ( (buf = JS_GetArrayBuffer(ctx, &len, argv[0])) ) {
+    len = send(data->fd, buf, len, 0);
+  } else if ( (buf = (uint8_t*) JS_ToCStringLen(ctx, &len, argv[0])) ) {
+    len = send(data->fd, buf, len, 0);
+    JS_FreeCString(ctx, (char *) buf);
+  } else return JS_ThrowTypeError(ctx,
+    "expected ArrayBuffer or String"
+  );
+
+  if ((int)len < 0) return libc_error(ctx);
+  else return JS_NewInt32(ctx, len);
+}
+
+static JSValue sock_recv(  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv, int str) {
+  SocketData * data = JS_GetOpaque2(ctx, this, socket_cid);
+  uint8_t *buf; int len; JSValue jbuf;
+
+  if ( (JS_ToInt32(ctx, &len, argv[0]) || len <= 0)
+    && ioctl(data->fd, FIONREAD, &len)
+  ) return libc_error(ctx);
+
+  if (len == 0 && str)
+    return JS_NewStringLen(ctx, NULL, 0);
+  else if (len == 0)
+    return JS_NewArrayBufferCopy(ctx, NULL, 0);
+  else if ( len > 0
+    && (buf = malloc(len))
+    && ((len = recv(data->fd, buf, len, 0)) >= 0)
+  ) {
+    if (str) jbuf = JS_NewStringLen(ctx, (char*) buf, len);
+    else     jbuf = JS_NewArrayBufferCopy(ctx, buf, len);
+    free(buf);
+    return jbuf;
+  } else {
+    free(buf);
+    return libc_error(ctx);
+  }
+}
+static JSValue sock_close( JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv) {
+  SocketData *data = JS_GetOpaque2(ctx, this, socket_cid);
+
+  if ( close(data->fd) ) return libc_error(ctx);
+  else return JS_UNDEFINED;
+}
+static void sock_destroy(JSRuntime *rt, JSValue this){
+  SocketData *data = JS_GetOpaque(this, socket_cid);
+  // close(data->fd);  // do not do what a c programmer wouldn't do
+  js_free_rt(rt, data);
+};
+
+static const JSCFunctionListEntry socket_ptype[] = {
+  JS_CFUNC_DEF("accept", 0, sock_accept),
+  JS_CFUNC_DEF("send",   1, sock_send),
+  JS_CFUNC_DEF("close",  0, sock_close),
+  JS_CFUNC_MAGIC_DEF("recv",       0, sock_recv, 0),
+  JS_CFUNC_MAGIC_DEF("recvString", 0, sock_recv, 1),
+};
+
+static JSClassDef socket_class = {
+  "Socket", .finalizer = sock_destroy
+};
+
+
+static int ai_socket(struct addrinfo *address) {
+  return socket(address->ai_family,
+                address->ai_socktype,
+                address->ai_protocol
+               );
+}
+
+static int ai_bind(int sock, struct addrinfo *address) {
+  return bind(sock,
+              address->ai_addr,
+              address->ai_addrlen
+             );
+}
+
+static int ai_connect(int sock, struct addrinfo *address) {
+  return connect(sock,
+                 address->ai_addr,
+                 address->ai_addrlen
+                );
+}
+
+static JSValue udp_listen( JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv) {
+  return JS_UNDEFINED;
+}
+static JSValue tcp_listen( JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv, int family) {
+  struct addrinfo hints = { .ai_family = family, .ai_socktype = SOCK_STREAM };
+  struct addrinfo * self;
+  const char *host, *port;
+  SocketData *data = js_mallocz(ctx, sizeof(*data));
+  JSValue new = JS_NewObjectClass(ctx, socket_cid);
+
+  host = JS_ToCString(ctx, argv[0]);
+  port = JS_ToCString(ctx, argv[1]);
+
+  if ( data && (data->fd = -1)
+    && !getaddrinfo( host, port, &hints, &self)
+    && (data->fd = ai_socket(self)) >= 0
+    && !ai_bind(data->fd, self)
+    && !listen(data->fd, 1)
+  ) {
+    memcpy(&(data->bind), self->ai_addr, sizeof(struct sockaddr));
+    JS_SetOpaque(new, data);
+  } else {
+    JS_FreeValue(ctx, new);
+    if (data->fd >= 0) close(data->fd);
+    js_free(ctx, data);
+    new = libc_error(ctx);
+  }
+  freeaddrinfo(self);
+  JS_FreeCString(ctx, host); JS_FreeCString(ctx, port);
+
+  return new;
+}
+static JSValue unix_listen(JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv) {
+  return JS_UNDEFINED;
+}
+
+static JSValue udp_connect( JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv) {
+  return JS_UNDEFINED;
+}
+static JSValue tcp_connect( JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv) {
+  return JS_UNDEFINED;
+}
+static JSValue unix_connect(JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv) {
+  return JS_UNDEFINED;
+}
+
+static const JSCFunctionListEntry net_funcs[] = {
+  JS_PROP_INT32_DEF("AF_UNIX",       AF_UNIX,       JS_PROP_CONFIGURABLE),
+  JS_PROP_INT32_DEF("AF_INET",       AF_INET,       JS_PROP_CONFIGURABLE),
+  JS_PROP_INT32_DEF("AF_INET6",      AF_INET6,      JS_PROP_CONFIGURABLE),
+  JS_PROP_INT32_DEF("SOCK_STREAM",   SOCK_STREAM,   JS_PROP_CONFIGURABLE),
+  JS_PROP_INT32_DEF("SOCK_DGRAM",    SOCK_DGRAM,    JS_PROP_CONFIGURABLE),
+  JS_PROP_INT32_DEF("SOCK_NONBLOCK", SOCK_NONBLOCK, JS_PROP_CONFIGURABLE),
+  JS_CFUNC_DEF("udpListen",   2, udp_listen),
+  JS_CFUNC_MAGIC_DEF("tcpListen",   2, tcp_listen, AF_INET),
+  JS_CFUNC_MAGIC_DEF("tcp6Listen",  2, tcp_listen, AF_INET6),
+  JS_CFUNC_DEF("unixListen",  1, unix_listen),
+  JS_CFUNC_DEF("udpConnect",  2, udp_connect),
+  JS_CFUNC_DEF("tcpConnect",  2, tcp_connect),
+  JS_CFUNC_DEF("unixConnect", 1, unix_connect),
+};
+
+// static const JSCFunctionListEntry net_obj[] = {
+//   JS_OBJECT_DEF("net", net_funcs, countof(net_funcs), JS_PROP_CONFIGURABLE)
+// };
+
+static int sock_modinit(JSContext *ctx, JSModuleDef *mod ) {
+  JSValue proto = JS_NewObject(ctx);
+
+  JS_NewClassID(&socket_cid);
+  JS_NewClass(JS_GetRuntime(ctx), socket_cid, &socket_class);
+
+  JS_SetPropertyFunctionList(ctx, proto, socket_ptype, countof(socket_ptype));
+  JS_SetClassProto(ctx, socket_cid, proto);
+
+  JS_SetModuleExportList(ctx, mod, net_funcs, countof(net_funcs));
+  return 0;
+}
+
+JSModuleDef *js_init_module(JSContext *ctx, const char *name) {
+  JSValue global = JS_GetGlobalObject(ctx);
+  JSValue os = JS_GetPropertyStr(ctx, global, "os");
+  JSModuleDef *mod = JS_NewCModule(ctx, name, sock_modinit);
+
+  if (mod) {
+    JS_AddModuleExportList(ctx, mod, net_funcs, countof(net_funcs));
+    // JS_SetPropertyFunctionList(ctx, global, net_obj, countof(net_obj));
+    if ( !JS_IsUndefined(os) )
+      JS_SetPropertyFunctionList(ctx, os, os_socket_funcs, countof(os_socket_funcs));
+  }
+  JS_FreeValue(ctx, global); JS_FreeValue(ctx, os);
+
+  return mod;
+}