]> git.plutz.net Git - quickjs_net/commitdiff
separate socket logic from js handling
authorPaul Hänsch <paul@plutz.net>
Sun, 15 Mar 2026 22:39:13 +0000 (23:39 +0100)
committerPaul Hänsch <paul@plutz.net>
Sun, 15 Mar 2026 22:39:13 +0000 (23:39 +0100)
socket.c

index 43858f1dc4aafa02a9fe53a29370591822f76d17..31ad4cb60d8b64824e6d700e24ad343ceedea692 100644 (file)
--- a/socket.c
+++ b/socket.c
@@ -8,18 +8,11 @@
 #include <unistd.h>
 #include <string.h>
 #include <errno.h>
+#include <fcntl.h>
 
 #define countof(x) ( sizeof(x) / sizeof(x[0]) )
 #define libc_error(ctx) ( JS_ThrowInternalError(ctx, "%s", strerror(errno)) )
 
-// use struct addrinfo in socket functions
-#define ai_socket(a) ( socket( (a)->ai_family, \
-                               (a)->ai_socktype, \
-                               (a)->ai_protocol \
-                     ) )
-#define    ai_bind(s, a) (    bind((s), (a)->ai_addr, (a)->ai_addrlen) )
-#define ai_connect(s, a) ( connect((s), (a)->ai_addr, (a)->ai_addrlen) )
-
 static JSValue js_os_socket(
   JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv
 ) {
@@ -130,10 +123,12 @@ static const JSCFunctionListEntry os_socket_funcs[] = {
 };
 
 static JSClassID socket_cid;
+static double net_timeout = -1;
 
 typedef struct {
   int fd;
   int type;
+  int connected;
   double timeout;
   struct sockaddr_storage bind;
   struct sockaddr_storage peer;
@@ -143,7 +138,7 @@ static JSValue sock_set_timeout( JSContext *ctx, JSValueConst this, JSValueConst
   SocketData * data = JS_GetOpaque2(ctx, this, socket_cid);
   struct timeval t; double delay;
 
-  if ( JS_IsNull(val)
+  if (!JS_IsNumber(val)
     || JS_ToFloat64(ctx, &delay, val) || delay < 0) {
     t.tv_sec = 0; t.tv_usec = 0;
     delay = -1;
@@ -155,8 +150,17 @@ static JSValue sock_set_timeout( JSContext *ctx, JSValueConst this, JSValueConst
   if ( setsockopt(data->fd, SOL_SOCKET, SO_SNDTIMEO, &t, sizeof(t))
     || setsockopt(data->fd, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(t))
   ) return libc_error(ctx);
+
   data->timeout = delay;
 
+  if ( delay == 0
+    && fcntl(data->fd, F_SETFL, O_NONBLOCK)
+  ) return libc_error(ctx);
+  else if ( delay != 0
+    && fcntl(data->fd, F_SETFL, 0)
+  ) return libc_error(ctx);
+
+
   return JS_UNDEFINED;
 }
 static JSValue sock_get_timeout( JSContext *ctx, JSValueConst this ) {
@@ -165,6 +169,23 @@ static JSValue sock_get_timeout( JSContext *ctx, JSValueConst this ) {
   else return JS_NewFloat64(ctx, data->timeout);
 }
 
+static JSValue sock_get_fd( JSContext *ctx, JSValueConst this ) {
+  SocketData * data = JS_GetOpaque2(ctx, this, socket_cid);
+  return JS_NewInt32(ctx, data->fd);
+}
+static JSValue sock_get_family( JSContext *ctx, JSValueConst this ) {
+  SocketData * data = JS_GetOpaque2(ctx, this, socket_cid);
+  return JS_NewInt32(ctx, data->bind.ss_family);
+}
+static JSValue sock_get_type( JSContext *ctx, JSValueConst this ) {
+  SocketData * data = JS_GetOpaque2(ctx, this, socket_cid);
+  return JS_NewInt32(ctx, data->type);
+}
+static JSValue sock_get_connected( JSContext *ctx, JSValueConst this ) {
+  SocketData * data = JS_GetOpaque2(ctx, this, socket_cid);
+  return JS_NewBool(ctx, data->connected);
+}
+
 static JSValue sock_accept(
   JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv
 ) {
@@ -175,10 +196,11 @@ static JSValue sock_accept(
 
   if ( newdata
     && ( newdata->fd =
-         accept( data->fd, (struct sockaddr *) &(newdata->peer), &ps)
+         accept( data->fd, (struct sockaddr *) &(newdata->peer), &ps )
        ) >= 0
   ) {
     memcpy(&(newdata->bind), &(data->bind), sizeof(data->bind));
+    newdata->connected = 1;
     JS_SetOpaque(new, newdata);
     if (data->timeout >= 0)
       (void) sock_set_timeout(ctx, new, JS_NewFloat64(ctx, data->timeout));
@@ -235,8 +257,10 @@ static JSValue sock_send(
     "expected ArrayBuffer or String"
   );
 
-  if ((int)len < 0) return libc_error(ctx);
-  else return JS_NewInt32(ctx, len);
+  if ((int)len < 0) {
+    if (errno == EPIPE) data->connected = 0;
+    return libc_error(ctx);
+  } else return JS_NewInt32(ctx, len);
 }
 
 // like in quickjs.c, needed for direct ArrayBuffer in sock_recv()
@@ -266,7 +290,11 @@ static JSValue sock_recv(
   ) {
     // resize/clear buffer in case of short read,
     if (len) js_realloc(ctx, buf, len);
-    else { js_free(ctx, buf); buf = NULL; }
+    else {
+      // XXX: does a 0 return alway indicate eof?
+      data->connected = 0;
+      js_free(ctx, buf); buf = NULL;
+    }
 
     // XXX: what does the opaque field in NewArryBuffer do? Is it important?
     if (str) {
@@ -315,6 +343,10 @@ static JSValue sock_close(
 ) {
   SocketData *data = JS_GetOpaque2(ctx, this, socket_cid);
 
+  if (data->connected && data->type == SOCK_STREAM)
+    shutdown(data->fd, SHUT_RDWR);
+  data->connected = 0;
+
   if ( close(data->fd) ) return libc_error(ctx);
   else return JS_UNDEFINED;
 }
@@ -335,21 +367,52 @@ static const JSCFunctionListEntry socket_ptype[] = {
   JS_CGETSET_MAGIC_DEF("peerName",  sock_get_addr, NULL, 2),
   JS_CGETSET_MAGIC_DEF("peerPort",  sock_get_addr, NULL, 3),
   JS_CGETSET_DEF("timeout", sock_get_timeout, sock_set_timeout),
-  // JS_CFUNC_DEF("reconnect", )  // TODO
+  JS_CGETSET_DEF("fd",      sock_get_fd, NULL),
+  JS_CGETSET_DEF("family",  sock_get_family, NULL),
+  JS_CGETSET_DEF("type",    sock_get_type, NULL),
+  JS_CGETSET_DEF("connected",    sock_get_connected, NULL),
 };
 
 static JSClassDef socket_class = {
   "Socket", .finalizer = sock_destroy
 };
 
-static JSValue ip_listen(
+static int net_addrinfo(
+  int family, int type, const char *host, const char *port,
+  struct sockaddr_storage * addr
+) {
+  struct addrinfo hints = { .ai_family = family, .ai_socktype = type };
+  struct addrinfo * info;
+  int gai_error;
+
+  if (!(gai_error = getaddrinfo( host, port, &hints, &info ))) {
+    memcpy(addr, info->ai_addr, info->ai_addrlen);
+  }
+  freeaddrinfo(info);
+  return gai_error;
+}
+
+static int net_ip_listen(SocketData *so, int type) {
+  if (!so) return 1;
+
+  if ( (so->fd = socket(so->bind.ss_family, type, 0)) >= 0
+    && !bind(so->fd, (struct sockaddr *) &(so->bind), sizeof(so->bind))
+    && (type == SOCK_DGRAM || !listen(so->fd, 1))
+  ){
+    so->type = type;
+    so->timeout = -1;
+    return 0;
+  } else {
+    if (so->fd >= 0) close(so->fd);
+    return 1;
+  }
+}
+
+static JSValue js_net_ip_listen(
   JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv, int magic
 ) {
-  struct addrinfo hints = {
-    .ai_family = (magic&2) ? AF_INET6 : AF_INET,
-    .ai_socktype = (magic&1) ? SOCK_STREAM : SOCK_DGRAM,
-  };
-  struct addrinfo * self = NULL;
+  int family = (magic&2) ? AF_INET6 : AF_INET;
+  int type = (magic&1) ? SOCK_STREAM : SOCK_DGRAM;
   const char *host, *port; int gai_err;
   SocketData *data = js_mallocz(ctx, sizeof(*data));
   JSValue new = JS_NewObjectClass(ctx, socket_cid);
@@ -358,37 +421,45 @@ static JSValue ip_listen(
   port = JS_ToCString(ctx, argv[1]);
 
   if ( data && (data->fd = -1)
-    && !(gai_err = getaddrinfo( host, port, &hints, &self))
-    && (data->fd = ai_socket(self)) >= 0
-    && !ai_bind(data->fd, self)
-    && ( hints.ai_socktype == SOCK_DGRAM || !listen(data->fd, 1))
+    && !(gai_err = net_addrinfo(family, type, host, port, &(data->bind)))
+    && !net_ip_listen(data, type)
   ) {
-    data->type = self->ai_socktype;
-    data->timeout = -1;
-    memcpy(&(data->bind), self->ai_addr, sizeof(struct sockaddr));
     JS_SetOpaque(new, data);
   } else {
-    if (data && data->fd >= 0) close(data->fd);
     if (!data) new = JS_EXCEPTION;
     else if (!gai_err) new = libc_error(ctx);
     else new = JS_ThrowInternalError(ctx, "%s", gai_strerror(gai_err));
     js_free(ctx, data);
     JS_FreeValue(ctx, new);
   }
-  freeaddrinfo(self);
   JS_FreeCString(ctx, host); JS_FreeCString(ctx, port);
 
   return new;
 }
 
-static JSValue ip_connect(
+static int net_ip_connect(SocketData *so, int type) {
+  socklen_t ps = sizeof(struct sockaddr_storage);
+  if (!so) return 1;
+
+  if ( (so->fd = socket(so->peer.ss_family, type, 0)) >= 0
+    && !connect(so->fd, (struct sockaddr *) &(so->peer), ps)
+    && !getsockname(so->fd, (struct sockaddr *) &(so->bind), &ps)
+  ){
+    so->type = type;
+    so->timeout = -1;
+    so->connected = 1;
+    return 0;
+  } else {
+    if (so->fd >= 0) close(so->fd);
+    return 1;
+  }
+}
+
+static JSValue js_net_ip_connect(
   JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv, int magic
 ) {
-  struct addrinfo hints = {
-    .ai_family = (magic&2) ? AF_INET6 : AF_INET,
-    .ai_socktype = (magic&1) ? SOCK_STREAM : SOCK_DGRAM,
-  };
-  struct addrinfo * peer = NULL;
+  int family = (magic&2) ? AF_INET6 : AF_INET;
+  int type = (magic&1) ? SOCK_STREAM : SOCK_DGRAM;
   const char *host, *port; int gai_err;
   SocketData *data = js_mallocz(ctx, sizeof(*data));
   JSValue new = JS_NewObjectClass(ctx, socket_cid);
@@ -397,29 +468,46 @@ static JSValue ip_connect(
   port = JS_ToCString(ctx, argv[1]);
 
   if ( data && (data->fd = -1)
-    && !(gai_err = getaddrinfo( host, port, &hints, &peer))
-    && (data->fd = ai_socket(peer)) >= 0
-    && !ai_connect(data->fd, peer)
+    && !(gai_err = net_addrinfo(family, type, host, port, &(data->peer)))
+    && !(net_ip_connect(data, type))
   ) {
-    data->type = peer->ai_socktype;
-    data->timeout = -1;
-    memcpy(&(data->peer), peer->ai_addr, sizeof(struct sockaddr));
     JS_SetOpaque(new, data);
   } else {
-    if (data && data->fd >= 0) close(data->fd);
     if (!data) new = JS_EXCEPTION;
     else if (!gai_err) new = libc_error(ctx);
     else new = JS_ThrowInternalError(ctx, "%s", gai_strerror(gai_err));
     js_free(ctx, data);
     JS_FreeValue(ctx, new);
   }
-  freeaddrinfo(peer);
   JS_FreeCString(ctx, host); JS_FreeCString(ctx, port);
 
   return new;
 }
 
-static JSValue unix_bind(
+static int net_unix_bind(SocketData *so, const char *path, size_t plen, int con) {
+  struct sockaddr_un * addr;
+  if (!so) return 1;
+
+  addr = (struct sockaddr_un *) (con ? &(so->peer) : &(so->bind));
+  addr->sun_family = AF_UNIX;
+  if (plen < sizeof(addr->sun_path)) strncpy(addr->sun_path, path, plen);
+
+  if ( (so->fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0
+    && !( con ? connect(so->fd,(struct sockaddr *) addr ,sizeof(*addr))
+            :      bind(so->fd,(struct sockaddr *) addr ,sizeof(*addr))
+    ) && (con || !listen(so->fd, 1))
+  ) {
+    so->type = SOCK_STREAM;
+    so->connected = con;
+    so->timeout = -1;
+    return 0;
+  } else {
+    if (so->fd >= 0) close(so->fd);
+    return 1;
+  }
+}
+
+static JSValue js_net_unix_bind(
   JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv, int c
 ) {
   SocketData *data = js_mallocz(ctx, sizeof(*data));
@@ -429,21 +517,11 @@ static JSValue unix_bind(
 
   path = JS_ToCStringLen(ctx, &plen, argv[0]);
 
-  if ( data && (data->fd = -1)
-    && (addr = (struct sockaddr_un *) (c ? &(data->peer) : &(data->bind)))
-    && (addr->sun_family = AF_UNIX)
-    && (plen < sizeof(addr->sun_path))
-    && strncpy(addr->sun_path, path, plen)
-    && (data->fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0
-    && !( c ?connect(data->fd,(struct sockaddr *) &(data->peer),sizeof(*addr))
-            :   bind(data->fd,(struct sockaddr *) &(data->bind),sizeof(*addr))
-    ) && (c || !listen(data->fd, 1))
+  if ( data && (plen < sizeof(addr->sun_path))
+    && !net_unix_bind(data, path, plen, c)
   ) {
-    data->type = SOCK_STREAM;
-    data->timeout = -1;
     JS_SetOpaque(new, data);
   } else {
-    if (data && data->fd >= 0) close(data->fd);
     if (!data) {
       new = JS_EXCEPTION;
     } else if (plen < sizeof(addr->sun_path)) {
@@ -459,22 +537,40 @@ static JSValue unix_bind(
   return new;
 }
 
+static JSValue net_set_timeout(
+  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv
+) {
+  if ( ! JS_ToFloat64(ctx, &net_timeout, argv[0])) return argv[0];
+  else return JS_EXCEPTION;
+}
+static JSValue net_get_timeout(
+  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv
+) {
+  if (net_timeout < 0) return JS_UNDEFINED;
+  return JS_NewFloat64(ctx, net_timeout);
+}
+
 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_CFUNC_MAGIC_DEF("udpListen",   2, ip_listen, 0),
-  JS_CFUNC_MAGIC_DEF("tcpListen",   2, ip_listen, 1),
-  JS_CFUNC_MAGIC_DEF("udp6Listen",  2, ip_listen, 2),
-  JS_CFUNC_MAGIC_DEF("tcp6Listen",  2, ip_listen, 3),
-  JS_CFUNC_MAGIC_DEF("udpConnect",  2, ip_connect, 0),
-  JS_CFUNC_MAGIC_DEF("tcpConnect",  2, ip_connect, 1),
-  JS_CFUNC_MAGIC_DEF("udp6Connect", 2, ip_connect, 2),
-  JS_CFUNC_MAGIC_DEF("tcp6Connect", 2, ip_connect, 3),
-  JS_CFUNC_MAGIC_DEF("unixListen",  1, unix_bind, 0),
-  JS_CFUNC_MAGIC_DEF("unixConnect", 1, unix_bind, 1),
+  JS_CFUNC_MAGIC_DEF("udpListen",   2, js_net_ip_listen, 0),
+  JS_CFUNC_MAGIC_DEF("tcpListen",   2, js_net_ip_listen, 1),
+  JS_CFUNC_MAGIC_DEF("udp6Listen",  2, js_net_ip_listen, 2),
+  JS_CFUNC_MAGIC_DEF("tcp6Listen",  2, js_net_ip_listen, 3),
+  JS_CFUNC_MAGIC_DEF("udpConnect",  2, js_net_ip_connect, 0),
+  JS_CFUNC_MAGIC_DEF("tcpConnect",  2, js_net_ip_connect, 1),
+  JS_CFUNC_MAGIC_DEF("udp6Connect", 2, js_net_ip_connect, 2),
+  JS_CFUNC_MAGIC_DEF("tcp6Connect", 2, js_net_ip_connect, 3),
+  JS_CFUNC_MAGIC_DEF("unixListen",  1, js_net_unix_bind, 0),
+  JS_CFUNC_MAGIC_DEF("unixConnect", 1, js_net_unix_bind, 1),
+
+  // XXX: How does one use a get/set property in a module provided object?
+  // JS_CGETSET_DEF("timeout", net_get_timeout, net_set_timeout),
+  JS_CFUNC_DEF("setConnectTimeout", 1, net_set_timeout),
+  JS_CFUNC_DEF("getConnectTimeout", 1, net_get_timeout),
 };
 
 // static const JSCFunctionListEntry net_obj[] = {
@@ -490,8 +586,7 @@ static int sock_modinit(JSContext *ctx, JSModuleDef *mod ) {
   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;
+  return JS_SetModuleExportList(ctx, mod, net_funcs, countof(net_funcs));
 }
 
 JSModuleDef *js_init_module(JSContext *ctx, const char *name) {