From: Paul Hänsch Date: Sun, 15 Mar 2026 22:39:13 +0000 (+0100) Subject: separate socket logic from js handling X-Git-Url: https://git.plutz.net/?a=commitdiff_plain;h=ded6b7e8c952aa80c7ab2b0cfc60db332a277394;p=quickjs_net separate socket logic from js handling --- diff --git a/socket.c b/socket.c index 43858f1..31ad4cb 100644 --- a/socket.c +++ b/socket.c @@ -8,18 +8,11 @@ #include #include #include +#include #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) {