diff --git a/source/qjs_fd.c b/source/qjs_fd.c index c790d6eb..0d1c9ff7 100644 --- a/source/qjs_fd.c +++ b/source/qjs_fd.c @@ -38,7 +38,12 @@ static JSClassDef js_fd_class = { // Helper to convert JS value to FDWrapper static FDWrapper *js2fd(JSContext *ctx, JSValueConst obj) { - return JS_GetOpaque2(ctx, obj, js_fd_class_id); + FDWrapper *fdw = JS_GetOpaque2(ctx, obj, js_fd_class_id); + if (!fdw) { + JS_ThrowTypeError(ctx, "Expected file descriptor object"); + return NULL; + } + return fdw; } // Helper to create JS FDWrapper object @@ -75,262 +80,8 @@ static ssize_t js_fd_write_helper(JSContext *js, int fd, JSValue val) return wrote; } -// Glob data structure -struct fd_globdata { - JSContext *js; - JSValue arr; - char **globs; - int idx; - int recurse; -}; -// Helper for recursive directory enumeration -static void enumerate_dir(struct fd_globdata *data, const char *path); - -// Callback for glob matching -static void globfs_cb(struct fd_globdata *data, const char *dir, const char *file) -{ - char *path; - int needfree = 0; - - if (dir[0] == 0) { - path = (char*)file; - } else { - size_t len = strlen(dir) + strlen(file) + 2; - path = malloc(len); - snprintf(path, len, "%s/%s", dir, file); - needfree = 1; - } - - char **glob = data->globs; - while (*glob != NULL) { - if (wildmatch(*glob, path, WM_WILDSTAR) == WM_MATCH) - goto END; - glob++; - } - - struct stat st; - if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { - enumerate_dir(data, path); - goto END; - } - - JS_SetPropertyUint32(data->js, data->arr, data->idx++, JS_NewString(data->js, path)); - -END: - if (needfree) free(path); -} - -// Helper for directory enumeration -static void enumerate_dir(struct fd_globdata *data, const char *path) -{ - DIR *dir = opendir(path); - if (!dir) return; - - struct dirent *entry; - while ((entry = readdir(dir)) != NULL) { - if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) - continue; - - globfs_cb(data, path, entry->d_name); - } - - closedir(dir); -} - -// FD I/O FUNCTIONS - -JSC_SCALL(fd_rm, - if (remove(str) != 0) - ret = JS_ThrowReferenceError(js, "could not remove %s: %s", str, strerror(errno)); -) - -JSC_SCALL(fd_mkdir, - if (mkdir(str, 0755) != 0) - ret = JS_ThrowReferenceError(js, "could not make directory %s: %s", str, strerror(errno)); -) - -JSC_SCALL(fd_exists, - struct stat st; - ret = JS_NewBool(js, stat(str, &st) == 0); -) - -JSC_SCALL(fd_stat, - struct stat st; - if (stat(str, &st) != 0) - return JS_ThrowReferenceError(js, "stat failed: %s", strerror(errno)); - - ret = JS_NewObject(js); - JS_SetPropertyStr(js, ret, "filesize", number2js(js, st.st_size)); - JS_SetPropertyStr(js, ret, "modtime", number2js(js, st.st_mtime)); - JS_SetPropertyStr(js, ret, "createtime", number2js(js, st.st_ctime)); - JS_SetPropertyStr(js, ret, "accesstime", number2js(js, st.st_atime)); - JS_SetPropertyStr(js, ret, "mode", JS_NewInt32(js, st.st_mode)); - JS_SetPropertyStr(js, ret, "is_directory", JS_NewBool(js, S_ISDIR(st.st_mode))); - JS_SetPropertyStr(js, ret, "is_file", JS_NewBool(js, S_ISREG(st.st_mode))); -) - -JSC_SCALL(fd_slurpbytes, - int fd = open(str, O_RDONLY); - if (fd < 0) { - ret = JS_ThrowReferenceError(js, "could not open %s: %s", str, strerror(errno)); - goto END; - } - - struct stat st; - if (fstat(fd, &st) != 0) { - close(fd); - ret = JS_ThrowReferenceError(js, "fstat failed: %s", strerror(errno)); - goto END; - } - - void *data = malloc(st.st_size); - if (!data) { - close(fd); - ret = JS_ThrowReferenceError(js, "malloc failed"); - goto END; - } - - ssize_t bytes_read = read(fd, data, st.st_size); - close(fd); - - if (bytes_read != st.st_size) { - free(data); - ret = JS_ThrowReferenceError(js, "read failed: %s", strerror(errno)); - goto END; - } - - ret = js_new_blob_stoned_copy(js, data, st.st_size); - free(data); - -END: -) - -JSC_SCALL(fd_slurp, - int fd = open(str, O_RDONLY); - if (fd < 0) { - ret = JS_ThrowReferenceError(js, "could not open %s: %s", str, strerror(errno)); - goto END; - } - - struct stat st; - if (fstat(fd, &st) != 0) { - close(fd); - ret = JS_ThrowReferenceError(js, "fstat failed: %s", strerror(errno)); - goto END; - } - - char *data = malloc(st.st_size + 1); - if (!data) { - close(fd); - ret = JS_ThrowReferenceError(js, "malloc failed"); - goto END; - } - - ssize_t bytes_read = read(fd, data, st.st_size); - close(fd); - - if (bytes_read != st.st_size) { - free(data); - ret = JS_ThrowReferenceError(js, "read failed: %s", strerror(errno)); - goto END; - } - - data[st.st_size] = '\0'; - ret = JS_NewStringLen(js, data, st.st_size); - free(data); - -END: -) - -JSC_SCALL(fd_slurpwrite, - int fd = open(str, O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (fd < 0) { - ret = JS_ThrowReferenceError(js, "could not open %s: %s", str, strerror(errno)); - goto END; - } - - ssize_t wrote = js_fd_write_helper(js, fd, argv[1]); - close(fd); - - if (wrote < 0) - ret = JS_ThrowReferenceError(js, "write failed: %s", strerror(errno)); - -END: -) - -JSC_SSCALL(fd_match, - if (wildmatch(str, str2, WM_PATHNAME | WM_PERIOD | WM_WILDSTAR) == WM_MATCH) - ret = JS_NewBool(js, 1); - else - ret = JS_NewBool(js, 0); -) - -JSC_CCALL(fd_globfs, - ret = JS_NewArray(js); - struct fd_globdata data; - data.js = js; - data.arr = ret; - data.idx = 0; - data.recurse = 1; - - int globs_len = JS_ArrayLength(js, argv[0]); - const char *globs[globs_len + 1]; - for (int i = 0; i < globs_len; i++) { - JSValue g = JS_GetPropertyUint32(js, argv[0], i); - globs[i] = JS_ToCString(js, g); - JS_FreeValue(js, g); - } - globs[globs_len] = NULL; - data.globs = (char**)globs; - - const char *path = "."; - if (!JS_IsUndefined(argv[1])) - path = JS_ToCString(js, argv[1]); - - enumerate_dir(&data, path); - - for (int i = 0; i < globs_len; i++) - JS_FreeCString(js, globs[i]); - - if (!JS_IsUndefined(argv[1])) - JS_FreeCString(js, path); -) - -JSC_SCALL(fd_enumerate, - ret = JS_NewArray(js); - - DIR *dir = opendir(str); - if (!dir) { - ret = JS_ThrowReferenceError(js, "could not open directory %s: %s", str, strerror(errno)); - goto END; - } - - int idx = 0; - struct dirent *entry; - while ((entry = readdir(dir)) != NULL) { - if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) - continue; - - JS_SetPropertyUint32(js, ret, idx++, JS_NewString(js, entry->d_name)); - } - - closedir(dir); - -END: -) - -JSC_CCALL(fd_getcwd, - char buf[PATH_MAX]; - if (getcwd(buf, sizeof(buf)) == NULL) - return JS_ThrowReferenceError(js, "getcwd failed: %s", strerror(errno)); - return JS_NewString(js, buf); -) - -JSC_SCALL(fd_chdir, - if (chdir(str) != 0) - ret = JS_ThrowReferenceError(js, "chdir failed: %s", strerror(errno)); -) +// POSIX FILE DESCRIPTOR FUNCTIONS JSC_SCALL(fd_open, int flags = O_RDWR | O_CREAT; @@ -359,44 +110,24 @@ JSC_SCALL(fd_open, ret = fd2js(js, fd); ) -JSC_SCALL(fd_is_directory, - struct stat st; - if (stat(str, &st) != 0) - ret = JS_NewBool(js, 0); - else - ret = JS_NewBool(js, S_ISDIR(st.st_mode)); -) - -// FILE DESCRIPTOR FUNCTIONS - -JSC_CCALL(file_close, - FDWrapper *fdw = js2fd(js, self); +JSC_CCALL(fd_write, + FDWrapper *fdw = js2fd(js, argv[0]); if (!fdw) return JS_EXCEPTION; - if (fdw->fd >= 0) { - close(fdw->fd); - fdw->fd = -1; - } -) - -JSC_CCALL(file_write, - FDWrapper *fdw = js2fd(js, self); - if (!fdw) return JS_EXCEPTION; - - ssize_t wrote = js_fd_write_helper(js, fdw->fd, argv[0]); + ssize_t wrote = js_fd_write_helper(js, fdw->fd, argv[1]); if (wrote < 0) return JS_ThrowReferenceError(js, "write failed: %s", strerror(errno)); return JS_NewInt64(js, wrote); ) -JSC_CCALL(file_read, - FDWrapper *fdw = js2fd(js, self); +JSC_CCALL(fd_read, + FDWrapper *fdw = js2fd(js, argv[0]); if (!fdw) return JS_EXCEPTION; size_t size = 4096; - if (argc > 0) - size = js2number(js, argv[0]); + if (argc > 1) + size = js2number(js, argv[1]); void *buf = malloc(size); if (!buf) @@ -413,15 +144,15 @@ JSC_CCALL(file_read, return ret; ) -JSC_CCALL(file_seek, - FDWrapper *fdw = js2fd(js, self); +JSC_CCALL(fd_lseek, + FDWrapper *fdw = js2fd(js, argv[0]); if (!fdw) return JS_EXCEPTION; - off_t offset = js2number(js, argv[0]); + off_t offset = js2number(js, argv[1]); int whence = SEEK_SET; - if (argc > 1) { - const char *whence_str = JS_ToCString(js, argv[1]); + if (argc > 2) { + const char *whence_str = JS_ToCString(js, argv[2]); if (strcmp(whence_str, "cur") == 0) whence = SEEK_CUR; else if (strcmp(whence_str, "end") == 0) whence = SEEK_END; JS_FreeCString(js, whence_str); @@ -434,52 +165,56 @@ JSC_CCALL(file_seek, return JS_NewInt64(js, new_pos); ) -JSC_CCALL(file_tell, - FDWrapper *fdw = js2fd(js, self); - if (!fdw) return JS_EXCEPTION; - - off_t pos = lseek(fdw->fd, 0, SEEK_CUR); - if (pos < 0) - return JS_ThrowReferenceError(js, "lseek failed: %s", strerror(errno)); - - return JS_NewInt64(js, pos); +JSC_CCALL(fd_getcwd, + char buf[PATH_MAX]; + if (getcwd(buf, sizeof(buf)) == NULL) + return JS_ThrowReferenceError(js, "getcwd failed: %s", strerror(errno)); + return JS_NewString(js, buf); ) -JSC_CCALL(file_eof, - FDWrapper *fdw = js2fd(js, self); +JSC_SCALL(fd_rmdir, + if (rmdir(str) != 0) + ret = JS_ThrowReferenceError(js, "could not remove directory %s: %s", str, strerror(errno)); +) + +JSC_SCALL(fd_mkdir, + if (mkdir(str, 0755) != 0) + ret = JS_ThrowReferenceError(js, "could not make directory %s: %s", str, strerror(errno)); +) + +JSC_CCALL(fd_fsync, + FDWrapper *fdw = js2fd(js, argv[0]); if (!fdw) return JS_EXCEPTION; - off_t cur = lseek(fdw->fd, 0, SEEK_CUR); - off_t end = lseek(fdw->fd, 0, SEEK_END); - lseek(fdw->fd, cur, SEEK_SET); + if (fsync(fdw->fd) != 0) + return JS_ThrowReferenceError(js, "fsync failed: %s", strerror(errno)); - return JS_NewBool(js, cur >= end); + return JS_UNDEFINED; ) +JSC_CCALL(fd_close, + FDWrapper *fdw = js2fd(js, argv[0]); + if (!fdw) return JS_EXCEPTION; + + if (fdw->fd >= 0) { + close(fdw->fd); + fdw->fd = -1; + } + + return JS_UNDEFINED; +) + + static const JSCFunctionListEntry js_fd_funcs[] = { - MIST_FUNC_DEF(fd, rm, 1), - MIST_FUNC_DEF(fd, mkdir, 1), - MIST_FUNC_DEF(fd, stat, 1), - MIST_FUNC_DEF(fd, globfs, 2), - MIST_FUNC_DEF(fd, match, 2), - MIST_FUNC_DEF(fd, exists, 1), - MIST_FUNC_DEF(fd, slurp, 1), - MIST_FUNC_DEF(fd, slurpbytes, 1), - MIST_FUNC_DEF(fd, slurpwrite, 2), - MIST_FUNC_DEF(fd, getcwd, 0), - MIST_FUNC_DEF(fd, chdir, 1), MIST_FUNC_DEF(fd, open, 2), - MIST_FUNC_DEF(fd, enumerate, 1), - MIST_FUNC_DEF(fd, is_directory, 1), -}; - -static const JSCFunctionListEntry js_fd_file_funcs[] = { - MIST_FUNC_DEF(file, close, 0), - MIST_FUNC_DEF(file, write, 1), - MIST_FUNC_DEF(file, read, 1), - MIST_FUNC_DEF(file, seek, 2), - MIST_FUNC_DEF(file, tell, 0), - MIST_FUNC_DEF(file, eof, 0), + MIST_FUNC_DEF(fd, write, 2), + MIST_FUNC_DEF(fd, read, 2), + MIST_FUNC_DEF(fd, lseek, 3), + MIST_FUNC_DEF(fd, getcwd, 0), + MIST_FUNC_DEF(fd, rmdir, 1), + MIST_FUNC_DEF(fd, mkdir, 1), + MIST_FUNC_DEF(fd, fsync, 1), + MIST_FUNC_DEF(fd, close, 1), }; JSValue js_fd_use(JSContext *js) { @@ -488,7 +223,6 @@ JSValue js_fd_use(JSContext *js) { JS_NewClass(JS_GetRuntime(js), js_fd_class_id, &js_fd_class); JSValue proto = JS_NewObject(js); - JS_SetPropertyFunctionList(js, proto, js_fd_file_funcs, countof(js_fd_file_funcs)); JS_SetClassProto(js, js_fd_class_id, proto); JSValue mod = JS_NewObject(js); diff --git a/source/qjs_socket.c b/source/qjs_socket.c new file mode 100644 index 00000000..5f881e21 --- /dev/null +++ b/source/qjs_socket.c @@ -0,0 +1,711 @@ +#include "quickjs.h" +#include "jsffi.h" +#include "qjs_blob.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Socket wrapper structure +typedef struct { + int sockfd; +} SocketWrapper; + +// Addrinfo wrapper structure +typedef struct { + struct addrinfo *info; +} AddrinfoWrapper; + +// Free function for socket +static void Socket_free(JSRuntime *rt, SocketWrapper *sw) +{ + if (sw->sockfd >= 0) { + close(sw->sockfd); + } + js_free_rt(rt, sw); +} + +// Free function for addrinfo +static void Addrinfo_free(JSRuntime *rt, AddrinfoWrapper *aw) +{ + if (aw->info) { + freeaddrinfo(aw->info); + } + js_free_rt(rt, aw); +} + +// Class definitions +static JSClassID js_socket_class_id; +static JSClassID js_addrinfo_class_id; + +static JSClassDef js_socket_class = { + "Socket", + .finalizer = (JSClassFinalizer *)Socket_free, +}; + +static JSClassDef js_addrinfo_class = { + "Addrinfo", + .finalizer = (JSClassFinalizer *)Addrinfo_free, +}; + +// Helper to convert JS value to SocketWrapper +static SocketWrapper *js2socket(JSContext *ctx, JSValueConst obj) +{ + SocketWrapper *sw = JS_GetOpaque2(ctx, obj, js_socket_class_id); + if (!sw) { + JS_ThrowTypeError(ctx, "Expected socket object"); + return NULL; + } + return sw; +} + +// Helper to convert JS value to AddrinfoWrapper +static AddrinfoWrapper *js2addrinfo(JSContext *ctx, JSValueConst obj) +{ + AddrinfoWrapper *aw = JS_GetOpaque2(ctx, obj, js_addrinfo_class_id); + if (!aw) { + JS_ThrowTypeError(ctx, "Expected addrinfo object"); + return NULL; + } + return aw; +} + +// Helper to create JS SocketWrapper object +static JSValue socket2js(JSContext *ctx, int sockfd) +{ + SocketWrapper *sw = js_mallocz(ctx, sizeof(SocketWrapper)); + if (!sw) return JS_EXCEPTION; + + sw->sockfd = sockfd; + + JSValue obj = JS_NewObjectClass(ctx, js_socket_class_id); + if (JS_IsException(obj)) { + js_free(ctx, sw); + return obj; + } + + JS_SetOpaque(obj, sw); + return obj; +} + +// Helper to create JS AddrinfoWrapper object +static JSValue addrinfo2js(JSContext *ctx, struct addrinfo *info) +{ + AddrinfoWrapper *aw = js_mallocz(ctx, sizeof(AddrinfoWrapper)); + if (!aw) return JS_EXCEPTION; + + aw->info = info; + + JSValue obj = JS_NewObjectClass(ctx, js_addrinfo_class_id); + if (JS_IsException(obj)) { + js_free(ctx, aw); + return obj; + } + + JS_SetOpaque(obj, aw); + return obj; +} + +// SOCKET FUNCTIONS + +JSC_CCALL(socket_getaddrinfo, + const char *node = NULL; + const char *service = NULL; + struct addrinfo hints, *res; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if (!JS_IsNull(argv[0]) && !JS_IsUndefined(argv[0])) + node = JS_ToCString(js, argv[0]); + + if (!JS_IsNull(argv[1]) && !JS_IsUndefined(argv[1])) + service = JS_ToCString(js, argv[1]); + + // Parse optional hints object + if (argc > 2 && JS_IsObject(argv[2])) { + JSValue val; + + val = JS_GetPropertyStr(js, argv[2], "family"); + if (!JS_IsUndefined(val)) { + const char *family = JS_ToCString(js, val); + if (strcmp(family, "AF_INET") == 0) hints.ai_family = AF_INET; + else if (strcmp(family, "AF_INET6") == 0) hints.ai_family = AF_INET6; + JS_FreeCString(js, family); + } + JS_FreeValue(js, val); + + val = JS_GetPropertyStr(js, argv[2], "socktype"); + if (!JS_IsUndefined(val)) { + const char *socktype = JS_ToCString(js, val); + if (strcmp(socktype, "SOCK_STREAM") == 0) hints.ai_socktype = SOCK_STREAM; + else if (strcmp(socktype, "SOCK_DGRAM") == 0) hints.ai_socktype = SOCK_DGRAM; + JS_FreeCString(js, socktype); + } + JS_FreeValue(js, val); + + val = JS_GetPropertyStr(js, argv[2], "flags"); + if (!JS_IsUndefined(val)) { + hints.ai_flags = js2number(js, val); + } + JS_FreeValue(js, val); + + val = JS_GetPropertyStr(js, argv[2], "passive"); + if (JS_ToBool(js, val)) { + hints.ai_flags |= AI_PASSIVE; + } + JS_FreeValue(js, val); + } + + int status = getaddrinfo(node, service, &hints, &res); + + if (node) JS_FreeCString(js, node); + if (service) JS_FreeCString(js, service); + + if (status != 0) { + return JS_ThrowReferenceError(js, "getaddrinfo error: %s", gai_strerror(status)); + } + + // Convert linked list to JS array + ret = JS_NewArray(js); + int idx = 0; + for (struct addrinfo *p = res; p != NULL; p = p->ai_next) { + JSValue info = JS_NewObject(js); + JS_SetPropertyStr(js, info, "family", JS_NewInt32(js, p->ai_family)); + JS_SetPropertyStr(js, info, "socktype", JS_NewInt32(js, p->ai_socktype)); + JS_SetPropertyStr(js, info, "protocol", JS_NewInt32(js, p->ai_protocol)); + + // Convert address to string + char ipstr[INET6_ADDRSTRLEN]; + void *addr; + if (p->ai_family == AF_INET) { + struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr; + addr = &(ipv4->sin_addr); + } else { + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr; + addr = &(ipv6->sin6_addr); + } + inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr); + JS_SetPropertyStr(js, info, "address", JS_NewString(js, ipstr)); + + // Store the addrinfo for later use + struct addrinfo *copy = malloc(sizeof(struct addrinfo)); + memcpy(copy, p, sizeof(struct addrinfo)); + copy->ai_addr = malloc(p->ai_addrlen); + memcpy(copy->ai_addr, p->ai_addr, p->ai_addrlen); + copy->ai_next = NULL; + if (p->ai_canonname) { + copy->ai_canonname = strdup(p->ai_canonname); + } + + JS_SetPropertyStr(js, info, "_addrinfo", addrinfo2js(js, copy)); + JS_SetPropertyUint32(js, ret, idx++, info); + } + + freeaddrinfo(res); +) + +JSC_CCALL(socket_socket, + int domain = AF_INET; + int type = SOCK_STREAM; + int protocol = 0; + + // Parse domain + if (JS_IsString(argv[0])) { + const char *domain_str = JS_ToCString(js, argv[0]); + if (strcmp(domain_str, "AF_INET") == 0) domain = AF_INET; + else if (strcmp(domain_str, "AF_INET6") == 0) domain = AF_INET6; + else if (strcmp(domain_str, "AF_UNIX") == 0) domain = AF_UNIX; + JS_FreeCString(js, domain_str); + } else if (JS_IsNumber(argv[0])) { + domain = js2number(js, argv[0]); + } + + // Parse type + if (argc > 1) { + if (JS_IsString(argv[1])) { + const char *type_str = JS_ToCString(js, argv[1]); + if (strcmp(type_str, "SOCK_STREAM") == 0) type = SOCK_STREAM; + else if (strcmp(type_str, "SOCK_DGRAM") == 0) type = SOCK_DGRAM; + JS_FreeCString(js, type_str); + } else if (JS_IsNumber(argv[1])) { + type = js2number(js, argv[1]); + } + } + + // Parse protocol + if (argc > 2) { + protocol = js2number(js, argv[2]); + } + + int sockfd = socket(domain, type, protocol); + if (sockfd < 0) { + return JS_ThrowReferenceError(js, "socket failed: %s", strerror(errno)); + } + + return socket2js(js, sockfd); +) + +JSC_CCALL(socket_bind, + SocketWrapper *sw = js2socket(js, argv[0]); + if (!sw) return JS_EXCEPTION; + + AddrinfoWrapper *aw = js2addrinfo(js, JS_GetPropertyStr(js, argv[1], "_addrinfo")); + if (!aw) { + // Try to parse address and port manually + const char *addr_str = JS_ToCString(js, JS_GetPropertyStr(js, argv[1], "address")); + int port = js2number(js, JS_GetPropertyStr(js, argv[1], "port")); + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + if (inet_pton(AF_INET, addr_str, &addr.sin_addr) <= 0) { + JS_FreeCString(js, addr_str); + return JS_ThrowReferenceError(js, "Invalid address"); + } + JS_FreeCString(js, addr_str); + + if (bind(sw->sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + return JS_ThrowReferenceError(js, "bind failed: %s", strerror(errno)); + } + } else { + if (bind(sw->sockfd, aw->info->ai_addr, aw->info->ai_addrlen) < 0) { + return JS_ThrowReferenceError(js, "bind failed: %s", strerror(errno)); + } + } + + return JS_UNDEFINED; +) + +JSC_CCALL(socket_connect, + SocketWrapper *sw = js2socket(js, argv[0]); + if (!sw) return JS_EXCEPTION; + + AddrinfoWrapper *aw = js2addrinfo(js, JS_GetPropertyStr(js, argv[1], "_addrinfo")); + if (!aw) { + // Try to parse address and port manually + const char *addr_str = JS_ToCString(js, JS_GetPropertyStr(js, argv[1], "address")); + int port = js2number(js, JS_GetPropertyStr(js, argv[1], "port")); + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + if (inet_pton(AF_INET, addr_str, &addr.sin_addr) <= 0) { + JS_FreeCString(js, addr_str); + return JS_ThrowReferenceError(js, "Invalid address"); + } + JS_FreeCString(js, addr_str); + + if (connect(sw->sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + return JS_ThrowReferenceError(js, "connect failed: %s", strerror(errno)); + } + } else { + if (connect(sw->sockfd, aw->info->ai_addr, aw->info->ai_addrlen) < 0) { + return JS_ThrowReferenceError(js, "connect failed: %s", strerror(errno)); + } + } + + return JS_UNDEFINED; +) + +JSC_CCALL(socket_listen, + SocketWrapper *sw = js2socket(js, argv[0]); + if (!sw) return JS_EXCEPTION; + + int backlog = 10; + if (argc > 1) { + backlog = js2number(js, argv[1]); + } + + if (listen(sw->sockfd, backlog) < 0) { + return JS_ThrowReferenceError(js, "listen failed: %s", strerror(errno)); + } + + return JS_UNDEFINED; +) + +JSC_CCALL(socket_accept, + SocketWrapper *sw = js2socket(js, argv[0]); + if (!sw) return JS_EXCEPTION; + + struct sockaddr_storage their_addr; + socklen_t addr_size = sizeof their_addr; + + int new_sockfd = accept(sw->sockfd, (struct sockaddr *)&their_addr, &addr_size); + if (new_sockfd < 0) { + return JS_ThrowReferenceError(js, "accept failed: %s", strerror(errno)); + } + + ret = JS_NewObject(js); + JS_SetPropertyStr(js, ret, "socket", socket2js(js, new_sockfd)); + + // Get peer address info + char ipstr[INET6_ADDRSTRLEN]; + int port; + if (their_addr.ss_family == AF_INET) { + struct sockaddr_in *s = (struct sockaddr_in *)&their_addr; + inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr); + port = ntohs(s->sin_port); + } else { + struct sockaddr_in6 *s = (struct sockaddr_in6 *)&their_addr; + inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof ipstr); + port = ntohs(s->sin6_port); + } + + JSValue addr_info = JS_NewObject(js); + JS_SetPropertyStr(js, addr_info, "address", JS_NewString(js, ipstr)); + JS_SetPropertyStr(js, addr_info, "port", JS_NewInt32(js, port)); + JS_SetPropertyStr(js, ret, "address", addr_info); +) + +JSC_CCALL(socket_send, + SocketWrapper *sw = js2socket(js, argv[0]); + if (!sw) return JS_EXCEPTION; + + size_t len; + ssize_t sent; + int flags = 0; + + if (argc > 2) { + flags = js2number(js, argv[2]); + } + + if (JS_IsString(argv[1])) { + const char *data = JS_ToCStringLen(js, &len, argv[1]); + sent = send(sw->sockfd, data, len, flags); + JS_FreeCString(js, data); + } else { + unsigned char *data = js_get_blob_data(js, &len, argv[1]); + sent = send(sw->sockfd, data, len, flags); + } + + if (sent < 0) { + return JS_ThrowReferenceError(js, "send failed: %s", strerror(errno)); + } + + return JS_NewInt64(js, sent); +) + +JSC_CCALL(socket_recv, + SocketWrapper *sw = js2socket(js, argv[0]); + if (!sw) return JS_EXCEPTION; + + size_t len = 4096; + if (argc > 1) { + len = js2number(js, argv[1]); + } + + int flags = 0; + if (argc > 2) { + flags = js2number(js, argv[2]); + } + + void *buf = malloc(len); + if (!buf) { + return JS_ThrowReferenceError(js, "malloc failed"); + } + + ssize_t received = recv(sw->sockfd, buf, len, flags); + if (received < 0) { + free(buf); + return JS_ThrowReferenceError(js, "recv failed: %s", strerror(errno)); + } + + ret = js_new_blob_stoned_copy(js, buf, received); + free(buf); + return ret; +) + +JSC_CCALL(socket_sendto, + SocketWrapper *sw = js2socket(js, argv[0]); + if (!sw) return JS_EXCEPTION; + + size_t len; + ssize_t sent; + int flags = 0; + + if (argc > 3) { + flags = js2number(js, argv[3]); + } + + // Get destination address + AddrinfoWrapper *aw = js2addrinfo(js, JS_GetPropertyStr(js, argv[2], "_addrinfo")); + struct sockaddr *to_addr; + socklen_t to_len; + + if (!aw) { + // Try to parse address and port manually + const char *addr_str = JS_ToCString(js, JS_GetPropertyStr(js, argv[2], "address")); + int port = js2number(js, JS_GetPropertyStr(js, argv[2], "port")); + + static struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + if (inet_pton(AF_INET, addr_str, &addr.sin_addr) <= 0) { + JS_FreeCString(js, addr_str); + return JS_ThrowReferenceError(js, "Invalid address"); + } + JS_FreeCString(js, addr_str); + + to_addr = (struct sockaddr *)&addr; + to_len = sizeof(addr); + } else { + to_addr = aw->info->ai_addr; + to_len = aw->info->ai_addrlen; + } + + if (JS_IsString(argv[1])) { + const char *data = JS_ToCStringLen(js, &len, argv[1]); + sent = sendto(sw->sockfd, data, len, flags, to_addr, to_len); + JS_FreeCString(js, data); + } else { + unsigned char *data = js_get_blob_data(js, &len, argv[1]); + sent = sendto(sw->sockfd, data, len, flags, to_addr, to_len); + } + + if (sent < 0) { + return JS_ThrowReferenceError(js, "sendto failed: %s", strerror(errno)); + } + + return JS_NewInt64(js, sent); +) + +JSC_CCALL(socket_recvfrom, + SocketWrapper *sw = js2socket(js, argv[0]); + if (!sw) return JS_EXCEPTION; + + size_t len = 4096; + if (argc > 1) { + len = js2number(js, argv[1]); + } + + int flags = 0; + if (argc > 2) { + flags = js2number(js, argv[2]); + } + + void *buf = malloc(len); + if (!buf) { + return JS_ThrowReferenceError(js, "malloc failed"); + } + + struct sockaddr_storage from_addr; + socklen_t from_len = sizeof from_addr; + + ssize_t received = recvfrom(sw->sockfd, buf, len, flags, + (struct sockaddr *)&from_addr, &from_len); + if (received < 0) { + free(buf); + return JS_ThrowReferenceError(js, "recvfrom failed: %s", strerror(errno)); + } + + ret = JS_NewObject(js); + JS_SetPropertyStr(js, ret, "data", js_new_blob_stoned_copy(js, buf, received)); + free(buf); + + // Get source address info + char ipstr[INET6_ADDRSTRLEN]; + int port; + if (from_addr.ss_family == AF_INET) { + struct sockaddr_in *s = (struct sockaddr_in *)&from_addr; + inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr); + port = ntohs(s->sin_port); + } else { + struct sockaddr_in6 *s = (struct sockaddr_in6 *)&from_addr; + inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof ipstr); + port = ntohs(s->sin6_port); + } + + JSValue addr_info = JS_NewObject(js); + JS_SetPropertyStr(js, addr_info, "address", JS_NewString(js, ipstr)); + JS_SetPropertyStr(js, addr_info, "port", JS_NewInt32(js, port)); + JS_SetPropertyStr(js, ret, "address", addr_info); +) + +JSC_CCALL(socket_shutdown, + SocketWrapper *sw = js2socket(js, argv[0]); + if (!sw) return JS_EXCEPTION; + + int how = SHUT_RDWR; + if (argc > 1) { + how = js2number(js, argv[1]); + } + + if (shutdown(sw->sockfd, how) < 0) { + return JS_ThrowReferenceError(js, "shutdown failed: %s", strerror(errno)); + } + + return JS_UNDEFINED; +) + +JSC_CCALL(socket_getpeername, + SocketWrapper *sw = js2socket(js, argv[0]); + if (!sw) return JS_EXCEPTION; + + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + + if (getpeername(sw->sockfd, (struct sockaddr *)&addr, &len) < 0) { + return JS_ThrowReferenceError(js, "getpeername failed: %s", strerror(errno)); + } + + char ipstr[INET6_ADDRSTRLEN]; + int port; + if (addr.ss_family == AF_INET) { + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr); + port = ntohs(s->sin_port); + } else { + struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr; + inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof ipstr); + port = ntohs(s->sin6_port); + } + + ret = JS_NewObject(js); + JS_SetPropertyStr(js, ret, "address", JS_NewString(js, ipstr)); + JS_SetPropertyStr(js, ret, "port", JS_NewInt32(js, port)); +) + +JSC_CCALL(socket_gethostname, + char hostname[256]; + if (gethostname(hostname, sizeof(hostname)) < 0) { + return JS_ThrowReferenceError(js, "gethostname failed: %s", strerror(errno)); + } + return JS_NewString(js, hostname); +) + +JSC_CCALL(socket_freeaddrinfo, + AddrinfoWrapper *aw = js2addrinfo(js, argv[0]); + if (!aw) return JS_EXCEPTION; + + if (aw->info) { + if (aw->info->ai_addr) free(aw->info->ai_addr); + if (aw->info->ai_canonname) free(aw->info->ai_canonname); + free(aw->info); + aw->info = NULL; + } + + return JS_UNDEFINED; +) + +JSC_CCALL(socket_gai_strerror, + int errcode = js2number(js, argv[0]); + return JS_NewString(js, gai_strerror(errcode)); +) + +JSC_CCALL(socket_setsockopt, + SocketWrapper *sw = js2socket(js, argv[0]); + if (!sw) return JS_EXCEPTION; + + int level = SOL_SOCKET; + int optname = 0; + + // Parse level + if (JS_IsString(argv[1])) { + const char *level_str = JS_ToCString(js, argv[1]); + if (strcmp(level_str, "SOL_SOCKET") == 0) level = SOL_SOCKET; + else if (strcmp(level_str, "IPPROTO_TCP") == 0) level = IPPROTO_TCP; + else if (strcmp(level_str, "IPPROTO_IP") == 0) level = IPPROTO_IP; + else if (strcmp(level_str, "IPPROTO_IPV6") == 0) level = IPPROTO_IPV6; + JS_FreeCString(js, level_str); + } else { + level = js2number(js, argv[1]); + } + + // Parse option name + if (JS_IsString(argv[2])) { + const char *opt_str = JS_ToCString(js, argv[2]); + if (strcmp(opt_str, "SO_REUSEADDR") == 0) optname = SO_REUSEADDR; + else if (strcmp(opt_str, "SO_KEEPALIVE") == 0) optname = SO_KEEPALIVE; + else if (strcmp(opt_str, "SO_BROADCAST") == 0) optname = SO_BROADCAST; + JS_FreeCString(js, opt_str); + } else { + optname = js2number(js, argv[2]); + } + + // Parse option value + if (JS_IsBool(argv[3])) { + int optval = JS_ToBool(js, argv[3]); + if (setsockopt(sw->sockfd, level, optname, &optval, sizeof(optval)) < 0) { + return JS_ThrowReferenceError(js, "setsockopt failed: %s", strerror(errno)); + } + } else if (JS_IsNumber(argv[3])) { + int optval = js2number(js, argv[3]); + if (setsockopt(sw->sockfd, level, optname, &optval, sizeof(optval)) < 0) { + return JS_ThrowReferenceError(js, "setsockopt failed: %s", strerror(errno)); + } + } else { + return JS_ThrowTypeError(js, "Invalid option value"); + } + + return JS_UNDEFINED; +) + +static const JSCFunctionListEntry js_socket_funcs[] = { + MIST_FUNC_DEF(socket, getaddrinfo, 3), + MIST_FUNC_DEF(socket, socket, 3), + MIST_FUNC_DEF(socket, bind, 2), + MIST_FUNC_DEF(socket, connect, 2), + MIST_FUNC_DEF(socket, listen, 2), + MIST_FUNC_DEF(socket, accept, 1), + MIST_FUNC_DEF(socket, send, 3), + MIST_FUNC_DEF(socket, recv, 3), + MIST_FUNC_DEF(socket, sendto, 4), + MIST_FUNC_DEF(socket, recvfrom, 3), + MIST_FUNC_DEF(socket, shutdown, 2), + MIST_FUNC_DEF(socket, getpeername, 1), + MIST_FUNC_DEF(socket, gethostname, 0), + MIST_FUNC_DEF(socket, freeaddrinfo, 1), + MIST_FUNC_DEF(socket, gai_strerror, 1), + MIST_FUNC_DEF(socket, setsockopt, 4), +}; + +JSValue js_socket_use(JSContext *js) { + // Initialize the socket class + JS_NewClassID(&js_socket_class_id); + JS_NewClass(JS_GetRuntime(js), js_socket_class_id, &js_socket_class); + + JSValue proto = JS_NewObject(js); + JS_SetClassProto(js, js_socket_class_id, proto); + + // Initialize the addrinfo class + JS_NewClassID(&js_addrinfo_class_id); + JS_NewClass(JS_GetRuntime(js), js_addrinfo_class_id, &js_addrinfo_class); + + JSValue addrinfo_proto = JS_NewObject(js); + JS_SetClassProto(js, js_addrinfo_class_id, addrinfo_proto); + + JSValue mod = JS_NewObject(js); + JS_SetPropertyFunctionList(js, mod, js_socket_funcs, countof(js_socket_funcs)); + + // Add constants + JS_SetPropertyStr(js, mod, "AF_UNSPEC", JS_NewInt32(js, AF_UNSPEC)); + JS_SetPropertyStr(js, mod, "AF_INET", JS_NewInt32(js, AF_INET)); + JS_SetPropertyStr(js, mod, "AF_INET6", JS_NewInt32(js, AF_INET6)); + JS_SetPropertyStr(js, mod, "AF_UNIX", JS_NewInt32(js, AF_UNIX)); + + JS_SetPropertyStr(js, mod, "SOCK_STREAM", JS_NewInt32(js, SOCK_STREAM)); + JS_SetPropertyStr(js, mod, "SOCK_DGRAM", JS_NewInt32(js, SOCK_DGRAM)); + + JS_SetPropertyStr(js, mod, "AI_PASSIVE", JS_NewInt32(js, AI_PASSIVE)); + + JS_SetPropertyStr(js, mod, "SHUT_RD", JS_NewInt32(js, SHUT_RD)); + JS_SetPropertyStr(js, mod, "SHUT_WR", JS_NewInt32(js, SHUT_WR)); + JS_SetPropertyStr(js, mod, "SHUT_RDWR", JS_NewInt32(js, SHUT_RDWR)); + + JS_SetPropertyStr(js, mod, "SOL_SOCKET", JS_NewInt32(js, SOL_SOCKET)); + JS_SetPropertyStr(js, mod, "SO_REUSEADDR", JS_NewInt32(js, SO_REUSEADDR)); + + return mod; +} \ No newline at end of file diff --git a/source/qjs_socket.h b/source/qjs_socket.h new file mode 100644 index 00000000..d2ee0a48 --- /dev/null +++ b/source/qjs_socket.h @@ -0,0 +1,8 @@ +#ifndef QJS_SOCKET_H +#define QJS_SOCKET_H + +#include "quickjs.h" + +JSValue js_socket_use(JSContext *js); + +#endif // QJS_SOCKET_H \ No newline at end of file