From e216775f24bfbba2cf2d02260569afd674c358b6 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Paul=20H=C3=A4nsch?= Date: Tue, 10 Mar 2026 21:20:57 +0100 Subject: [PATCH] initial commit --- socket.c | 327 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 socket.c diff --git a/socket.c b/socket.c new file mode 100644 index 0000000..9c3d58e --- /dev/null +++ b/socket.c @@ -0,0 +1,327 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} -- 2.39.5