sockets and fd
This commit is contained in:
386
source/qjs_fd.c
386
source/qjs_fd.c
@@ -38,7 +38,12 @@ static JSClassDef js_fd_class = {
|
|||||||
// Helper to convert JS value to FDWrapper
|
// Helper to convert JS value to FDWrapper
|
||||||
static FDWrapper *js2fd(JSContext *ctx, JSValueConst obj)
|
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
|
// 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;
|
return wrote;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Glob data structure
|
|
||||||
struct fd_globdata {
|
|
||||||
JSContext *js;
|
|
||||||
JSValue arr;
|
|
||||||
char **globs;
|
|
||||||
int idx;
|
|
||||||
int recurse;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helper for recursive directory enumeration
|
// POSIX FILE DESCRIPTOR FUNCTIONS
|
||||||
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));
|
|
||||||
)
|
|
||||||
|
|
||||||
JSC_SCALL(fd_open,
|
JSC_SCALL(fd_open,
|
||||||
int flags = O_RDWR | O_CREAT;
|
int flags = O_RDWR | O_CREAT;
|
||||||
@@ -359,44 +110,24 @@ JSC_SCALL(fd_open,
|
|||||||
ret = fd2js(js, fd);
|
ret = fd2js(js, fd);
|
||||||
)
|
)
|
||||||
|
|
||||||
JSC_SCALL(fd_is_directory,
|
JSC_CCALL(fd_write,
|
||||||
struct stat st;
|
FDWrapper *fdw = js2fd(js, argv[0]);
|
||||||
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);
|
|
||||||
if (!fdw) return JS_EXCEPTION;
|
if (!fdw) return JS_EXCEPTION;
|
||||||
|
|
||||||
if (fdw->fd >= 0) {
|
ssize_t wrote = js_fd_write_helper(js, fdw->fd, argv[1]);
|
||||||
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]);
|
|
||||||
if (wrote < 0)
|
if (wrote < 0)
|
||||||
return JS_ThrowReferenceError(js, "write failed: %s", strerror(errno));
|
return JS_ThrowReferenceError(js, "write failed: %s", strerror(errno));
|
||||||
|
|
||||||
return JS_NewInt64(js, wrote);
|
return JS_NewInt64(js, wrote);
|
||||||
)
|
)
|
||||||
|
|
||||||
JSC_CCALL(file_read,
|
JSC_CCALL(fd_read,
|
||||||
FDWrapper *fdw = js2fd(js, self);
|
FDWrapper *fdw = js2fd(js, argv[0]);
|
||||||
if (!fdw) return JS_EXCEPTION;
|
if (!fdw) return JS_EXCEPTION;
|
||||||
|
|
||||||
size_t size = 4096;
|
size_t size = 4096;
|
||||||
if (argc > 0)
|
if (argc > 1)
|
||||||
size = js2number(js, argv[0]);
|
size = js2number(js, argv[1]);
|
||||||
|
|
||||||
void *buf = malloc(size);
|
void *buf = malloc(size);
|
||||||
if (!buf)
|
if (!buf)
|
||||||
@@ -413,15 +144,15 @@ JSC_CCALL(file_read,
|
|||||||
return ret;
|
return ret;
|
||||||
)
|
)
|
||||||
|
|
||||||
JSC_CCALL(file_seek,
|
JSC_CCALL(fd_lseek,
|
||||||
FDWrapper *fdw = js2fd(js, self);
|
FDWrapper *fdw = js2fd(js, argv[0]);
|
||||||
if (!fdw) return JS_EXCEPTION;
|
if (!fdw) return JS_EXCEPTION;
|
||||||
|
|
||||||
off_t offset = js2number(js, argv[0]);
|
off_t offset = js2number(js, argv[1]);
|
||||||
int whence = SEEK_SET;
|
int whence = SEEK_SET;
|
||||||
|
|
||||||
if (argc > 1) {
|
if (argc > 2) {
|
||||||
const char *whence_str = JS_ToCString(js, argv[1]);
|
const char *whence_str = JS_ToCString(js, argv[2]);
|
||||||
if (strcmp(whence_str, "cur") == 0) whence = SEEK_CUR;
|
if (strcmp(whence_str, "cur") == 0) whence = SEEK_CUR;
|
||||||
else if (strcmp(whence_str, "end") == 0) whence = SEEK_END;
|
else if (strcmp(whence_str, "end") == 0) whence = SEEK_END;
|
||||||
JS_FreeCString(js, whence_str);
|
JS_FreeCString(js, whence_str);
|
||||||
@@ -434,52 +165,56 @@ JSC_CCALL(file_seek,
|
|||||||
return JS_NewInt64(js, new_pos);
|
return JS_NewInt64(js, new_pos);
|
||||||
)
|
)
|
||||||
|
|
||||||
JSC_CCALL(file_tell,
|
JSC_CCALL(fd_getcwd,
|
||||||
FDWrapper *fdw = js2fd(js, self);
|
char buf[PATH_MAX];
|
||||||
if (!fdw) return JS_EXCEPTION;
|
if (getcwd(buf, sizeof(buf)) == NULL)
|
||||||
|
return JS_ThrowReferenceError(js, "getcwd failed: %s", strerror(errno));
|
||||||
off_t pos = lseek(fdw->fd, 0, SEEK_CUR);
|
return JS_NewString(js, buf);
|
||||||
if (pos < 0)
|
|
||||||
return JS_ThrowReferenceError(js, "lseek failed: %s", strerror(errno));
|
|
||||||
|
|
||||||
return JS_NewInt64(js, pos);
|
|
||||||
)
|
)
|
||||||
|
|
||||||
JSC_CCALL(file_eof,
|
JSC_SCALL(fd_rmdir,
|
||||||
FDWrapper *fdw = js2fd(js, self);
|
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;
|
if (!fdw) return JS_EXCEPTION;
|
||||||
|
|
||||||
off_t cur = lseek(fdw->fd, 0, SEEK_CUR);
|
if (fsync(fdw->fd) != 0)
|
||||||
off_t end = lseek(fdw->fd, 0, SEEK_END);
|
return JS_ThrowReferenceError(js, "fsync failed: %s", strerror(errno));
|
||||||
lseek(fdw->fd, cur, SEEK_SET);
|
|
||||||
|
|
||||||
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[] = {
|
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, open, 2),
|
||||||
MIST_FUNC_DEF(fd, enumerate, 1),
|
MIST_FUNC_DEF(fd, write, 2),
|
||||||
MIST_FUNC_DEF(fd, is_directory, 1),
|
MIST_FUNC_DEF(fd, read, 2),
|
||||||
};
|
MIST_FUNC_DEF(fd, lseek, 3),
|
||||||
|
MIST_FUNC_DEF(fd, getcwd, 0),
|
||||||
static const JSCFunctionListEntry js_fd_file_funcs[] = {
|
MIST_FUNC_DEF(fd, rmdir, 1),
|
||||||
MIST_FUNC_DEF(file, close, 0),
|
MIST_FUNC_DEF(fd, mkdir, 1),
|
||||||
MIST_FUNC_DEF(file, write, 1),
|
MIST_FUNC_DEF(fd, fsync, 1),
|
||||||
MIST_FUNC_DEF(file, read, 1),
|
MIST_FUNC_DEF(fd, close, 1),
|
||||||
MIST_FUNC_DEF(file, seek, 2),
|
|
||||||
MIST_FUNC_DEF(file, tell, 0),
|
|
||||||
MIST_FUNC_DEF(file, eof, 0),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
JSValue js_fd_use(JSContext *js) {
|
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);
|
JS_NewClass(JS_GetRuntime(js), js_fd_class_id, &js_fd_class);
|
||||||
|
|
||||||
JSValue proto = JS_NewObject(js);
|
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);
|
JS_SetClassProto(js, js_fd_class_id, proto);
|
||||||
|
|
||||||
JSValue mod = JS_NewObject(js);
|
JSValue mod = JS_NewObject(js);
|
||||||
|
|||||||
711
source/qjs_socket.c
Normal file
711
source/qjs_socket.c
Normal file
@@ -0,0 +1,711 @@
|
|||||||
|
#include "quickjs.h"
|
||||||
|
#include "jsffi.h"
|
||||||
|
#include "qjs_blob.h"
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
8
source/qjs_socket.h
Normal file
8
source/qjs_socket.h
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user