summaryrefslogtreecommitdiffhomepage
path: root/lib
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2024-04-19 09:19:12 +0200
committerJo-Philipp Wich <jo@mein.io>2024-04-24 12:30:28 +0200
commite8d78a26da0c6a3144b2b08bee989564a516b951 (patch)
tree6ef6a1e786df552d2efb7c697306f834475ebe59 /lib
parentcfe137be068a7ba1895d3c9bcb7b38d21e5a95dd (diff)
lib: introduce socket library
Introduce a new socket module which provides bindings for the BSD sockets API to ucode scripts. Example usage: import * as socket from 'socket'; let sk = socket.create(socket.AF_INET, socket.SOCK_STREAM); sk.connect("192.168.1.1", 80); sk.send("GET / HTTP/1.0\r\n\r\n"); print(sk.recv(4096)); sk.close(); Signed-off-by: Jo-Philipp Wich <jo@mein.io>
Diffstat (limited to 'lib')
-rw-r--r--lib/socket.c3541
1 files changed, 3541 insertions, 0 deletions
diff --git a/lib/socket.c b/lib/socket.c
new file mode 100644
index 0000000..6ec5809
--- /dev/null
+++ b/lib/socket.c
@@ -0,0 +1,3541 @@
+/*
+ * Copyright (C) 2024 Jo-Philipp Wich <jo@mein.io>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * # Socket Module
+ *
+ * The `socket` module provides functions for interacting with sockets.
+ *
+ * Functions can be individually imported and directly accessed using the
+ * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#named_import named import}
+ * syntax:
+ *
+ * ```javascript
+ * import { AF_INET, SOCK_STREAM, create as socket } from 'socket';
+ *
+ * let sock = socket(AF_INET, SOCK_STREAM, 0);
+ * sock.connect('192.168.1.1', 80);
+ * sock.send(…);
+ * sock.recv(…);
+ * sock.close();
+ * ```
+ *
+ * Alternatively, the module namespace can be imported
+ * using a wildcard import statement:
+ *
+ * ```javascript
+ * import * as socket from 'socket';
+ *
+ * let sock = socket.create(socket.AF_INET, socket.SOCK_STREAM, 0);
+ * sock.connect('192.168.1.1', 80);
+ * sock.send(…);
+ * sock.recv(…);
+ * sock.close();
+ * ```
+ *
+ * Additionally, the socket module namespace may also be imported by invoking
+ * the `ucode` interpreter with the `-lsocket` switch.
+ *
+ * @module socket
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <poll.h>
+#include <limits.h>
+
+#include "ucode/module.h"
+#include "ucode/platform.h"
+
+#if defined(__APPLE__)
+# include <sys/ucred.h>
+
+# define SOCK_NONBLOCK (1 << 16)
+# define SOCK_CLOEXEC (1 << 17)
+#endif
+
+#ifndef NI_IDN
+# define NI_IDN 0
+#endif
+
+#ifndef AI_IDN
+# define AI_IDN 0
+#endif
+
+#ifndef AI_CANONIDN
+# define AI_CANONIDN 0
+#endif
+
+#define ok_return(expr) do { set_error(0, NULL); return (expr); } while(0)
+#define err_return(err, ...) do { set_error(err, __VA_ARGS__); return NULL; } while(0)
+
+static struct {
+ int code;
+ char *msg;
+} last_error;
+
+__attribute__((format(printf, 2, 3))) static void
+set_error(int errcode, const char *fmt, ...)
+{
+ va_list ap;
+
+ free(last_error.msg);
+
+ last_error.code = errcode;
+ last_error.msg = NULL;
+
+ if (fmt) {
+ va_start(ap, fmt);
+ xvasprintf(&last_error.msg, fmt, ap);
+ va_end(ap);
+ }
+}
+
+static char *
+arg_type_(uc_type_t type)
+{
+ switch (type) {
+ case UC_INTEGER: return "an integer value";
+ case UC_BOOLEAN: return "a boolean value";
+ case UC_STRING: return "a string value";
+ case UC_DOUBLE: return "a double value";
+ case UC_ARRAY: return "an array";
+ case UC_OBJECT: return "an object";
+ case UC_REGEXP: return "a regular expression";
+ case UC_CLOSURE: return "a function";
+ case UC_RESOURCE: return "a resource value";
+ default: return "the expected type";
+ }
+}
+
+static bool
+args_get_(uc_vm_t *vm, size_t nargs, int *fdptr, ...)
+{
+ const char *name, *rtype = NULL;
+ uc_value_t **ptr, *arg;
+ uc_type_t type, t;
+ size_t index = 0;
+ int *sockfd;
+ va_list ap;
+ bool opt;
+
+ if (fdptr) {
+ sockfd = uc_fn_this("socket");
+
+ if (!sockfd || *sockfd == -1)
+ err_return(EBADF, "Invalid socket context");
+
+ *fdptr = *sockfd;
+ }
+
+ va_start(ap, fdptr);
+
+ while (true) {
+ name = va_arg(ap, const char *);
+
+ if (!name)
+ break;
+
+ arg = uc_fn_arg(index++);
+
+ type = va_arg(ap, uc_type_t);
+ opt = va_arg(ap, int);
+ ptr = va_arg(ap, uc_value_t **);
+
+ if (type == UC_RESOURCE) {
+ rtype = name;
+ name = strrchr(rtype, '.');
+ name = name ? name + 1 : rtype;
+
+ if (arg && !ucv_resource_dataptr(arg, rtype))
+ err_return(EINVAL,
+ "Argument %s is not a %s resource", name, rtype);
+ }
+
+ if (!opt && !arg)
+ err_return(EINVAL,
+ "Argument %s is required", name);
+
+ t = ucv_type(arg);
+
+ if (t == UC_CFUNCTION)
+ t = UC_CLOSURE;
+
+ if (arg && type != UC_NULL && t != type)
+ err_return(EINVAL,
+ "Argument %s is not %s", name, arg_type_(type));
+
+ *ptr = arg;
+ }
+
+ va_end(ap);
+
+ ok_return(true);
+}
+
+#define args_get(vm, nargs, fdptr, ...) do { \
+ if (!args_get_(vm, nargs, fdptr, ##__VA_ARGS__, NULL)) \
+ return NULL; \
+} while(0)
+
+static void
+strbuf_free(uc_stringbuf_t *sb)
+{
+ printbuf_free(sb);
+}
+
+static bool
+strbuf_grow(uc_stringbuf_t *sb, size_t size)
+{
+ if (size > 0) {
+ if (printbuf_memset(sb, sizeof(uc_string_t) + size - 1, '\0', 1))
+ err_return(ENOMEM, "Out of memory");
+ }
+
+ return true;
+}
+
+static char *
+strbuf_data(uc_stringbuf_t *sb)
+{
+ return sb->buf + sizeof(uc_string_t);
+}
+
+static size_t
+strbuf_size(uc_stringbuf_t *sb)
+{
+ return (size_t)sb->bpos - sizeof(uc_string_t);
+}
+
+static uc_value_t *
+strbuf_finish(uc_stringbuf_t **sb, size_t final_size)
+{
+ uc_string_t *us = (uc_string_t *)(*sb)->buf;
+ size_t buffer_size = strbuf_size(*sb);
+
+ if (final_size > buffer_size)
+ final_size = buffer_size;
+
+ free(*sb);
+ *sb = NULL;
+
+ us = xrealloc(us, sizeof(uc_string_t) + final_size + 1);
+ us->length = final_size;
+ us->str[us->length] = 0;
+
+ return &us->header;
+}
+
+static uc_stringbuf_t *
+strbuf_alloc(size_t size)
+{
+ uc_stringbuf_t *sb = ucv_stringbuf_new();
+
+ if (!strbuf_grow(sb, size)) {
+ printbuf_free(sb);
+
+ return NULL;
+ }
+
+ return sb;
+}
+
+static bool
+sockaddr_to_uv(struct sockaddr_storage *ss, uc_value_t *addrobj)
+{
+ char *ifname, addrstr[INET6_ADDRSTRLEN];
+ struct sockaddr_in6 *s6;
+ struct sockaddr_in *s4;
+ struct sockaddr_un *su;
+
+ ucv_object_add(addrobj, "family", ucv_uint64_new(ss->ss_family));
+
+ switch (ss->ss_family) {
+ case AF_INET6:
+ s6 = (struct sockaddr_in6 *)ss;
+
+ inet_ntop(AF_INET6, &s6->sin6_addr, addrstr, sizeof(addrstr));
+ ucv_object_add(addrobj, "address",
+ ucv_string_new(addrstr));
+
+ ucv_object_add(addrobj, "port",
+ ucv_uint64_new(ntohs(s6->sin6_port)));
+
+ ucv_object_add(addrobj, "flowinfo",
+ ucv_uint64_new(ntohl(s6->sin6_flowinfo)));
+
+ if (s6->sin6_scope_id) {
+ ifname = if_indextoname(s6->sin6_scope_id, addrstr);
+
+ if (ifname)
+ ucv_object_add(addrobj, "interface",
+ ucv_string_new(ifname));
+ else
+ ucv_object_add(addrobj, "interface",
+ ucv_uint64_new(s6->sin6_scope_id));
+ }
+
+ return true;
+
+ case AF_INET:
+ s4 = (struct sockaddr_in *)ss;
+
+ inet_ntop(AF_INET, &s4->sin_addr, addrstr, sizeof(addrstr));
+ ucv_object_add(addrobj, "address",
+ ucv_string_new(addrstr));
+
+ ucv_object_add(addrobj, "port",
+ ucv_uint64_new(ntohs(s4->sin_port)));
+
+ return true;
+
+ case AF_UNIX:
+ su = (struct sockaddr_un *)ss;
+
+ ucv_object_add(addrobj, "path",
+ ucv_string_new(su->sun_path));
+
+ return true;
+ }
+
+ return false;
+}
+
+static bool
+parse_addr(char *addr, struct sockaddr_storage *ss)
+{
+ bool v6 = (ss->ss_family == 0 || ss->ss_family == AF_INET6);
+ bool v4 = (ss->ss_family == 0 || ss->ss_family == AF_INET);
+ struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)ss;
+ struct sockaddr_in *s4 = (struct sockaddr_in *)ss;
+ unsigned long n;
+ char *scope, *e;
+
+ if (v6 && (scope = strchr(addr, '%')) != NULL) {
+ *scope++ = 0;
+ n = strtoul(scope, &e, 10);
+
+ if (e == scope || *e != 0) {
+ n = if_nametoindex(scope);
+
+ if (n == 0)
+ err_return(errno, "Unable to resolve interface %s", scope);
+ }
+
+ if (inet_pton(AF_INET6, addr, &s6->sin6_addr) != 1)
+ err_return(errno, "Invalid IPv6 address");
+
+ s6->sin6_family = AF_INET6;
+ s6->sin6_scope_id = n;
+
+ return true;
+ }
+ else if (v6 && inet_pton(AF_INET6, addr, &s6->sin6_addr) == 1) {
+ s6->sin6_family = AF_INET6;
+
+ return true;
+ }
+ else if (v4 && inet_pton(AF_INET, addr, &s4->sin_addr) == 1) {
+ s4->sin_family = AF_INET;
+
+ return true;
+ }
+
+ err_return(EINVAL, "Unable to parse IP address");
+}
+
+static bool
+uv_to_sockaddr(uc_value_t *addr, struct sockaddr_storage *ss, socklen_t *slen)
+{
+ char *s, *p, addrstr[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255%2147483648")];
+ struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)ss;
+ struct sockaddr_in *s4 = (struct sockaddr_in *)ss;
+ struct sockaddr_un *su = (struct sockaddr_un *)ss;
+ uc_value_t *item;
+ unsigned long n;
+ size_t len;
+
+ memset(ss, 0, sizeof(*ss));
+
+ if (ucv_type(addr) == UC_STRING) {
+ s = ucv_string_get(addr);
+ len = ucv_string_length(addr);
+
+ if (memchr(s, '/', len) != NULL) {
+ *slen = ucv_string_length(addr);
+
+ if (*slen >= sizeof(su->sun_path))
+ *slen = sizeof(su->sun_path) - 1;
+
+ memcpy(su->sun_path, s, *slen);
+ su->sun_path[(*slen)++] = 0;
+ su->sun_family = AF_UNIX;
+
+ ok_return(true);
+ }
+
+ if (len == 0)
+ err_return(EINVAL, "Invalid IP address");
+
+ if (*s == '[') {
+ p = memchr(++s, ']', --len);
+
+ if (!p || (size_t)(p - s) >= sizeof(addrstr))
+ err_return(EINVAL, "Invalid IPv6 address");
+
+ memcpy(addrstr, s, p - s);
+ addrstr[(p - s) + 1] = 0;
+
+ ss->ss_family = AF_INET6;
+ len -= ((p - s) + 1);
+ s = p + 1;
+ }
+ else if ((p = memchr(s, ':', len)) != NULL &&
+ memchr(p + 1, ':', len - ((p - s) + 1)) == NULL) {
+ if ((size_t)(p - s) >= sizeof(addrstr))
+ err_return(EINVAL, "Invalid IP address");
+
+ memcpy(addrstr, s, p - s);
+ addrstr[p - s + 1] = 0;
+
+ ss->ss_family = AF_INET;
+ len -= (p - s);
+ s = p;
+ }
+ else {
+ if (len >= sizeof(addrstr))
+ err_return(EINVAL, "Invalid IP address");
+
+ memcpy(addrstr, s, len);
+ addrstr[len] = 0;
+
+ ss->ss_family = 0;
+ len = 0;
+ s = NULL;
+ }
+
+ if (!parse_addr(addrstr, ss))
+ return NULL;
+
+ if (s && *s == ':') {
+ if (len <= 1)
+ err_return(EINVAL, "Invalid port number");
+
+ for (s++, len--, n = 0; len > 0; len--, s++) {
+ if (*s < '0' || *s > '9')
+ err_return(EINVAL, "Invalid port number");
+
+ n = n * 10 + (*s - '0');
+ }
+
+ if (n > 65535)
+ err_return(EINVAL, "Invalid port number");
+
+ s6->sin6_port = htons(n);
+ }
+
+ *slen = (ss->ss_family == AF_INET6) ? sizeof(*s6) : sizeof(*s4);
+
+ ok_return(true);
+ }
+ else if (ucv_type(addr) == UC_ARRAY) {
+ if (ucv_array_length(addr) == 16) {
+ uint8_t *u8 = (uint8_t *)&s6->sin6_addr;
+
+ for (size_t i = 0; i < 16; i++) {
+ item = ucv_array_get(addr, i);
+ n = ucv_uint64_get(item);
+
+ if (ucv_type(item) != UC_INTEGER || errno != 0 || n > 255)
+ err_return(EINVAL, "Invalid IP address array");
+
+ u8[i] = n;
+ }
+
+ s6->sin6_family = AF_INET6;
+ *slen = sizeof(*s6);
+
+ ok_return(true);
+ }
+ else if (ucv_array_length(addr) == 4) {
+ s4->sin_addr.s_addr = 0;
+
+ for (size_t i = 0; i < 4; i++) {
+ item = ucv_array_get(addr, i);
+ n = ucv_uint64_get(item);
+
+ if (ucv_type(item) != UC_INTEGER || errno != 0 || n > 255)
+ err_return(EINVAL, "Invalid IP address array");
+
+ s4->sin_addr.s_addr = s4->sin_addr.s_addr * 256 + n;
+ }
+
+ s4->sin_addr.s_addr = htonl(s4->sin_addr.s_addr);
+ s4->sin_family = AF_INET;
+ *slen = sizeof(*s4);
+
+ ok_return(true);
+ }
+
+ err_return(EINVAL, "Invalid IP address array");
+ }
+ else if (ucv_type(addr) == UC_OBJECT) {
+ n = ucv_to_unsigned(ucv_object_get(addr, "family", NULL));
+
+ if (n == 0) {
+ if (ucv_type(ucv_object_get(addr, "path", NULL)) == UC_STRING) {
+ n = AF_UNIX;
+ }
+ else {
+ item = ucv_object_get(addr, "address", NULL);
+ len = ucv_string_length(item);
+ s = ucv_string_get(item);
+ n = (s && memchr(s, ':', len) != NULL) ? AF_INET6 : AF_INET;
+ }
+
+ if (n == 0)
+ err_return(EINVAL, "Invalid address object");
+ }
+
+ switch (n) {
+ case AF_INET6:
+ item = ucv_object_get(addr, "flowinfo", NULL);
+ s6->sin6_flowinfo = htonl(ucv_to_unsigned(item));
+
+ item = ucv_object_get(addr, "interface", NULL);
+
+ if (ucv_type(item) == UC_STRING) {
+ s6->sin6_scope_id = if_nametoindex(ucv_string_get(item));
+
+ if (s6->sin6_scope_id == 0)
+ err_return(errno, "Unable to resolve interface %s",
+ ucv_string_get(item));
+ }
+ else if (item != NULL) {
+ s6->sin6_scope_id = ucv_to_unsigned(item);
+
+ if (errno != 0)
+ err_return(errno, "Invalid scope ID");
+ }
+
+ /* fall through */
+
+ case AF_INET:
+ ss->ss_family = n;
+ *slen = (n == AF_INET6) ? sizeof(*s6) : sizeof(*s4);
+
+ item = ucv_object_get(addr, "port", NULL);
+ n = ucv_to_unsigned(item);
+
+ if (errno != 0 || n > 65535)
+ err_return(EINVAL, "Invalid port number");
+
+ s6->sin6_port = htons(n);
+
+ item = ucv_object_get(addr, "address", NULL);
+ len = ucv_string_length(item);
+ s = ucv_string_get(item);
+
+ if (len >= sizeof(addrstr))
+ err_return(EINVAL, "Invalid IP address");
+
+ if (len > 0) {
+ memcpy(addrstr, s, len);
+ addrstr[len] = 0;
+
+ if (!parse_addr(addrstr, ss))
+ return NULL;
+ }
+
+ ok_return(true);
+
+ case AF_UNIX:
+ item = ucv_object_get(addr, "path", NULL);
+ len = ucv_string_length(item);
+
+ if (len == 0 || len >= sizeof(su->sun_path))
+ err_return(EINVAL, "Invalid path value");
+
+ memcpy(su->sun_path, ucv_string_get(item), len);
+ su->sun_path[len++] = 0;
+ su->sun_family = AF_UNIX;
+ *slen = len;
+
+ ok_return(true);
+ }
+ }
+
+ err_return(EINVAL, "Invalid address value");
+}
+
+static bool
+uv_to_fileno(uc_vm_t *vm, uc_value_t *val, int *fileno)
+{
+ uc_value_t *fn;
+ int *fdptr;
+
+ fdptr = (int *)ucv_resource_dataptr(val, "socket");
+
+ if (fdptr) {
+ if (*fdptr < 0)
+ err_return(EBADF, "Socket is closed");
+
+ *fileno = *fdptr;
+
+ return true;
+ }
+
+ fn = ucv_property_get(val, "fileno");
+
+ if (ucv_is_callable(fn)) {
+ uc_vm_stack_push(vm, ucv_get(val));
+ uc_vm_stack_push(vm, ucv_get(fn));
+
+ if (uc_vm_call(vm, true, 0) != EXCEPTION_NONE)
+ return false;
+
+ val = uc_vm_stack_pop(vm);
+ }
+ else {
+ ucv_get(val);
+ }
+
+ *fileno = ucv_int64_get(val);
+
+ ucv_put(val);
+
+ if (errno != 0 || *fileno < 0)
+ err_return(EBADF, "Invalid file descriptor number");
+
+ return true;
+}
+
+static bool
+uv_to_pollfd(uc_vm_t *vm, uc_value_t *val, struct pollfd *pfd)
+{
+ int64_t flags;
+
+ if (ucv_type(val) == UC_ARRAY) {
+ if (!uv_to_fileno(vm, ucv_array_get(val, 0), &pfd->fd))
+ return false;
+
+ flags = ucv_to_integer(ucv_array_get(val, 1));
+
+ if (errno != 0 || flags < -32768 || flags > 32767)
+ err_return(ERANGE, "Flags value out of range");
+
+ pfd->events = flags;
+ pfd->revents = 0;
+ }
+ else {
+ if (!uv_to_fileno(vm, val, &pfd->fd))
+ return false;
+
+ pfd->events = POLLIN | POLLERR | POLLHUP;
+ pfd->revents = 0;
+ }
+
+ return true;
+}
+
+static uc_value_t *
+ucv_socket_new(uc_vm_t *vm, int fd)
+{
+ return ucv_resource_new(
+ ucv_resource_type_lookup(vm, "socket"),
+ (void *)(intptr_t)fd
+ );
+}
+
+static bool
+xclose(int *fdptr)
+{
+ bool rv = true;
+
+ if (fdptr) {
+ if (*fdptr >= 0)
+ rv = (close(*fdptr) == 0);
+
+ *fdptr = -1;
+ }
+
+ return rv;
+}
+
+
+typedef struct {
+ const char *name;
+ enum { DT_SIGNED, DT_UNSIGNED, DT_IPV4ADDR, DT_CALLBACK } type;
+ union {
+ size_t offset;
+ bool (*to_c)(void *, uc_value_t *);
+ } u1;
+ union {
+ size_t size;
+ uc_value_t *(*to_uv)(void *);
+ } u2;
+} member_t;
+
+typedef struct {
+ size_t size;
+ member_t *members;
+} struct_t;
+
+typedef struct {
+ int level;
+ int option;
+ struct_t *ctype;
+} sockopt_t;
+
+#define STRUCT_MEMBER_NP(struct_name, member_name, data_type) \
+ { #member_name, data_type, \
+ { .offset = offsetof(struct struct_name, member_name) }, \
+ { .size = sizeof(((struct struct_name *)NULL)->member_name) } }
+
+#define STRUCT_MEMBER_CB(member_name, to_c_fn, to_uv_fn) \
+ { #member_name, DT_CALLBACK, { .to_c = to_c_fn }, { .to_uv = to_uv_fn } }
+
+#define STRUCT_MEMBER(struct_name, member_prefix, member_name, data_type) \
+ { #member_name, data_type, \
+ { .offset = offsetof(struct struct_name, member_prefix##_##member_name) }, \
+ { .size = sizeof(((struct struct_name *)NULL)->member_prefix##_##member_name) } }
+
+static struct_t st_timeval = {
+ .size = sizeof(struct timeval),
+ .members = (member_t []){
+ STRUCT_MEMBER(timeval, tv, sec, DT_SIGNED),
+ STRUCT_MEMBER(timeval, tv, usec, DT_SIGNED),
+ { 0 }
+ }
+};
+
+#if defined(__linux__)
+static struct_t st_ucred = {
+ .size = sizeof(struct ucred),
+ .members = (member_t []){
+ STRUCT_MEMBER_NP(ucred, pid, DT_SIGNED),
+ STRUCT_MEMBER_NP(ucred, uid, DT_SIGNED),
+ STRUCT_MEMBER_NP(ucred, gid, DT_SIGNED),
+ { 0 }
+ }
+};
+#endif
+
+static struct_t st_linger = {
+ .size = sizeof(struct linger),
+ .members = (member_t []){
+ STRUCT_MEMBER(linger, l, onoff, DT_SIGNED),
+ STRUCT_MEMBER(linger, l, linger, DT_SIGNED),
+ { 0 }
+ }
+};
+
+static struct_t st_ip_mreqn = {
+ .size = sizeof(struct ip_mreqn),
+ .members = (member_t []){
+ STRUCT_MEMBER(ip_mreqn, imr, multiaddr, DT_IPV4ADDR),
+ STRUCT_MEMBER(ip_mreqn, imr, address, DT_IPV4ADDR),
+ STRUCT_MEMBER(ip_mreqn, imr, ifindex, DT_SIGNED),
+ { 0 }
+ }
+};
+
+static struct_t st_ip_mreq_source = {
+ .size = sizeof(struct ip_mreq_source),
+ .members = (member_t []){
+ STRUCT_MEMBER(ip_mreq_source, imr, multiaddr, DT_IPV4ADDR),
+ STRUCT_MEMBER(ip_mreq_source, imr, interface, DT_IPV4ADDR),
+ STRUCT_MEMBER(ip_mreq_source, imr, sourceaddr, DT_IPV4ADDR),
+ { 0 }
+ }
+};
+
+#if defined(__linux__)
+static struct_t st_ip_msfilter = {
+ .size = sizeof(struct ip_msfilter),
+ .members = (member_t []){
+ STRUCT_MEMBER(ip_msfilter, imsf, multiaddr, DT_IPV4ADDR),
+ STRUCT_MEMBER(ip_msfilter, imsf, interface, DT_IPV4ADDR),
+ STRUCT_MEMBER(ip_msfilter, imsf, fmode, DT_SIGNED),
+ STRUCT_MEMBER(ip_msfilter, imsf, numsrc, DT_SIGNED),
+ STRUCT_MEMBER(ip_msfilter, imsf, slist, DT_SIGNED),
+ { 0 }
+ }
+};
+
+static uc_value_t *
+snd_wscale_to_uv(void *st)
+{
+ return ucv_uint64_new(((struct tcp_info *)st)->tcpi_snd_wscale);
+}
+
+static uc_value_t *
+rcv_wscale_to_uv(void *st)
+{
+ return ucv_uint64_new(((struct tcp_info *)st)->tcpi_rcv_wscale);
+}
+
+static bool
+snd_wscale_to_c(void *st, uc_value_t *uv)
+{
+ ((struct tcp_info *)st)->tcpi_snd_wscale = ucv_to_unsigned(uv);
+
+ if (errno)
+ err_return(errno, "Unable to convert field snd_wscale to unsigned");
+
+ return true;
+}
+
+static bool
+rcv_wscale_to_c(void *st, uc_value_t *uv)
+{
+ ((struct tcp_info *)st)->tcpi_rcv_wscale = ucv_to_unsigned(uv);
+
+ if (errno)
+ err_return(errno, "Unable to convert field rcv_wscale to unsigned");
+
+ return true;
+}
+
+static struct_t st_tcp_info = {
+ .size = sizeof(struct tcp_info),
+ .members = (member_t []){
+ STRUCT_MEMBER(tcp_info, tcpi, state, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, ca_state, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, retransmits, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, probes, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, backoff, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, options, DT_UNSIGNED),
+ STRUCT_MEMBER_CB(snd_wscale, snd_wscale_to_c, snd_wscale_to_uv),
+ STRUCT_MEMBER_CB(rcv_wscale, rcv_wscale_to_c, rcv_wscale_to_uv),
+ STRUCT_MEMBER(tcp_info, tcpi, rto, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, ato, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, snd_mss, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, rcv_mss, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, unacked, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, sacked, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, lost, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, retrans, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, fackets, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, last_data_sent, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, last_ack_sent, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, last_data_recv, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, last_ack_recv, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, pmtu, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, rcv_ssthresh, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, rtt, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, rttvar, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, snd_ssthresh, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, snd_cwnd, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, advmss, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, reordering, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, rcv_rtt, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, rcv_space, DT_UNSIGNED),
+ STRUCT_MEMBER(tcp_info, tcpi, total_retrans, DT_UNSIGNED),
+ { 0 }
+ }
+};
+#endif
+
+static uc_value_t *
+ai_addr_to_uv(void *st)
+{
+ uc_value_t *rv = ucv_object_new(NULL);
+ struct sockaddr_storage ss = { 0 };
+ struct addrinfo *ai = st;
+
+ memcpy(&ss, ai->ai_addr, ai->ai_addrlen);
+
+ if (!sockaddr_to_uv(&ss, rv)) {
+ ucv_put(rv);
+ return NULL;
+ }
+
+ return rv;
+}
+
+static uc_value_t *
+ai_canonname_to_uv(void *st)
+{
+ struct addrinfo *ai = st;
+ return ai->ai_canonname ? ucv_string_new(ai->ai_canonname) : NULL;
+}
+
+/**
+ * Represents a network address information object returned by
+ * {@link module:socket#addrinfo|`addrinfo()`}.
+ *
+ * @typedef {Object} module:socket.AddressInfo
+ *
+ * @property {module:socket.socket.SocketAddress} addr - A socket address structure.
+ * @property {string} [canonname=null] - The canonical hostname associated with the address.
+ * @property {number} family - The address family (e.g., `2` for `AF_INET`, `10` for `AF_INET6`).
+ * @property {number} flags - Additional flags indicating properties of the address.
+ * @property {number} protocol - The protocol number.
+ * @property {number} socktype - The socket type (e.g., `1` for `SOCK_STREAM`, `2` for `SOCK_DGRAM`).
+ */
+static struct_t st_addrinfo = {
+ .size = sizeof(struct addrinfo),
+ .members = (member_t []){
+ STRUCT_MEMBER(addrinfo, ai, flags, DT_SIGNED),
+ STRUCT_MEMBER(addrinfo, ai, family, DT_SIGNED),
+ STRUCT_MEMBER(addrinfo, ai, socktype, DT_SIGNED),
+ STRUCT_MEMBER(addrinfo, ai, protocol, DT_SIGNED),
+ STRUCT_MEMBER_CB(addr, NULL, ai_addr_to_uv),
+ STRUCT_MEMBER_CB(canonname, NULL, ai_canonname_to_uv),
+ { 0 }
+ }
+};
+
+#define SV_VOID (struct_t *)0
+#define SV_INT (struct_t *)1
+#define SV_INT_RO (struct_t *)2
+#define SV_BOOL (struct_t *)3
+#define SV_STRING (struct_t *)4
+
+static sockopt_t sockopts[] = {
+ { SOL_SOCKET, SO_ACCEPTCONN, SV_BOOL },
+ { SOL_SOCKET, SO_BROADCAST, SV_BOOL },
+ { SOL_SOCKET, SO_DEBUG, SV_BOOL },
+ { SOL_SOCKET, SO_ERROR, SV_INT_RO },
+ { SOL_SOCKET, SO_DONTROUTE, SV_BOOL },
+ { SOL_SOCKET, SO_KEEPALIVE, SV_BOOL },
+ { SOL_SOCKET, SO_LINGER, &st_linger },
+ { SOL_SOCKET, SO_OOBINLINE, SV_BOOL },
+ { SOL_SOCKET, SO_RCVBUF, SV_INT },
+ { SOL_SOCKET, SO_RCVLOWAT, SV_INT },
+ { SOL_SOCKET, SO_RCVTIMEO, &st_timeval },
+ { SOL_SOCKET, SO_REUSEADDR, SV_BOOL },
+ { SOL_SOCKET, SO_REUSEPORT, SV_BOOL },
+ { SOL_SOCKET, SO_SNDBUF, SV_INT },
+ { SOL_SOCKET, SO_SNDLOWAT, SV_INT },
+ { SOL_SOCKET, SO_SNDTIMEO, &st_timeval },
+ { SOL_SOCKET, SO_TIMESTAMP, SV_BOOL },
+ { SOL_SOCKET, SO_TYPE, SV_INT },
+#if defined(__linux__)
+ { SOL_SOCKET, SO_ATTACH_FILTER, SV_STRING },
+ { SOL_SOCKET, SO_ATTACH_BPF, SV_INT },
+ { SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, SV_STRING },
+ { SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF, SV_INT },
+ { SOL_SOCKET, SO_BINDTODEVICE, SV_STRING },
+ { SOL_SOCKET, SO_DETACH_FILTER, SV_VOID },
+ { SOL_SOCKET, SO_DETACH_BPF, SV_VOID },
+ { SOL_SOCKET, SO_DOMAIN, SV_INT_RO },
+ { SOL_SOCKET, SO_INCOMING_CPU, SV_INT },
+ { SOL_SOCKET, SO_INCOMING_NAPI_ID, SV_INT_RO },
+ { SOL_SOCKET, SO_LOCK_FILTER, SV_INT },
+ { SOL_SOCKET, SO_MARK, SV_INT },
+ { SOL_SOCKET, SO_PASSCRED, SV_BOOL },
+ { SOL_SOCKET, SO_PASSSEC, SV_BOOL },
+ { SOL_SOCKET, SO_PEEK_OFF, SV_INT },
+ { SOL_SOCKET, SO_PEERCRED, &st_ucred },
+ { SOL_SOCKET, SO_PEERSEC, SV_STRING },
+ { SOL_SOCKET, SO_PRIORITY, SV_INT },
+ { SOL_SOCKET, SO_PROTOCOL, SV_INT },
+ { SOL_SOCKET, SO_RCVBUFFORCE, SV_INT },
+ { SOL_SOCKET, SO_RXQ_OVFL, SV_BOOL },
+ { SOL_SOCKET, SO_SNDBUFFORCE, SV_INT },
+ { SOL_SOCKET, SO_TIMESTAMPNS, SV_BOOL },
+ { SOL_SOCKET, SO_BUSY_POLL, SV_INT },
+#endif
+
+ { IPPROTO_IP, IP_ADD_MEMBERSHIP, &st_ip_mreqn },
+ { IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, &st_ip_mreq_source },
+ { IPPROTO_IP, IP_BLOCK_SOURCE, &st_ip_mreq_source },
+ { IPPROTO_IP, IP_DROP_MEMBERSHIP, &st_ip_mreqn },
+ { IPPROTO_IP, IP_DROP_SOURCE_MEMBERSHIP, &st_ip_mreq_source },
+ { IPPROTO_IP, IP_HDRINCL, SV_BOOL },
+ { IPPROTO_IP, IP_MULTICAST_IF, &st_ip_mreqn },
+ { IPPROTO_IP, IP_MULTICAST_LOOP, SV_BOOL },
+ { IPPROTO_IP, IP_MULTICAST_TTL, SV_INT },
+ { IPPROTO_IP, IP_OPTIONS, SV_STRING },
+ { IPPROTO_IP, IP_PKTINFO, SV_BOOL },
+ { IPPROTO_IP, IP_RECVOPTS, SV_BOOL },
+ { IPPROTO_IP, IP_RECVTOS, SV_BOOL },
+ { IPPROTO_IP, IP_RECVTTL, SV_BOOL },
+ { IPPROTO_IP, IP_RETOPTS, SV_BOOL },
+ { IPPROTO_IP, IP_TOS, SV_INT },
+ { IPPROTO_IP, IP_TTL, SV_INT },
+ { IPPROTO_IP, IP_UNBLOCK_SOURCE, &st_ip_mreq_source },
+#if defined(__linux__)
+ { IPPROTO_IP, IP_MSFILTER, &st_ip_msfilter },
+ { IPPROTO_IP, IP_BIND_ADDRESS_NO_PORT, SV_BOOL },
+ { IPPROTO_IP, IP_FREEBIND, SV_BOOL },
+ { IPPROTO_IP, IP_MTU, SV_INT },
+ { IPPROTO_IP, IP_MTU_DISCOVER, SV_INT },
+ { IPPROTO_IP, IP_MULTICAST_ALL, SV_BOOL },
+ { IPPROTO_IP, IP_NODEFRAG, SV_BOOL },
+ { IPPROTO_IP, IP_PASSSEC, SV_BOOL },
+ { IPPROTO_IP, IP_RECVERR, SV_BOOL },
+ { IPPROTO_IP, IP_RECVORIGDSTADDR, SV_BOOL },
+ { IPPROTO_IP, IP_ROUTER_ALERT, SV_BOOL },
+ { IPPROTO_IP, IP_TRANSPARENT, SV_BOOL },
+#endif
+
+ { IPPROTO_TCP, TCP_KEEPCNT, SV_INT },
+ { IPPROTO_TCP, TCP_KEEPINTVL, SV_INT },
+ { IPPROTO_TCP, TCP_MAXSEG, SV_INT },
+ { IPPROTO_TCP, TCP_NODELAY, SV_BOOL },
+ { IPPROTO_TCP, TCP_FASTOPEN, SV_INT },
+#if defined(__linux__)
+ { IPPROTO_TCP, TCP_CONGESTION, SV_STRING },
+ { IPPROTO_TCP, TCP_CORK, SV_BOOL },
+ { IPPROTO_TCP, TCP_DEFER_ACCEPT, SV_INT },
+ { IPPROTO_TCP, TCP_INFO, &st_tcp_info },
+ { IPPROTO_TCP, TCP_KEEPIDLE, SV_INT },
+ { IPPROTO_TCP, TCP_LINGER2, SV_INT },
+ { IPPROTO_TCP, TCP_QUICKACK, SV_BOOL },
+ { IPPROTO_TCP, TCP_SYNCNT, SV_INT },
+ { IPPROTO_TCP, TCP_USER_TIMEOUT, SV_INT },
+ { IPPROTO_TCP, TCP_WINDOW_CLAMP, SV_INT },
+ { IPPROTO_TCP, TCP_FASTOPEN_CONNECT, SV_INT },
+#endif
+
+#if defined(__linux__)
+ { IPPROTO_UDP, UDP_CORK, SV_BOOL }
+#endif
+};
+
+
+static char *
+uv_to_struct(uc_value_t *uv, struct_t *spec)
+{
+ uc_value_t *fv;
+ const char *s;
+ uint64_t u64;
+ int64_t s64;
+ member_t *m;
+ bool found;
+ char *st;
+
+ union {
+ int8_t s8;
+ int16_t s16;
+ int32_t s32;
+ int64_t s64;
+ uint8_t u8;
+ uint16_t u16;
+ uint32_t u32;
+ uint64_t u64;
+ } v;
+
+ st = xalloc(spec->size);
+
+ for (size_t i = 0; spec->members[i].name; i++) {
+ m = &spec->members[i];
+ fv = ucv_object_get(uv, m->name, &found);
+
+ if (!found || !fv)
+ continue;
+
+ switch (spec->members[i].type) {
+ case DT_UNSIGNED:
+ u64 = ucv_to_unsigned(fv);
+
+ if (errno) {
+ free(st);
+ err_return(errno,
+ "Unable to convert field %s to unsigned",
+ m->name);
+ }
+
+ switch (m->u2.size) {
+ case 1: v.u8 = (uint8_t)u64; break;
+ case 2: v.u16 = (uint16_t)u64; break;
+ case 4: v.u32 = (uint32_t)u64; break;
+ case 8: v.u64 = (uint64_t)u64; break;
+ }
+
+ memcpy(st + m->u1.offset, &v, m->u2.size);
+ break;
+
+ case DT_SIGNED:
+ s64 = ucv_to_integer(fv);
+
+ if (errno) {
+ free(st);
+ err_return(errno,
+ "Unable to convert field %s to integer", m->name);
+ }
+
+ switch (m->u2.size) {
+ case 1: v.s8 = (int8_t)s64; break;
+ case 2: v.s16 = (int16_t)s64; break;
+ case 4: v.s32 = (int32_t)s64; break;
+ case 8: v.s64 = (int64_t)s64; break;
+ }
+
+ memcpy(st + m->u1.offset, &v, m->u2.size);
+ break;
+
+ case DT_IPV4ADDR:
+ s = ucv_string_get(fv);
+
+ if (!s || inet_pton(AF_INET, s, st + m->u1.offset) != 1) {
+ free(st);
+ err_return(EINVAL,
+ "Unable to convert field %s to IP address", m->name);
+ }
+
+ break;
+
+ case DT_CALLBACK:
+ if (m->u1.to_c && !m->u1.to_c(st, fv)) {
+ free(st);
+ return NULL;
+ }
+
+ break;
+ }
+ }
+
+ return st;
+}
+
+static uc_value_t *
+struct_to_uv(char *st, struct_t *spec)
+{
+ char s[sizeof("255.255.255.255")];
+ uc_value_t *uv, *fv;
+ member_t *m;
+
+ uv = ucv_object_new(NULL);
+
+ for (size_t i = 0; spec->members[i].name; i++) {
+ m = &spec->members[i];
+ fv = NULL;
+
+ switch (spec->members[i].type) {
+ case DT_UNSIGNED:
+ switch (spec->members[i].u2.size) {
+ case 1:
+ fv = ucv_uint64_new(*(uint8_t *)(st + m->u1.offset));
+ break;
+
+ case 2:
+ fv = ucv_uint64_new(*(uint16_t *)(st + m->u1.offset));
+ break;
+
+ case 4:
+ fv = ucv_uint64_new(*(uint32_t *)(st + m->u1.offset));
+ break;
+
+ case 8:
+ fv = ucv_uint64_new(*(uint64_t *)(st + m->u1.offset));
+ break;
+ }
+
+ break;
+
+ case DT_SIGNED:
+ switch (spec->members[i].u2.size) {
+ case 1:
+ fv = ucv_int64_new(*(int8_t *)(st + m->u1.offset));
+ break;
+
+ case 2:
+ fv = ucv_int64_new(*(int16_t *)(st + m->u1.offset));
+ break;
+
+ case 4:
+ fv = ucv_int64_new(*(int32_t *)(st + m->u1.offset));
+ break;
+
+ case 8:
+ fv = ucv_int64_new(*(int64_t *)(st + m->u1.offset));
+ break;
+ }
+
+ break;
+
+ case DT_IPV4ADDR:
+ if (inet_ntop(AF_INET, st + m->u1.offset, s, sizeof(s)))
+ fv = ucv_string_new(s);
+
+ break;
+
+ case DT_CALLBACK:
+ fv = m->u2.to_uv ? m->u2.to_uv(st) : NULL;
+ break;
+ }
+
+ ucv_object_add(uv, m->name, fv);
+ }
+
+ return uv;
+}
+
+/**
+ * Sets options on the socket.
+ *
+ * Sets the specified option on the socket to the given value.
+ *
+ * Returns `true` if the option was successfully set.
+ *
+ * Returns `null` if an error occurred.
+ *
+ * @function module:socket.socket#setopt
+ *
+ * @param {number} level
+ * The protocol level at which the option resides. This can be a level such as
+ * `SOL_SOCKET` for the socket API level or a specific protocol level defined
+ * by the system.
+ *
+ * @param {number} option
+ * The socket option to set. This can be an integer representing the option,
+ * such as `SO_REUSEADDR`, or a constant defined by the system.
+ *
+ * @param {*} value
+ * The value to set the option to. The type of this argument depends on the
+ * specific option being set. It can be an integer, a boolean, a string, or a
+ * dictionary representing the value to set. If a dictionary is provided, it is
+ * internally translated to the corresponding C struct type required by the
+ * option.
+ *
+ * @returns {?boolean}
+ */
+static uc_value_t *
+uc_socket_inst_setopt(uc_vm_t *vm, size_t nargs)
+{
+ int sockfd, solvl, soopt, soval, ret;
+ uc_value_t *level, *option, *value;
+ void *valptr = NULL, *st = NULL;
+ socklen_t vallen = 0;
+ size_t i;
+
+ args_get(vm, nargs, &sockfd,
+ "level", UC_INTEGER, false, &level,
+ "option", UC_INTEGER, false, &option,
+ "value", UC_NULL, false, &value);
+
+ solvl = ucv_int64_get(level);
+ soopt = ucv_int64_get(option);
+
+ for (i = 0; i < ARRAY_SIZE(sockopts); i++) {
+ if (sockopts[i].level != solvl || sockopts[i].option != soopt)
+ continue;
+
+ switch ((uintptr_t)sockopts[i].ctype) {
+ case (uintptr_t)SV_INT_RO:
+ err_return(EOPNOTSUPP, "Socket option is read only");
+
+ case (uintptr_t)SV_VOID:
+ valptr = NULL;
+ vallen = 0;
+ break;
+
+ case (uintptr_t)SV_INT:
+ soval = ucv_to_integer(value);
+
+ if (errno)
+ err_return(errno, "Unable to convert value to integer");
+
+ valptr = &soval;
+ vallen = sizeof(int);
+ break;
+
+ case (uintptr_t)SV_BOOL:
+ soval = ucv_to_unsigned(value) ? 1 : 0;
+
+ if (errno)
+ err_return(errno, "Unable to convert value to boolean");
+
+ valptr = &soval;
+ vallen = sizeof(int);
+ break;
+
+ case (uintptr_t)SV_STRING:
+ valptr = ucv_string_get(value);
+ vallen = ucv_string_length(value);
+ break;
+
+ default:
+ st = uv_to_struct(value, sockopts[i].ctype);
+ valptr = st;
+ vallen = sockopts[i].ctype->size;
+ break;
+ }
+
+ break;
+ }
+
+ if (i == ARRAY_SIZE(sockopts))
+ err_return(EINVAL, "Unknown socket level or option");
+
+ ret = setsockopt(sockfd, solvl, soopt, valptr, vallen);
+
+ free(st);
+
+ if (ret == -1)
+ err_return(errno, "setsockopt() failed");
+
+ ok_return(ucv_boolean_new(true));
+}
+
+/**
+ * Gets options from the socket.
+ *
+ * Retrieves the value of the specified option from the socket.
+ *
+ * Returns the value of the requested option.
+ *
+ * Returns `null` if an error occurred or if the option is not supported.
+ *
+ * @function module:socket.socket#getopt
+ *
+ * @param {number} level
+ * The protocol level at which the option resides. This can be a level such as
+ * `SOL_SOCKET` for the socket API level or a specific protocol level defined
+ * by the system.
+ *
+ * @param {number} option
+ * The socket option to retrieve. This can be an integer representing the
+ * option, such as `SO_REUSEADDR`, or a constant defined by the system.
+ *
+ * @returns {?*}
+ * The value of the requested option. The type of the returned value depends
+ * on the specific option being retrieved. It can be an integer, a boolean, a
+ * string, or a dictionary representing a complex data structure.
+ */
+static uc_value_t *
+uc_socket_inst_getopt(uc_vm_t *vm, size_t nargs)
+{
+ uc_value_t *level, *option, *value = NULL;
+ int sockfd, solvl, soopt, soval, ret;
+ void *valptr = NULL, *st = NULL;
+ uc_stringbuf_t *sb = NULL;
+ socklen_t vallen;
+ size_t i;
+
+ args_get(vm, nargs, &sockfd,
+ "level", UC_INTEGER, false, &level,
+ "option", UC_INTEGER, false, &option);
+
+ solvl = ucv_int64_get(level);
+ soopt = ucv_int64_get(option);
+ soval = 0;
+
+ for (i = 0; i < ARRAY_SIZE(sockopts); i++) {
+ if (sockopts[i].level != solvl || sockopts[i].option != soopt)
+ continue;
+
+ switch ((uintptr_t)sockopts[i].ctype) {
+ case (uintptr_t)SV_VOID:
+ err_return(EOPNOTSUPP, "Socket option is write only");
+
+ case (uintptr_t)SV_INT:
+ case (uintptr_t)SV_INT_RO:
+ case (uintptr_t)SV_BOOL:
+ valptr = &soval;
+ vallen = sizeof(int);
+ break;
+
+ case (uintptr_t)SV_STRING:
+ sb = strbuf_alloc(64);
+ valptr = strbuf_data(sb);
+ vallen = strbuf_size(sb);
+ break;
+
+ default:
+ st = xalloc(sockopts[i].ctype->size);
+ valptr = st;
+ vallen = sockopts[i].ctype->size;
+ break;
+ }
+
+ break;
+ }
+
+ if (i == ARRAY_SIZE(sockopts))
+ err_return(EINVAL, "Unknown socket level or option");
+
+ while (true) {
+ ret = getsockopt(sockfd, solvl, soopt, valptr, &vallen);
+
+ if (sockopts[i].ctype == SV_STRING &&
+ (ret == 0 || (ret == -1 && errno == ERANGE)) &&
+ vallen > strbuf_size(sb)) {
+
+ if (!strbuf_grow(sb, vallen))
+ return NULL;
+
+ valptr = strbuf_data(sb);
+ continue;
+ }
+
+ break;
+ }
+
+ if (ret == 0) {
+ switch ((uintptr_t)sockopts[i].ctype) {
+ case (uintptr_t)SV_VOID:
+ break;
+
+ case (uintptr_t)SV_INT:
+ case (uintptr_t)SV_INT_RO:
+ value = ucv_int64_new(soval);
+ break;
+
+ case (uintptr_t)SV_BOOL:
+ value = ucv_boolean_new(soval);
+ break;
+
+ case (uintptr_t)SV_STRING:
+ value = strbuf_finish(&sb, vallen);
+ break;
+
+ default:
+ value = struct_to_uv(st, sockopts[i].ctype);
+ break;
+ }
+ }
+
+ strbuf_free(sb);
+ free(st);
+
+ if (ret == -1)
+ err_return(errno, "getsockopt() failed");
+
+ ok_return(value);
+}
+
+/**
+ * Returns the UNIX file descriptor number associated with the socket.
+ *
+ * Returns the file descriptor number.
+ *
+ * Returns `-1` if an error occurred.
+ *
+ * @function module:socket.socket#fileno
+ *
+ * @returns {number}
+ */
+static uc_value_t *
+uc_socket_inst_fileno(uc_vm_t *vm, size_t nargs)
+{
+ int sockfd;
+
+ args_get(vm, nargs, &sockfd);
+
+ ok_return(ucv_int64_new(sockfd));
+}
+
+/**
+ * Query error information.
+ *
+ * Returns a string containing a description of the last occurred error or
+ * `null` if there is no error information.
+ *
+ * @function module:socket#error
+ *
+ *
+ * @returns {?string}
+ *
+ * @example
+ * // Trigger file system error
+ * unlink('/path/does/not/exist');
+ *
+ * // Print error (should yield "No such file or directory")
+ * print(error(), "\n");
+ */
+static uc_value_t *
+uc_socket_error(uc_vm_t *vm, size_t nargs)
+{
+ uc_value_t *numeric = uc_fn_arg(0), *rv;
+ uc_stringbuf_t *buf;
+
+ if (last_error.code == 0)
+ return NULL;
+
+ if (ucv_is_truish(numeric)) {
+ rv = ucv_int64_new(last_error.code);
+ }
+ else {
+ buf = ucv_stringbuf_new();
+
+ if (last_error.code == 0 && last_error.msg) {
+ ucv_stringbuf_addstr(buf, last_error.msg, strlen(last_error.msg));
+ }
+ else {
+ if (last_error.code >= 0)
+ ucv_stringbuf_printf(buf, "%s", strerror(last_error.code));
+ else
+ ucv_stringbuf_printf(buf, "%s", gai_strerror(last_error.code));
+
+ if (last_error.msg)
+ ucv_stringbuf_printf(buf, ": %s", last_error.msg);
+ }
+
+ rv = ucv_stringbuf_finish(buf);
+ }
+
+ ok_return(rv);
+}
+
+/**
+ * @typedef {Object} module:socket.socket.SocketAddress
+ * @property {number} family
+ * Address family, one of AF_INET, AF_INET6 or AF_UNIX.
+ *
+ * @property {string} address
+ * IPv4 or IPv6 address string (AF_INET or AF_INET6 only).
+ *
+ * @property {number} [port]
+ * Port number (AF_INET or AF_INET6 only).
+ *
+ * @property {number} [flowinfo]
+ * IPv6 flow information (AF_INET6 only).
+ *
+ * @property {string|number} [interface]
+ * Link local address scope, either a network device name string or a nonzero
+ * positive integer representing a network interface index (AF_INET6 only).
+ *
+ * @property {string} path
+ * Domain socket filesystem path (AF_UNIX only).
+ */
+
+/**
+ * Parses the provided address value into a socket address representation.
+ *
+ * This function parses the given address value into a socket address
+ * representation required for a number of socket operations. The address value
+ * can be provided in various formats:
+ * - For IPv4 addresses, it can be a string representing the IP address,
+ * optionally followed by a port number separated by colon, e.g.
+ * `192.168.0.1:8080`.
+ * - For IPv6 addresses, it must be an address string enclosed in square
+ * brackets if a port number is specified, otherwise the brackets are
+ * optional. The address string may also include a scope ID in the form
+ * `%ifname` or `%number`, e.g. `[fe80::1%eth0]:8080` or `fe80::1%15`.
+ * - Any string value containing a slash is treated as UNIX domain socket path.
+ * - Alternatively, it can be provided as an array returned by
+ * {@link module:core#iptoarr|iptoarr()}, representing the address octets.
+ * - It can also be an object representing a network address, with properties
+ * for `address` (the IP address) and `port` or a single property `path` to
+ * denote a UNIX domain socket address.
+ *
+ * @function module:socket#sockaddr
+ *
+ * @param {string|number[]|module:socket.socket.SocketAddress} address
+ * The address value to parse.
+ *
+ * @returns {?module:socket.socket.SocketAddress}
+ * A socket address representation of the provided address value, or `null` if
+ * the address could not be parsed.
+ *
+ * @example
+ * // Parse an IP address string with port
+ * const address1 = sockaddr('192.168.0.1:8080');
+ *
+ * // Parse an IPv6 address string with port and scope identifier
+ * const address2 = sockaddr('[fe80::1%eth0]:8080');
+ *
+ * // Parse an array representing an IP address
+ * const address3 = sockaddr([192, 168, 0, 1]);
+ *
+ * // Parse a network address object
+ * const address4 = sockaddr({ address: '192.168.0.1', port: 8080 });
+ *
+ * // Convert a path value to a UNIX domain socket address
+ * const address5 = sockaddr('/var/run/daemon.sock');
+ */
+static uc_value_t *
+uc_socket_sockaddr(uc_vm_t *vm, size_t nargs)
+{
+ struct sockaddr_storage ss = { 0 };
+ uc_value_t *addr, *rv;
+ socklen_t slen;
+
+ args_get(vm, nargs, NULL,
+ "address", UC_NULL, false, &addr);
+
+ if (!uv_to_sockaddr(addr, &ss, &slen))
+ return NULL;
+
+ rv = ucv_object_new(vm);
+
+ if (!sockaddr_to_uv(&ss, rv)) {
+ ucv_put(rv);
+ return NULL;
+ }
+
+ ok_return(rv);
+}
+
+/**
+ * Resolves the given network address into hostname and service name.
+ *
+ * The `nameinfo()` function provides an API for reverse DNS lookup and service
+ * name resolution. It returns an object containing the following properties:
+ * - `hostname`: The resolved hostname.
+ * - `service`: The resolved service name.
+ *
+ * Returns an object representing the resolved hostname and service name.
+ * Return `null` if an error occurred during resolution.
+ *
+ * @function module:socket#nameinfo
+ *
+ * @param {string|module:socket.socket.SocketAddress} address
+ * The network address to resolve. It can be specified as:
+ * - A string representing the IP address.
+ * - An object representing the address with properties `address` and `port`.
+ *
+ * @param {number} [flags]
+ * Optional flags that provide additional control over the resolution process,
+ * specified as bitwise OR-ed number of `NI_*` constants.
+ *
+ * @returns {?{hostname: string, service: string}}
+ *
+ * @see {@link module:socket~"Socket Types"|Socket Types}
+ * @see {@link module:socket~"Name Info Constants"|AName Info Constants}
+ *
+ * @example
+ * // Resolve a network address into hostname and service name
+ * const result = network.getnameinfo('192.168.1.1:80');
+ * print(result); // { "hostname": "example.com", "service": "http" }
+ */
+static uc_value_t *
+uc_socket_nameinfo(uc_vm_t *vm, size_t nargs)
+{
+ char host[NI_MAXHOST], serv[NI_MAXSERV];
+ uc_value_t *addr, *flags, *rv;
+ struct sockaddr_storage ss;
+ socklen_t slen;
+ int ret;
+
+ args_get(vm, nargs, NULL,
+ "address", UC_NULL, false, &addr,
+ "flags", UC_INTEGER, true, &flags);
+
+ if (!uv_to_sockaddr(addr, &ss, &slen))
+ return NULL;
+
+ ret = getnameinfo((struct sockaddr *)&ss, slen,
+ host, sizeof(host), serv, sizeof(serv),
+ flags ? ucv_int64_get(flags) : 0);
+
+ if (ret != 0)
+ err_return((ret == EAI_SYSTEM) ? errno : ret, "getnameinfo() failed");
+
+ rv = ucv_object_new(vm);
+
+ ucv_object_add(rv, "hostname", ucv_string_new(host));
+ ucv_object_add(rv, "service", ucv_string_new(serv));
+
+ ok_return(rv);
+}
+
+/**
+ * Resolves the given hostname and optional service name into a list of network
+ * addresses, according to the provided hints.
+ *
+ * The `addrinfo()` function provides an API for performing DNS and service name
+ * resolution. It returns an array of objects, each representing a resolved
+ * address.
+ *
+ * Returns an array of resolved addresses.
+ * Returns `null` if an error occurred during resolution.
+ *
+ * @function module:socket#addrinfo
+ *
+ * @param {string} hostname
+ * The hostname to resolve.
+ *
+ * @param {string} [service]
+ * Optional service name to resolve. If not provided, the service field of the
+ * resulting address information structures is left uninitialized.
+ *
+ * @param {Object} [hints]
+ * Optional hints object that provides additional control over the resolution
+ * process. It can contain the following properties:
+ * - `family`: The preferred address family (`AF_INET` or `AF_INET6`).
+ * - `socktype`: The socket type (`SOCK_STREAM`, `SOCK_DGRAM`, etc.).
+ * - `protocol`: The protocol of returned addresses.
+ * - `flags`: Bitwise OR-ed `AI_*` flags to control the resolution behavior.
+ *
+ * @returns {?module:socket.AddressInfo[]}
+ *
+ * @see {@link module:socket~"Socket Types"|Socket Types}
+ * @see {@link module:socket~"Address Info Flags"|Address Info Flags}
+ *
+ * @example
+ * // Resolve all addresses
+ * const addresses = socket.addrinfo('example.org');
+ *
+ * // Resolve IPv4 addresses for a given hostname and service
+ * const ipv4addresses = socket.addrinfo('example.com', 'http', { family: socket.AF_INET });
+ *
+ * // Resolve IPv6 addresses without specifying a service
+ * const ipv6Addresses = socket.addrinfo('example.com', null, { family: socket.AF_INET6 });
+ */
+
+static uc_value_t *
+uc_socket_addrinfo(uc_vm_t *vm, size_t nargs)
+{
+ struct addrinfo *ai_hints = NULL, *ai_res;
+ uc_value_t *host, *serv, *hints, *rv;
+ char *servstr;
+ int ret;
+
+ args_get(vm, nargs, NULL,
+ "hostname", UC_STRING, false, &host,
+ "service", UC_NULL, true, &serv,
+ "hints", UC_OBJECT, true, &hints);
+
+ if (hints) {
+ ai_hints = (struct addrinfo *)uv_to_struct(hints, &st_addrinfo);
+
+ if (!ai_hints)
+ return NULL;
+ }
+
+ servstr = (ucv_type(serv) != UC_STRING) ? ucv_to_string(vm, serv) : NULL;
+ ret = getaddrinfo(ucv_string_get(host),
+ servstr ? servstr : ucv_string_get(serv),
+ ai_hints, &ai_res);
+
+ free(ai_hints);
+ free(servstr);
+
+ if (ret != 0)
+ err_return((ret == EAI_SYSTEM) ? errno : ret, "getaddrinfo() failed");
+
+ rv = ucv_array_new(vm);
+
+ for (struct addrinfo *ai = ai_res; ai; ai = ai->ai_next) {
+ uc_value_t *item = struct_to_uv((char *)ai, &st_addrinfo);
+
+ if (item)
+ ucv_array_push(rv, item);
+ }
+
+ freeaddrinfo(ai_res);
+
+ ok_return(rv);
+}
+
+/**
+ * Represents a poll state serving as input parameter and return value type for
+ * {@link module:socket#poll|`poll()`}.
+ *
+ * @typedef {Array} module:socket.PollSpec
+ * @property {module:socket.socket} 0
+ * The polled socket instance.
+ *
+ * @property {number} 1
+ * Requested or returned status flags of the polled socket instance.
+ */
+
+/**
+ * Polls a number of sockets for state changes.
+ *
+ * Returns an array of socket poll states.
+ * Returns `null` if an error occurred.
+ *
+ * @function module:socket#poll
+ *
+ * @param {number} timeout
+ * Amount of milliseconds to wait for socket activity before aborting the poll
+ * call. If set to `0`, the poll call will return immediately if none of the
+ * provided sockets has pending events, if set to a negative value, the poll
+ * call will wait indefinitely, in all other cases the poll call will wait at
+ * most for the given amount of milliseconds before returning.
+ *
+ * @param {...(module:socket.socket|module:socket.PollSpec)} sockets
+ * An arbitrary amount of socket arguments. Each argument may be either a plain
+ * {@link module:socket.socket|socket instance} or a `[socket, flags]` tuple
+ * specifying the socket and requested poll flags. If a plain socket and not a
+ * tuple is provided, the requested poll flags default to
+ * `POLLIN|POLLERR|POLLHUP` for this socket.
+ *
+ * @returns {module:socket.PollSpec[]}
+ */
+static uc_value_t *
+uc_socket_poll(uc_vm_t *vm, size_t nargs)
+{
+ struct { struct pollfd *entries; size_t count; } pfds = { 0 };
+ uc_value_t *timeoutarg, *rv, *item;
+ int64_t timeout;
+ int ret;
+
+ args_get(vm, nargs, NULL, "timeout", UC_INTEGER, false, &timeoutarg);
+
+ timeout = ucv_to_integer(timeoutarg);
+
+ if (errno != 0 || timeout < (int64_t)INT_MIN || timeout > (int64_t)INT_MAX)
+ err_return(ERANGE, "Invalid timeout value");
+
+ rv = ucv_array_new(vm);
+
+ for (size_t i = 1; i < nargs; i++) {
+ uc_vector_grow(&pfds);
+
+ if (uv_to_pollfd(vm, uc_fn_arg(i), &pfds.entries[pfds.count])) {
+ item = ucv_array_new_length(vm, 2);
+ ucv_array_set(item, 0, ucv_get(uc_fn_arg(i)));
+ ucv_array_set(rv, pfds.count++, item);
+ }
+ }
+
+ ret = poll(pfds.entries, pfds.count, timeout);
+
+ if (ret == -1) {
+ ucv_put(rv);
+ uc_vector_clear(&pfds);
+ err_return(errno, "poll() failed");
+ }
+
+ for (size_t i = 0; i < pfds.count; i++)
+ ucv_array_set(ucv_array_get(rv, i), 1,
+ ucv_int64_new(pfds.entries[i].revents));
+
+ uc_vector_clear(&pfds);
+ ok_return(rv);
+}
+
+/**
+ * Creates a network socket and connects it to the specified host and service.
+ *
+ * This high level function combines the functionality of
+ * {@link module:socket#create|create()},
+ * {@link module:socket#addrinfo|addrinfo()} and
+ * {@link module:socket.socket#connect|connect()} to simplify connection
+ * establishment with the socket module.
+ *
+ * @function module:socket#connect
+ *
+ * @param {string|number[]|module:socket.socket.SocketAddress} host
+ * The host to connect to, can be an IP address, hostname,
+ * {@link module:socket.socket.SocketAddress|SocketAddress}, or an array value
+ * returned by {@link module:core#iptoarr|iptoarr()}.
+ *
+ * @param {string|number} [service]
+ * The service to connect to, can be a symbolic service name (such as "http") or
+ * a port number. Optional if host is specified as
+ * {@link module:socket.socket.SocketAddress|SocketAddress}.
+ *
+ * @param {Object} [hints]
+ * Optional preferences for the socket. It can contain the following properties:
+ * - `family`: The preferred address family (`AF_INET` or `AF_INET6`).
+ * - `socktype`: The socket type (`SOCK_STREAM`, `SOCK_DGRAM`, etc.).
+ * - `protocol`: The protocol of the created socket.
+ * - `flags`: Bitwise OR-ed `AI_*` flags to control the resolution behavior.
+ *
+ * If no hints are not provided, the default socket type preference is set to
+ * `SOCK_STREAM`.
+ *
+ * @param {number} [timeout=-1]
+ * The timeout in milliseconds for socket connect operations. If set to a
+ * negative value, no specifc time limit is imposed and the function will
+ * block until either a connection was successfull or the underlying operating
+ * system timeout is reached.
+ *
+ * @returns {module:socket.socket}
+ *
+ * @example
+ * // Resolve host, try to connect to both resulting IPv4 and IPv6 addresses
+ * let conn = socket.connect("example.org", 80);
+ *
+ * // Enforce usage of IPv6
+ * let conn = socket.connect("example.com", 80, { family: socket.AF_INET6 });
+ *
+ * // Connect a UDP socket
+ * let conn = socket.connect("192.168.1.1", 53, { socktype: socket.SOCK_DGRAM });
+ *
+ * // Bypass name resolution by specifying a SocketAddress structure
+ * let conn = socket.connect({ address: "127.0.0.1", port: 9000 });
+ *
+ * // Use SocketAddress structure to connect a UNIX domain socket
+ * let conn = socket.connect({ path: "/var/run/daemon.sock" });
+ */
+static uc_value_t *
+uc_socket_connect(uc_vm_t *vm, size_t nargs)
+{
+ struct address {
+ struct sockaddr_storage ss;
+ struct addrinfo ai;
+ int flags;
+ int fd;
+ } *ap;
+
+ struct { struct address *entries; size_t count; } addresses = { 0 };
+ struct { struct pollfd *entries; size_t count; } pollfds = { 0 };
+ struct addrinfo *ai_results, *ai_hints, *ai;
+ uc_value_t *host, *serv, *hints, *timeout;
+ const char *errmsg = NULL;
+ struct pollfd *pp = NULL;
+ size_t slot, connected;
+ int ret, err;
+
+ args_get(vm, nargs, NULL,
+ "host", UC_NULL, false, &host,
+ "service", UC_NULL, true, &serv,
+ "hints", UC_OBJECT, true, &hints,
+ "timeout", UC_INTEGER, true, &timeout);
+
+ ai_hints = hints
+ ? (struct addrinfo *)uv_to_struct(hints, &st_addrinfo) : NULL;
+
+ if (ucv_type(host) == UC_STRING) {
+ char *servstr = (ucv_type(serv) != UC_STRING)
+ ? ucv_to_string(vm, serv) : NULL;
+
+ ret = getaddrinfo(ucv_string_get(host),
+ servstr ? servstr : ucv_string_get(serv),
+ ai_hints ? ai_hints : &(struct addrinfo){
+ .ai_socktype = SOCK_STREAM
+ }, &ai_results);
+
+ if (ret != 0) {
+ free(servstr);
+ free(ai_hints);
+ err_return((ret == EAI_SYSTEM) ? errno : ret,
+ "getaddrinfo() failed");
+ }
+
+ for (ai = ai_results; ai != NULL; ai = ai->ai_next) {
+ if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
+ continue;
+
+ uc_vector_grow(&addresses);
+ ap = &addresses.entries[addresses.count++];
+ memcpy(&ap->ss, ai->ai_addr, ai->ai_addrlen);
+ memcpy(&ap->ai, ai, sizeof(*ai));
+ ap->ai.ai_addr = (struct sockaddr *)&ap->ss;
+ }
+
+ freeaddrinfo(ai_results);
+ free(servstr);
+ }
+ else {
+ uc_vector_grow(&addresses);
+ ap = &addresses.entries[addresses.count++];
+
+ if (!uv_to_sockaddr(host, &ap->ss, &ap->ai.ai_addrlen))
+ return NULL;
+
+ if (serv) {
+ uint64_t port = ucv_to_unsigned(serv);
+
+ if (port > 65535)
+ errno = ERANGE;
+
+ if (errno != 0) {
+ free(ai_hints);
+ uc_vector_clear(&addresses);
+ err_return(errno, "Invalid port number");
+ }
+
+ ((struct sockaddr_in *)&ap->ss)->sin_port = htons(port);
+ }
+
+ ap->ai.ai_addr = (struct sockaddr *)&ap->ss;
+ ap->ai.ai_family = ap->ss.ss_family;
+ ap->ai.ai_socktype = ai_hints ? ai_hints->ai_socktype : SOCK_STREAM;
+ ap->ai.ai_protocol = ai_hints ? ai_hints->ai_protocol : 0;
+ }
+
+ free(ai_hints);
+
+ for (connected = 0, slot = 0, ap = &addresses.entries[slot];
+ slot < addresses.count;
+ slot++, ap = &addresses.entries[slot])
+ {
+ uc_vector_grow(&pollfds);
+ pp = &pollfds.entries[pollfds.count++];
+ pp->events = POLLIN | POLLOUT | POLLHUP | POLLERR;
+ pp->fd = socket(ap->ai.ai_family, ap->ai.ai_socktype, ap->ai.ai_protocol);
+
+ if (pp->fd == -1)
+ continue;
+
+ if ((ap->flags = fcntl(pp->fd, F_GETFL, 0)) == -1) {
+ xclose(&pp->fd);
+ continue;
+ }
+
+ if (fcntl(pp->fd, F_SETFL, ap->flags | O_NONBLOCK) == -1) {
+ xclose(&pp->fd);
+ continue;
+ }
+
+ ret = connect(pp->fd, ap->ai.ai_addr, ap->ai.ai_addrlen);
+
+ if (ret == -1 && errno != EINPROGRESS) {
+ xclose(&pp->fd);
+ continue;
+ }
+
+ connected++;
+ }
+
+ if (connected == 0) {
+ err = EAI_NONAME;
+ errmsg = "Could not connect to any host address";
+ goto out;
+ }
+
+ ret = poll(pollfds.entries, pollfds.count,
+ timeout ? ucv_int64_get(timeout) : -1);
+
+ if (ret == -1) {
+ err = errno;
+ errmsg = "poll() failed";
+ goto out;
+ }
+
+ for (slot = 0, ap = NULL, pp = NULL; slot < pollfds.count; slot++) {
+ if (pollfds.entries[slot].revents & (POLLIN|POLLOUT)) {
+ ap = &addresses.entries[slot];
+ pp = &pollfds.entries[slot];
+ break;
+ }
+ }
+
+ if (!ap) {
+ err = ETIMEDOUT;
+ errmsg = "Connection timed out";
+ goto out;
+ }
+
+ if (fcntl(pp->fd, F_SETFL, ap->flags) == -1) {
+ err = errno;
+ errmsg = "fcntl(F_SETFL) failed";
+ goto out;
+ }
+
+out:
+ for (slot = 0, ret = -1; slot < pollfds.count; slot++) {
+ if (pp == &pollfds.entries[slot])
+ ret = pollfds.entries[slot].fd;
+ else
+ xclose(&pollfds.entries[slot].fd);
+ }
+
+ uc_vector_clear(&addresses);
+ uc_vector_clear(&pollfds);
+
+ if (errmsg)
+ err_return(err, "%s", errmsg);
+
+ ok_return(ucv_socket_new(vm, ret));
+}
+
+/**
+ * Binds a listening network socket to the specified host and service.
+ *
+ * This high-level function combines the functionality of
+ * {@link module:socket#create|create()},
+ * {@link module:socket#addrinfo|addrinfo()},
+ * {@link module:socket.socket#bind|bind()}, and
+ * {@link module:socket.socket#listen|listen()} to simplify setting up a
+ * listening socket with the socket module.
+ *
+ * @function module:socket#listen
+ *
+ * @param {string|number[]|module:socket.socket.SocketAddress} host
+ * The host to bind to, can be an IP address, hostname,
+ * {@link module:socket.socket.SocketAddress|SocketAddress}, or an array value
+ * returned by {@link module:core#iptoarr|iptoarr()}.
+ *
+ * @param {string|number} [service]
+ * The service to listen on, can be a symbolic service name (such as "http") or
+ * a port number. Optional if host is specified as
+ * {@link module:socket.socket.SocketAddress|SocketAddress}.
+ *
+ * @param {Object} [hints]
+ * Optional preferences for the socket. It can contain the following properties:
+ * - `family`: The preferred address family (`AF_INET` or `AF_INET6`).
+ * - `socktype`: The socket type (`SOCK_STREAM`, `SOCK_DGRAM`, etc.).
+ * - `protocol`: The protocol of the created socket.
+ * - `flags`: Bitwise OR-ed `AI_*` flags to control the resolution behavior.
+ *
+ * If no hints are provided, the default socket type preference is set to
+ * `SOCK_STREAM`.
+ *
+ * @param {number} [backlog=128]
+ * The maximum length of the queue of pending connections.
+ *
+ * @returns {module:socket.socket}
+ *
+ * @example
+ * // Listen for incoming TCP connections on port 80
+ * let server = socket.listen("localhost", 80);
+ *
+ * // Listen on IPv6 address only
+ * let server = socket.listen("machine.local", 8080, { family: socket.AF_INET6 });
+ *
+ * // Listen on a UNIX domain socket
+ * let server = socket.listen({ path: "/var/run/server.sock" });
+ */
+static uc_value_t *
+uc_socket_listen(uc_vm_t *vm, size_t nargs)
+{
+ int ret, fd, curr_weight, prev_weight, socktype = 0, protocol = 0;
+ struct addrinfo *ai_results, *ai_hints, *ai;
+ uc_value_t *host, *serv, *hints, *backlog;
+ struct sockaddr_storage ss = { 0 };
+ bool v6, lo, ll;
+ socklen_t slen;
+
+ args_get(vm, nargs, NULL,
+ "host", UC_NULL, true, &host,
+ "service", UC_NULL, true, &serv,
+ "hints", UC_OBJECT, true, &hints,
+ "backlog", UC_INTEGER, true, &backlog);
+
+ ai_hints = hints
+ ? (struct addrinfo *)uv_to_struct(hints, &st_addrinfo) : NULL;
+
+ if (host == NULL || ucv_type(host) == UC_STRING) {
+ char *servstr = (ucv_type(serv) != UC_STRING)
+ ? ucv_to_string(vm, serv) : NULL;
+
+ ret = getaddrinfo(ucv_string_get(host),
+ servstr ? servstr : ucv_string_get(serv),
+ ai_hints ? ai_hints : &(struct addrinfo){
+ .ai_flags = AI_PASSIVE | AI_ADDRCONFIG,
+ .ai_socktype = SOCK_STREAM
+ }, &ai_results);
+
+ free(servstr);
+
+ if (ret != 0) {
+ free(ai_hints);
+ err_return((ret == EAI_SYSTEM) ? errno : ret,
+ "getaddrinfo() failed");
+ }
+
+ for (ai = ai_results, prev_weight = -1; ai != NULL; ai = ai->ai_next) {
+ struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)ai->ai_addr;
+ struct sockaddr_in *s4 = (struct sockaddr_in *)ai->ai_addr;
+
+ v6 = (s6->sin6_family == AF_INET6);
+ ll = v6
+ ? IN6_IS_ADDR_LINKLOCAL(&s6->sin6_addr)
+ : ((ntohl(s4->sin_addr.s_addr) & 0xffff0000) == 0xa9fe0000);
+ lo = v6
+ ? IN6_IS_ADDR_LOOPBACK(&s6->sin6_addr)
+ : ((ntohl(s4->sin_addr.s_addr) & 0xff000000) == 0x7f000000);
+
+ curr_weight = (!lo << 2) | (v6 << 1) | (!ll << 0);
+
+ if (curr_weight > prev_weight) {
+ prev_weight = curr_weight;
+ socktype = ai->ai_socktype;
+ protocol = ai->ai_protocol;
+ slen = ai->ai_addrlen;
+ memcpy(&ss, ai->ai_addr, slen);
+ }
+ }
+
+ freeaddrinfo(ai_results);
+ }
+ else {
+ if (!uv_to_sockaddr(host, &ss, &slen)) {
+ free(ai_hints);
+ return NULL;
+ }
+
+ if (serv) {
+ uint64_t port = ucv_to_unsigned(serv);
+
+ if (port > 65535)
+ errno = ERANGE;
+
+ if (errno != 0) {
+ free(ai_hints);
+ err_return(errno, "Invalid port number");
+ }
+
+ ((struct sockaddr_in *)&ss)->sin_port = htons(port);
+ }
+
+ socktype = ai_hints ? ai_hints->ai_socktype : SOCK_STREAM;
+ protocol = ai_hints ? ai_hints->ai_protocol : 0;
+ }
+
+ free(ai_hints);
+
+ if (ss.ss_family == AF_UNSPEC)
+ err_return(EAI_NONAME, "Could not resolve host address");
+
+ fd = socket(ss.ss_family, socktype, protocol);
+
+ if (fd == -1)
+ err_return(errno, "socket() failed");
+
+ ret = bind(fd, (struct sockaddr *)&ss, slen);
+
+ if (ret == -1) {
+ close(fd);
+ err_return(errno, "bind() failed");
+ }
+
+ ret = listen(fd, backlog ? ucv_to_unsigned(backlog) : 128);
+
+ if (ret == -1) {
+ close(fd);
+ err_return(errno, "listen() failed");
+ }
+
+ ok_return(ucv_socket_new(vm, fd));
+}
+
+/**
+ * Represents a socket handle.
+ *
+ * @class module:socket.socket
+ * @hideconstructor
+ *
+ * @borrows module:socket#error as module:socket.socket#error
+ *
+ * @see {@link module:socket#create|create()}
+ *
+ * @example
+ *
+ * const sock = create(…);
+ *
+ * sock.getopt(…);
+ * sock.setopt(…);
+ *
+ * sock.connect(…);
+ * sock.listen(…);
+ * sock.accept(…);
+ * sock.bind(…);
+ *
+ * sock.send(…);
+ * sock.recv(…);
+ *
+ * sock.shutdown(…);
+ *
+ * sock.fileno();
+ * sock.peername();
+ * sock.sockname();
+ *
+ * sock.close();
+ *
+ * sock.error();
+ */
+
+/**
+ * Creates a socket and returns its hand
+ *
+ * The handle will be connected to the process stdin or stdout, depending on the
+ * value of the mode argument.
+ *
+ * The mode argument may be either "r" to open the process for reading (connect
+ * to its stdin) or "w" to open the process for writing (connect to its stdout).
+ *
+ * The mode character "r" or "w" may be optionally followed by "e" to apply the
+ * FD_CLOEXEC flag onto the open descriptor.
+ *
+ * Returns a process handle referring to the executed process.
+ *
+ * Returns `null` if an error occurred.
+ *
+ * @function module:fs#popen
+ *
+ * @param {string} command
+ * The command to be executed.
+ *
+ * @param {string} [mode="r"]
+ * The open mode of the process handle.
+ *
+ * @returns {?module:fs.proc}
+ *
+ * @example
+ * // Open a process
+ * const process = popen('command', 'r');
+ */
+
+/**
+ * Creates a network socket instance.
+ *
+ * This function creates a new network socket with the specified domain and
+ * type, determined by one of the modules `AF_*` and `SOCK_*` constants
+ * respectively, and returns the resulting socket instance for use in subsequent
+ * socket operations.
+ *
+ * The domain argument specifies the protocol family, such as AF_INET or
+ * AF_INET6, and defaults to AF_INET if not provided.
+ *
+ * The type argument specifies the socket type, such as SOCK_STREAM or
+ * SOCK_DGRAM, and defaults to SOCK_STREAM if not provided. It may also
+ * be bitwise OR-ed with SOCK_NONBLOCK to enable non-blocking mode or
+ * SOCK_CLOEXEC to enable close-on-exec semantics.
+ *
+ * The protocol argument may be used to indicate a particular protocol
+ * to be used with the socket, and it defaults to 0 (automatically
+ * determined protocol) if not provided.
+ *
+ * Returns a socket descriptor representing the newly created socket.
+ *
+ * Returns `null` if an error occurred during socket creation.
+ *
+ * @function module:socket#create
+ *
+ * @param {number} [domain=AF_INET]
+ * The communication domain for the socket, e.g., AF_INET or AF_INET6.
+ *
+ * @param {number} [type=SOCK_STREAM]
+ * The socket type, e.g., SOCK_STREAM or SOCK_DGRAM. It may also be
+ * bitwise OR-ed with SOCK_NONBLOCK or SOCK_CLOEXEC.
+ *
+ * @param {number} [protocol=0]
+ * The protocol to be used with the socket.
+ *
+ * @returns {?module:socket.socket}
+ * A socket instance representing the newly created socket.
+ *
+ * @example
+ * // Create a TCP socket
+ * const tcp_socket = create(AF_INET, SOCK_STREAM);
+ *
+ * // Create a nonblocking IPv6 UDP socket
+ * const udp_socket = create(AF_INET6, SOCK_DGRAM | SOCK_NONBLOCK);
+ */
+static uc_value_t *
+uc_socket_create(uc_vm_t *vm, size_t nargs)
+{
+ uc_value_t *domain, *type, *protocol;
+ int sockfd, socktype;
+
+ args_get(vm, nargs, NULL,
+ "domain", UC_INTEGER, true, &domain,
+ "type", UC_INTEGER, true, &type,
+ "protocol", UC_INTEGER, true, &protocol);
+
+ socktype = type ? (int)ucv_int64_get(type) : SOCK_STREAM;
+
+ sockfd = socket(
+ domain ? (int)ucv_int64_get(domain) : AF_INET,
+#if defined(__APPLE__)
+ socktype & ~(SOCK_NONBLOCK|SOCK_CLOEXEC),
+#else
+ socktype,
+#endif
+ protocol ? (int)ucv_int64_get(protocol) : 0);
+
+ if (sockfd == -1)
+ err_return(errno, "socket() failed");
+
+#if defined(__APPLE__)
+ if (socktype & SOCK_NONBLOCK) {
+ int flags = fcntl(sockfd, F_GETFL);
+
+ if (flags == -1) {
+ close(sockfd);
+ err_return(errno, "fcntl(F_GETFL) failed");
+ }
+
+ if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1) {
+ close(sockfd);
+ err_return(errno, "fcntl(F_SETFL) failed");
+ }
+ }
+
+ if (socktype & SOCK_CLOEXEC) {
+ if (fcntl(sockfd, F_SETFD, FD_CLOEXEC) == -1) {
+ close(sockfd);
+ err_return(errno, "fcntl(F_SETFD) failed");
+ }
+ }
+#endif
+
+ ok_return(ucv_socket_new(vm, sockfd));
+}
+
+/**
+ * Connects the socket to a remote address.
+ *
+ * Attempts to establish a connection to the specified remote address.
+ *
+ * Returns `true` if the connection is successfully established.
+ * Returns `null` if an error occurred during the connection attempt.
+ *
+ * @function module:socket.socket#connect
+ *
+ * @param {string|module:socket.socket.SocketAddress} address
+ * The address of the remote endpoint to connect to.
+ *
+ * @param {number} port
+ * The port number of the remote endpoint to connect to.
+ *
+ * @returns {?boolean}
+ */
+static uc_value_t *
+uc_socket_inst_connect(uc_vm_t *vm, size_t nargs)
+{
+ struct sockaddr_storage ss;
+ uc_value_t *addr;
+ int ret, sockfd;
+ socklen_t slen;
+
+ args_get(vm, nargs, &sockfd,
+ "address", UC_NULL, false, &addr);
+
+ if (!uv_to_sockaddr(addr, &ss, &slen))
+ return NULL;
+
+ ret = connect(sockfd, (struct sockaddr *)&ss, slen);
+
+ if (ret == -1)
+ err_return(errno, "connect() failed");
+
+ ok_return(ucv_boolean_new(true));
+}
+
+/**
+ * Sends data through the socket.
+ *
+ * Sends the provided data through the socket handle to the specified remote
+ * address, if provided.
+ *
+ * Returns the number of bytes sent.
+ * Returns `null` if an error occurred during the send operation.
+ *
+ * @function module:socket.socket#send
+ *
+ * @param {string} data
+ * The data to be sent through the socket.
+ *
+ * @param {number} [flags]
+ * Optional flags that modify the behavior of the send operation.
+ *
+ * @param {module:socket.socket.SocketAddress|number[]|string} [address]
+ * The address of the remote endpoint to send the data to. It can be either an
+ * IP address string, an array returned by {@link module:core#iptoarr|iptoarr()},
+ * or an object representing a network address. If not provided, the data is
+ * sent to the remote endpoint the socket is connected to.
+ *
+ * @returns {?number}
+ *
+ * @see {@link module:socket#sockaddr|sockaddr()}
+ *
+ * @example
+ * // Send to connected socket
+ * let tcp_sock = socket.create(socket.AF_INET, socket.SOCK_STREAM);
+ * tcp_sock.connect("192.168.1.1", 80);
+ * tcp_sock.send("GET / HTTP/1.0\r\n\r\n");
+ *
+ * // Send a datagram on unconnected socket
+ * let udp_sock = socket.create(socket.AF_INET, socket.SOCK_DGRAM);
+ * udp_sock.send("Hello there!", 0, "255.255.255.255:9000");
+ * udp_sock.send("Hello there!", 0, {
+ * family: socket.AF_INET, // optional
+ * address: "255.255.255.255",
+ * port: 9000
+ * });
+ */
+static uc_value_t *
+uc_socket_inst_send(uc_vm_t *vm, size_t nargs)
+{
+ uc_value_t *data, *flags, *addr;
+ struct sockaddr_storage ss = { 0 };
+ struct sockaddr *sa = NULL;
+ socklen_t salen = 0;
+ ssize_t ret;
+ int sockfd;
+
+ args_get(vm, nargs, &sockfd,
+ "data", UC_STRING, false, &data,
+ "flags", UC_INTEGER, true, &flags,
+ "address", UC_NULL, true, &addr);
+
+ if (addr) {
+ if (!uv_to_sockaddr(addr, &ss, &salen))
+ return NULL;
+
+ sa = (struct sockaddr *)&ss;
+ }
+
+ ret = sendto(sockfd,
+ ucv_string_get(data), ucv_string_length(data),
+ (flags ? ucv_int64_get(flags) : 0) | MSG_NOSIGNAL, sa, salen);
+
+ if (ret == -1)
+ err_return(errno, "send() failed");
+
+ ok_return(ucv_int64_new(ret));
+}
+
+/**
+ * Receives data from the socket.
+ *
+ * Receives data from the socket handle, optionally specifying the maximum
+ * length of data to receive, flags to modify the receive behavior, and an
+ * optional address dictionary where the function will place the address from
+ * which the data was received (for unconnected sockets).
+ *
+ * Returns a string containing the received data.
+ * Returns an empty string if the remote side closed the socket.
+ * Returns `null` if an error occurred during the receive operation.
+ *
+ * @function module:socket.socket#recv
+ *
+ * @param {number} [length=4096]
+ * The maximum number of bytes to receive.
+ *
+ * @param {number} [flags]
+ * Optional flags that modify the behavior of the receive operation.
+ *
+ * @param {Object} [address]
+ * An object where the function will store the address from which the data was
+ * received. If provided, it will be filled with the details obtained from the
+ * sockaddr argument of the underlying `recvfrom()` syscall. See the type
+ * definition of {@link module:socket.socket.SocketAddress|SocketAddress} for
+ * details on the format.
+ *
+ * @returns {?string}
+ */
+static uc_value_t *
+uc_socket_inst_recv(uc_vm_t *vm, size_t nargs)
+{
+ uc_value_t *length, *flags, *addrobj;
+ struct sockaddr_storage ss = { 0 };
+ uc_stringbuf_t *buf;
+ ssize_t len, ret;
+ socklen_t sslen;
+ int sockfd;
+
+ args_get(vm, nargs, &sockfd,
+ "length", UC_INTEGER, true, &length,
+ "flags", UC_INTEGER, true, &flags,
+ "address", UC_OBJECT, true, &addrobj);
+
+ len = length ? ucv_to_integer(length) : 4096;
+
+ if (errno || len <= 0)
+ err_return(errno, "Invalid length argument");
+
+ buf = strbuf_alloc(len);
+
+ if (!buf)
+ return NULL;
+
+ do {
+ sslen = sizeof(ss);
+ ret = recvfrom(sockfd, strbuf_data(buf), len,
+ flags ? ucv_int64_get(flags) : 0, (struct sockaddr *)&ss, &sslen);
+ } while (ret == -1 && errno == EINTR);
+
+ if (ret == -1) {
+ strbuf_free(buf);
+ err_return(errno, "recv() failed");
+ }
+
+ if (addrobj)
+ sockaddr_to_uv(&ss, addrobj);
+
+ ok_return(strbuf_finish(&buf, ret));
+}
+
+/**
+ * Binds a socket to a specific address.
+ *
+ * This function binds the socket to the specified address.
+ *
+ * Returns `true` if the socket is successfully bound.
+ *
+ * Returns `null` on error, e.g. when the address is in use.
+ *
+ * @function module:socket.socket#bind
+ *
+ * @param {string|module:socket.socket.SocketAddress} address
+ * The IP address to bind the socket to.
+ *
+ * @returns {?boolean}
+ *
+ * @example
+ * const sock = socket.create(…);
+ * const success = sock.bind("192.168.0.1:80");
+ *
+ * if (success)
+ * print(`Socket bound successfully!\n`);
+ * else
+ * print(`Failed to bind socket: ${sock.error()}.\n`);
+ */
+static uc_value_t *
+uc_socket_inst_bind(uc_vm_t *vm, size_t nargs)
+{
+ struct sockaddr_storage ss = { 0 };
+ uc_value_t *addr;
+ socklen_t slen;
+ int sockfd;
+
+ args_get(vm, nargs, &sockfd,
+ "address", UC_NULL, true, &addr);
+
+ if (addr) {
+ if (!uv_to_sockaddr(addr, &ss, &slen))
+ return NULL;
+
+ if (bind(sockfd, (struct sockaddr *)&ss, slen) == -1)
+ err_return(errno, "bind() failed");
+ }
+ else {
+#if defined(__linux__)
+ int sval = 0;
+ slen = sizeof(sval);
+
+ if (getsockopt(sockfd, SOL_SOCKET, SO_DOMAIN, &sval, &slen) == -1)
+ err_return(errno, "getsockopt() failed");
+
+ switch (sval) {
+ case AF_INET6:
+ ss.ss_family = AF_INET6;
+ slen = sizeof(struct sockaddr_in6);
+ break;
+
+ case AF_INET:
+ ss.ss_family = AF_INET;
+ slen = sizeof(struct sockaddr_in);
+ break;
+
+ default:
+ err_return(EAFNOSUPPORT, "Unsupported socket address family");
+ }
+
+ if (bind(sockfd, (struct sockaddr *)&ss, slen) == -1)
+ err_return(errno, "bind() failed");
+#else
+ ss.ss_family = AF_INET6;
+ slen = sizeof(struct sockaddr_in6);
+
+ if (bind(sockfd, (struct sockaddr *)&ss, slen) == -1) {
+ if (errno != EAFNOSUPPORT)
+ err_return(errno, "bind() failed");
+
+ ss.ss_family = AF_INET;
+ slen = sizeof(struct sockaddr_in);
+
+ if (bind(sockfd, (struct sockaddr *)&ss, slen) == -1)
+ err_return(errno, "bind() failed");
+ }
+#endif
+ }
+
+ ok_return(ucv_boolean_new(true));
+}
+
+/**
+ * Listen for connections on a socket.
+ *
+ * This function marks the socket as a passive socket, that is, as a socket that
+ * will be used to accept incoming connection requests using `accept()`.
+ *
+ * The `backlog` parameter specifies the maximum length to which the queue of
+ * pending connections may grow. If a connection request arrives when the queue
+ * is full, the client connection might get refused.
+ *
+ * If `backlog` is not provided, it defaults to 128.
+ *
+ * Returns `true` if the socket is successfully marked as passive.
+ * Returns `null` if an error occurred, e.g. when the requested port is in use.
+ *
+ * @function module:socket.socket#listen
+ *
+ * @param {number} [backlog=128]
+ * The maximum length of the queue of pending connections.
+ *
+ * @returns {?boolean}
+ *
+ * @see {@link module:socket.socket#accept|accept()}
+ *
+ * @example
+ * const sock = socket.create(…);
+ * sock.bind(…);
+ *
+ * const success = sock.listen(10);
+ * if (success)
+ * print(`Socket is listening for incoming connections!\n`);
+ * else
+ * print(`Failed to listen on socket: ${sock.error()}\n`);
+ */
+static uc_value_t *
+uc_socket_inst_listen(uc_vm_t *vm, size_t nargs)
+{
+ uc_value_t *backlog;
+ int ret, sockfd;
+
+ args_get(vm, nargs, &sockfd,
+ "backlog", UC_INTEGER, true, &backlog);
+
+ ret = listen(sockfd, backlog ? ucv_to_unsigned(backlog) : 128);
+
+ if (ret == -1)
+ err_return(errno, "listen() failed");
+
+ ok_return(ucv_boolean_new(true));
+}
+
+/**
+ * Accept a connection on a socket.
+ *
+ * This function accepts a connection on the socket. It extracts the first
+ * connection request on the queue of pending connections, creates a new
+ * connected socket, and returns a new socket handle referring to that socket.
+ * The newly created socket is not in listening state and has no backlog.
+ *
+ * When a optional `address` dictionary is provided, it is populated with the
+ * remote address details of the peer socket.
+ *
+ * The optional `flags` parameter is a bitwise-or-ed number of flags to modify
+ * the behavior of accepted peer socket. Possible values are:
+ * - `SOCK_CLOEXEC`: Enable close-on-exec semantics for the new socket.
+ * - `SOCK_NONBLOCK`: Enable nonblocking mode for the new socket.
+ *
+ * Returns a socket handle representing the newly created peer socket of the
+ * accepted connection.
+ *
+ * Returns `null` if an error occurred.
+ *
+ * @function module:socket.socket#accept
+ *
+ * @param {object} [address]
+ * An optional dictionary to receive the address details of the peer socket.
+ * See {@link module:socket.socket.SocketAddress|SocketAddress} for details.
+ *
+ * @param {number} [flags]
+ * Optional flags to modify the behavior of the peer socket.
+ *
+ * @returns {?module:socket.socket}
+ *
+ * @example
+ * const sock = socket.create(…);
+ * sock.bind(…);
+ * sock.listen();
+ *
+ * const peerAddress = {};
+ * const newSocket = sock.accept(peerAddress, socket.SOCK_CLOEXEC);
+ * if (newSocket)
+ * print(`Accepted connection from: ${peerAddress}\n`);
+ * else
+ * print(`Failed to accept connection: ${sock.error()}\n`);
+ */
+static uc_value_t *
+uc_socket_inst_accept(uc_vm_t *vm, size_t nargs)
+{
+ struct sockaddr_storage ss = { 0 };
+ int peerfd, sockfd, sockflags;
+ uc_value_t *addrobj, *flags;
+ socklen_t slen;
+
+ args_get(vm, nargs, &sockfd,
+ "address", UC_OBJECT, true, &addrobj,
+ "flags", UC_INTEGER, true, &flags);
+
+ slen = sizeof(ss);
+ sockflags = flags ? ucv_to_integer(flags) : 0;
+
+#ifdef __APPLE__
+ peerfd = accept(sockfd, (struct sockaddr *)&ss, &slen);
+
+ if (peerfd == -1)
+ err_return(errno, "accept() failed");
+
+ if (sockflags & SOCK_CLOEXEC) {
+ if (fcntl(peerfd, F_SETFD, FD_CLOEXEC) == -1) {
+ close(peerfd);
+ err_return(errno, "fcntl(F_SETFD) failed");
+ }
+ }
+
+ if (sockflags & SOCK_NONBLOCK) {
+ sockflags = fcntl(peerfd, F_GETFL);
+
+ if (sockflags == -1) {
+ close(peerfd);
+ err_return(errno, "fcntl(F_GETFL) failed");
+ }
+
+ if (fcntl(peerfd, F_SETFL, sockflags | O_NONBLOCK) == -1) {
+ close(peerfd);
+ err_return(errno, "fcntl(F_SETFL) failed");
+ }
+ }
+#else
+ peerfd = accept4(sockfd, (struct sockaddr *)&ss, &slen, sockflags);
+
+ if (peerfd == -1)
+ err_return(errno, "accept4() failed");
+#endif
+
+ if (addrobj)
+ sockaddr_to_uv(&ss, addrobj);
+
+ ok_return(ucv_socket_new(vm, peerfd));
+}
+
+/**
+ * Shutdown part of a full-duplex connection.
+ *
+ * This function shuts down part of the full-duplex connection associated with
+ * the socket handle. The `how` parameter specifies which half of the connection
+ * to shut down. It can take one of the following constant values:
+ *
+ * - `SHUT_RD`: Disables further receive operations.
+ * - `SHUT_WR`: Disables further send operations.
+ * - `SHUT_RDWR`: Disables further send and receive operations.
+ *
+ * Returns `true` if the shutdown operation is successful.
+ * Returns `null` if an error occurred.
+ *
+ * @function module:socket.socket#shutdown
+ *
+ * @param {number} how
+ * Specifies which half of the connection to shut down.
+ * It can be one of the following constant values: `SHUT_RD`, `SHUT_WR`,
+ * or `SHUT_RDWR`.
+ *
+ * @returns {?boolean}
+ *
+ * @example
+ * const sock = socket.create(…);
+ * sock.connect(…);
+ * // Perform data exchange…
+ *
+ * const success = sock.shutdown(socket.SHUT_WR);
+ * if (success)
+ * print(`Send operations on socket shut down successfully.\n`);
+ * else
+ * print(`Failed to shut down send operations: ${sock.error()}\n`);
+ */
+static uc_value_t *
+uc_socket_inst_shutdown(uc_vm_t *vm, size_t nargs)
+{
+ uc_value_t *how;
+ int sockfd, ret;
+
+ args_get(vm, nargs, &sockfd,
+ "how", UC_INTEGER, true, &how);
+
+ ret = shutdown(sockfd, ucv_int64_get(how));
+
+ if (ret == -1)
+ err_return(errno, "shutdown() failed");
+
+ ok_return(ucv_boolean_new(true));
+}
+
+/**
+ * Represents a credentials information object returned by
+ * {@link module:socket.socket#peercred|`peercred()`}.
+ *
+ * @typedef {Object} module:socket.socket.PeerCredentials
+ * @property {number} uid
+ * The effective user ID the remote socket endpoint.
+ *
+ * @property {number} gid
+ * The effective group ID the remote socket endpoint.
+ *
+ * @property {number} pid
+ * The ID of the process the remote socket endpoint belongs to.
+ */
+
+/**
+ * Retrieves the peer credentials.
+ *
+ * This function retrieves the remote uid, gid and pid of a connected UNIX
+ * domain socket.
+ *
+ * Returns the remote credentials if the operation is successful.
+ * Returns `null` on error.
+ *
+ * @function module:socket.socket#peercred
+ *
+ * @returns {?module:socket.socket.PeerCredentials}
+ *
+ * @example
+ * const sock = socket.create(socket.AF_UNIX, …);
+ * sock.connect(…);
+ *
+ * const peerCredentials = sock.peercred();
+ * if (peerCredentials)
+ * print(`Peer credentials: ${peerCredentials}\n`);
+ * else
+ * print(`Failed to retrieve peer credentials: ${sock.error()}\n`);
+ */
+static uc_value_t *
+uc_socket_inst_peercred(uc_vm_t *vm, size_t nargs)
+{
+ uc_value_t *rv = NULL;
+ socklen_t optlen;
+ int ret, sockfd;
+
+ args_get(vm, nargs, &sockfd);
+
+#if defined(__linux__)
+ struct ucred cred;
+
+ optlen = sizeof(cred);
+ ret = getsockopt(sockfd, SOL_SOCKET, SO_PEERCRED, &cred, &optlen);
+
+ if (ret == -1)
+ err_return(errno, "getsockopt() failed");
+
+ if (optlen != sizeof(cred))
+ err_return(EINVAL, "Invalid credentials received");
+
+ rv = ucv_object_new(vm);
+
+ ucv_object_add(rv, "uid", ucv_uint64_new(cred.uid));
+ ucv_object_add(rv, "gid", ucv_uint64_new(cred.gid));
+ ucv_object_add(rv, "pid", ucv_int64_new(cred.pid));
+#elif defined(__APPLE__)
+ struct xucred cred;
+ pid_t pid;
+
+ optlen = sizeof(cred);
+ ret = getsockopt(sockfd, SOL_LOCAL, LOCAL_PEERCRED, &cred, &optlen);
+
+ if (ret == -1)
+ err_return(errno, "getsockopt(LOCAL_PEERCRED) failed");
+
+ if (optlen != sizeof(cred) || cred.cr_version != XUCRED_VERSION)
+ err_return(EINVAL, "Invalid credentials received");
+
+ rv = ucv_object_new(vm);
+
+ ucv_object_add(rv, "uid", ucv_uint64_new(cred.cr_uid));
+ ucv_object_add(rv, "gid", ucv_uint64_new(cred.cr_gid));
+
+ optlen = sizeof(pid);
+ ret = getsockopt(sockfd, SOL_LOCAL, LOCAL_PEERPID, &pid, &optlen);
+
+ if (ret == -1) {
+ ucv_put(rv);
+ err_return(errno, "getsockopt(LOCAL_PEERPID) failed");
+ }
+
+ ucv_object_add(rv, "pid", ucv_int64_new(pid));
+#else
+ err_return(ENOSYS, "Operation not supported on this system");
+#endif
+
+ return rv;
+}
+
+/**
+ * Retrieves the remote address.
+ *
+ * This function retrieves the remote address of a connected socket.
+ *
+ * Returns the remote address if the operation is successful.
+ * Returns `null` on error.
+ *
+ * @function module:socket.socket#peername
+ *
+ * @returns {?module:socket.socket.SocketAddress}
+ *
+ * @see {@link module:socket.socket#sockname|sockname()}
+ *
+ * @example
+ * const sock = socket.create(…);
+ * sock.connect(…);
+ *
+ * const peerAddress = sock.peername();
+ * if (peerAddress)
+ * print(`Connected to ${peerAddress}\n`);
+ * else
+ * print(`Failed to retrieve peer address: ${sock.error()}\n`);
+ */
+static uc_value_t *
+uc_socket_inst_peername(uc_vm_t *vm, size_t nargs)
+{
+ struct sockaddr_storage ss = { 0 };
+ uc_value_t *addr;
+ socklen_t sslen;
+ int sockfd, ret;
+
+ args_get(vm, nargs, &sockfd);
+
+ sslen = sizeof(ss);
+ ret = getpeername(sockfd, (struct sockaddr *)&ss, &sslen);
+
+ if (ret == -1)
+ err_return(errno, "getpeername() failed");
+
+ addr = ucv_object_new(vm);
+ sockaddr_to_uv(&ss, addr);
+
+ ok_return(addr);
+}
+
+/**
+ * Retrieves the local address.
+ *
+ * This function retrieves the local address of a bound or connected socket.
+ *
+ * Returns the local address if the operation is successful.
+ * Returns `null` on error.
+ *
+ * @function module:socket.socket#sockname
+ *
+ * @returns {?module:socket.socket.SocketAddress}
+ *
+ * @see {@link module:socket.socket#peername|peername()}
+ *
+ * @example
+ * const sock = socket.create(…);
+ * sock.connect(…);
+ *
+ * const myAddress = sock.sockname();
+ * if (myAddress)
+ * print(`My source IP address is ${myAddress}\n`);
+ * else
+ * print(`Failed to retrieve peer address: ${sock.error()}\n`);
+ */
+static uc_value_t *
+uc_socket_inst_sockname(uc_vm_t *vm, size_t nargs)
+{
+ struct sockaddr_storage ss = { 0 };
+ uc_value_t *addr;
+ socklen_t sslen;
+ int sockfd, ret;
+
+ args_get(vm, nargs, &sockfd);
+
+ sslen = sizeof(ss);
+ ret = getsockname(sockfd, (struct sockaddr *)&ss, &sslen);
+
+ if (ret == -1)
+ err_return(errno, "getsockname() failed");
+
+ addr = ucv_object_new(vm);
+ sockaddr_to_uv(&ss, addr);
+
+ ok_return(addr);
+}
+
+/**
+ * Closes the socket.
+ *
+ * This function closes the socket, releasing its resources and terminating its
+ * associated connections.
+ *
+ * Returns `true` if the socket was successfully closed.
+ * Returns `null` on error.
+ *
+ * @function module:socket.socket#close
+ *
+ * @returns {?boolean}
+ *
+ * @example
+ * const sock = socket.create(…);
+ * sock.connect(…);
+ * // Perform operations with the socket…
+ * sock.close();
+ */
+static uc_value_t *
+uc_socket_inst_close(uc_vm_t *vm, size_t nargs)
+{
+ int *sockfd = uc_fn_this("socket");
+
+ if (!sockfd || *sockfd == -1)
+ err_return(EBADF, "Invalid socket context");
+
+ if (!xclose(sockfd))
+ err_return(errno, "close() failed");
+
+ ok_return(ucv_boolean_new(true));
+}
+
+static void
+close_socket(void *ud)
+{
+ int fd = (intptr_t)ud;
+
+ if (fd != -1)
+ close(fd);
+}
+
+static const uc_function_list_t socket_fns[] = {
+ { "connect", uc_socket_inst_connect },
+ { "bind", uc_socket_inst_bind },
+ { "listen", uc_socket_inst_listen },
+ { "accept", uc_socket_inst_accept },
+ { "send", uc_socket_inst_send },
+ { "recv", uc_socket_inst_recv },
+ { "setopt", uc_socket_inst_setopt },
+ { "getopt", uc_socket_inst_getopt },
+ { "fileno", uc_socket_inst_fileno },
+ { "shutdown", uc_socket_inst_shutdown },
+ { "peercred", uc_socket_inst_peercred },
+ { "peername", uc_socket_inst_peername },
+ { "sockname", uc_socket_inst_sockname },
+ { "close", uc_socket_inst_close },
+ { "error", uc_socket_error },
+};
+
+static const uc_function_list_t global_fns[] = {
+ { "sockaddr", uc_socket_sockaddr },
+ { "create", uc_socket_create },
+ { "nameinfo", uc_socket_nameinfo },
+ { "addrinfo", uc_socket_addrinfo },
+ { "poll", uc_socket_poll },
+ { "connect", uc_socket_connect },
+ { "listen", uc_socket_listen },
+ { "error", uc_socket_error },
+};
+
+void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
+{
+ uc_function_list_register(scope, global_fns);
+
+#define ADD_CONST(x) ucv_object_add(scope, #x, ucv_int64_new(x))
+
+ /**
+ * @typedef
+ * @name Address Families
+ * @description Constants representing address families and socket domains.
+ * @property {number} AF_UNSPEC - Unspecified address family.
+ * @property {number} AF_UNIX - UNIX domain sockets.
+ * @property {number} AF_INET - IPv4 Internet protocols.
+ * @property {number} AF_INET6 - IPv6 Internet protocols.
+ * @property {number} AF_PACKET - Low-level packet interface.
+ */
+ ADD_CONST(AF_UNSPEC);
+ ADD_CONST(AF_UNIX);
+ ADD_CONST(AF_INET);
+ ADD_CONST(AF_INET6);
+#if defined(__linux__)
+ ADD_CONST(AF_PACKET);
+#endif
+
+ /**
+ * @typedef
+ * @name Socket Types
+ * @description
+ * The `SOCK_*` type and flag constants are used by
+ * {@link module:socket#create|create()} to specify the type of socket to
+ * open. The {@link module:socket.socket#accept|accept()} function
+ * recognizes the `SOCK_NONBLOCK` and `SOCK_CLOEXEC` flags and applies them
+ * to accepted peer sockets.
+ * @property {number} SOCK_STREAM - Provides sequenced, reliable, two-way, connection-based byte streams.
+ * @property {number} SOCK_DGRAM - Supports datagrams (connectionless, unreliable messages of a fixed maximum length).
+ * @property {number} SOCK_RAW - Provides raw network protocol access.
+ * @property {number} SOCK_PACKET - Obsolete and should not be used.
+ * @property {number} SOCK_NONBLOCK - Enables non-blocking operation.
+ * @property {number} SOCK_CLOEXEC - Sets the close-on-exec flag on the new file descriptor.
+ */
+ ADD_CONST(SOCK_STREAM);
+ ADD_CONST(SOCK_DGRAM);
+ ADD_CONST(SOCK_RAW);
+ ADD_CONST(SOCK_NONBLOCK);
+ ADD_CONST(SOCK_CLOEXEC);
+#if defined(__linux__)
+ ADD_CONST(SOCK_PACKET);
+#endif
+
+ /**
+ * @typedef
+ * @name Message Flags
+ * @description
+ * The `MSG_*` flag constants are commonly used in conjunction with the
+ * {@link module:socket.socket#send|send()} and
+ * {@link module:socket.socket#recv|recv()} functions.
+ * @property {number} MSG_CONFIRM - Confirm path validity.
+ * @property {number} MSG_DONTROUTE - Send without using routing tables.
+ * @property {number} MSG_DONTWAIT - Enables non-blocking operation.
+ * @property {number} MSG_EOR - End of record.
+ * @property {number} MSG_MORE - Sender will send more.
+ * @property {number} MSG_NOSIGNAL - Do not generate SIGPIPE.
+ * @property {number} MSG_OOB - Process out-of-band data.
+ * @property {number} MSG_FASTOPEN - Send data in TCP SYN.
+ * @property {number} MSG_CMSG_CLOEXEC - Sets the close-on-exec flag on the received file descriptor.
+ * @property {number} MSG_ERRQUEUE - Receive errors from ICMP.
+ * @property {number} MSG_PEEK - Peeks at incoming messages.
+ * @property {number} MSG_TRUNC - Report if datagram truncation occurred.
+ * @property {number} MSG_WAITALL - Wait for full message.
+ */
+ ADD_CONST(MSG_DONTROUTE);
+ ADD_CONST(MSG_DONTWAIT);
+ ADD_CONST(MSG_EOR);
+ ADD_CONST(MSG_NOSIGNAL);
+ ADD_CONST(MSG_OOB);
+ ADD_CONST(MSG_PEEK);
+ ADD_CONST(MSG_TRUNC);
+ ADD_CONST(MSG_WAITALL);
+#if defined(__linux__)
+ ADD_CONST(MSG_CONFIRM);
+ ADD_CONST(MSG_MORE);
+ ADD_CONST(MSG_FASTOPEN);
+ ADD_CONST(MSG_CMSG_CLOEXEC);
+ ADD_CONST(MSG_ERRQUEUE);
+#endif
+
+ /**
+ * @typedef
+ * @name IP Protocol Constants
+ * @description
+ * The `IPPROTO_IP` constant specifies the IP protocol number and may be
+ * passed as third argument to {@link module:socket#create|create()} as well
+ * as *level* argument value to {@link module:socket.socket#getopt|getopt()}
+ * and {@link module:socket.socket#setopt|setopt()}.
+ *
+ * The `IP_*` constants are option names recognized by
+ * {@link module:socket.socket#getopt|getopt()}
+ * and {@link module:socket.socket#setopt|setopt()}, in conjunction with
+ * the `IPPROTO_IP` socket level.
+ * @property {number} IPPROTO_IP - Dummy protocol for IP.
+ * @property {number} IP_ADD_MEMBERSHIP - Add an IP group membership.
+ * @property {number} IP_ADD_SOURCE_MEMBERSHIP - Add an IP group/source membership.
+ * @property {number} IP_BIND_ADDRESS_NO_PORT - Bind to the device only.
+ * @property {number} IP_BLOCK_SOURCE - Block IP group/source.
+ * @property {number} IP_DROP_MEMBERSHIP - Drop an IP group membership.
+ * @property {number} IP_DROP_SOURCE_MEMBERSHIP - Drop an IP group/source membership.
+ * @property {number} IP_FREEBIND - Allow binding to an IP address not assigned to a network interface.
+ * @property {number} IP_HDRINCL - Header is included with data.
+ * @property {number} IP_MSFILTER - Filter IP multicast source memberships.
+ * @property {number} IP_MTU - Path MTU discovery.
+ * @property {number} IP_MTU_DISCOVER - Control Path MTU discovery.
+ * @property {number} IP_MULTICAST_ALL - Receive all multicast packets.
+ * @property {number} IP_MULTICAST_IF - Set outgoing interface for multicast packets.
+ * @property {number} IP_MULTICAST_LOOP - Control multicast packet looping.
+ * @property {number} IP_MULTICAST_TTL - Set time-to-live for outgoing multicast packets.
+ * @property {number} IP_NODEFRAG - Don't fragment IP packets.
+ * @property {number} IP_OPTIONS - Set/get IP options.
+ * @property {number} IP_PASSSEC - Pass security information.
+ * @property {number} IP_PKTINFO - Receive packet information.
+ * @property {number} IP_RECVERR - Receive all ICMP errors.
+ * @property {number} IP_RECVOPTS - Receive all IP options.
+ * @property {number} IP_RECVORIGDSTADDR - Receive original destination address of the socket.
+ * @property {number} IP_RECVTOS - Receive IP TOS.
+ * @property {number} IP_RECVTTL - Receive IP TTL.
+ * @property {number} IP_RETOPTS - Set/get IP options.
+ * @property {number} IP_ROUTER_ALERT - Receive ICMP msgs generated by router.
+ * @property {number} IP_TOS - IP type of service and precedence.
+ * @property {number} IP_TRANSPARENT - Transparent proxy support.
+ * @property {number} IP_TTL - IP time-to-live.
+ * @property {number} IP_UNBLOCK_SOURCE - Unblock IP group/source.
+ */
+ ADD_CONST(IPPROTO_IP);
+ ADD_CONST(IP_ADD_MEMBERSHIP);
+ ADD_CONST(IP_ADD_SOURCE_MEMBERSHIP);
+ ADD_CONST(IP_BLOCK_SOURCE);
+ ADD_CONST(IP_DROP_MEMBERSHIP);
+ ADD_CONST(IP_DROP_SOURCE_MEMBERSHIP);
+ ADD_CONST(IP_HDRINCL);
+ ADD_CONST(IP_MSFILTER);
+ ADD_CONST(IP_MULTICAST_IF);
+ ADD_CONST(IP_MULTICAST_LOOP);
+ ADD_CONST(IP_MULTICAST_TTL);
+ ADD_CONST(IP_OPTIONS);
+ ADD_CONST(IP_PKTINFO);
+ ADD_CONST(IP_RECVOPTS);
+ ADD_CONST(IP_RECVTOS);
+ ADD_CONST(IP_RECVTTL);
+ ADD_CONST(IP_RETOPTS);
+ ADD_CONST(IP_TOS);
+ ADD_CONST(IP_TTL);
+ ADD_CONST(IP_UNBLOCK_SOURCE);
+#if defined(__linux__)
+ ADD_CONST(IP_BIND_ADDRESS_NO_PORT);
+ ADD_CONST(IP_FREEBIND);
+ ADD_CONST(IP_MTU);
+ ADD_CONST(IP_MTU_DISCOVER);
+ ADD_CONST(IP_MULTICAST_ALL);
+ ADD_CONST(IP_NODEFRAG);
+ ADD_CONST(IP_PASSSEC);
+ ADD_CONST(IP_RECVERR);
+ ADD_CONST(IP_RECVORIGDSTADDR);
+ ADD_CONST(IP_ROUTER_ALERT);
+ ADD_CONST(IP_TRANSPARENT);
+#endif
+
+ /**
+ * @typedef
+ * @name Socket Option Constants
+ * @description
+ * The `SOL_SOCKET` constant is passed as *level* argument to the
+ * {@link module:socket.socket#getopt|getopt()} and
+ * {@link module:socket.socket#setopt|setopt()} functions in order to set
+ * or retrieve generic socket option values.
+ *
+ * The `SO_*` constants are passed as *option* argument in conjunction with
+ * the `SOL_SOCKET` level to specify the specific option to get or set on
+ * the socket.
+ * @property {number} SOL_SOCKET - Socket options at the socket API level.
+ * @property {number} SO_ACCEPTCONN - Reports whether socket listening is enabled.
+ * @property {number} SO_ATTACH_BPF - Attach BPF program to socket.
+ * @property {number} SO_ATTACH_FILTER - Attach a socket filter.
+ * @property {number} SO_ATTACH_REUSEPORT_CBPF - Attach BPF program for cgroup and skb program reuseport hook.
+ * @property {number} SO_ATTACH_REUSEPORT_EBPF - Attach eBPF program for cgroup and skb program reuseport hook.
+ * @property {number} SO_BINDTODEVICE - Bind socket to a specific interface.
+ * @property {number} SO_BROADCAST - Allow transmission of broadcast messages.
+ * @property {number} SO_BUSY_POLL - Enable busy polling.
+ * @property {number} SO_DEBUG - Enable socket debugging.
+ * @property {number} SO_DETACH_BPF - Detach BPF program from socket.
+ * @property {number} SO_DETACH_FILTER - Detach a socket filter.
+ * @property {number} SO_DOMAIN - Retrieves the domain of the socket.
+ * @property {number} SO_DONTROUTE - Send packets directly without routing.
+ * @property {number} SO_ERROR - Retrieves and clears the error status for the socket.
+ * @property {number} SO_INCOMING_CPU - Retrieves the CPU number on which the last packet was received.
+ * @property {number} SO_INCOMING_NAPI_ID - Retrieves the NAPI ID of the device.
+ * @property {number} SO_KEEPALIVE - Enable keep-alive packets.
+ * @property {number} SO_LINGER - Set linger on close.
+ * @property {number} SO_LOCK_FILTER - Set or get the socket filter lock state.
+ * @property {number} SO_MARK - Set the mark for packets sent through the socket.
+ * @property {number} SO_OOBINLINE - Enables out-of-band data to be received in the normal data stream.
+ * @property {number} SO_PASSCRED - Enable the receiving of SCM_CREDENTIALS control messages.
+ * @property {number} SO_PASSSEC - Enable the receiving of security context.
+ * @property {number} SO_PEEK_OFF - Returns the number of bytes in the receive buffer without removing them.
+ * @property {number} SO_PEERCRED - Retrieves the credentials of the foreign peer.
+ * @property {number} SO_PEERSEC - Retrieves the security context of the foreign peer.
+ * @property {number} SO_PRIORITY - Set the protocol-defined priority for all packets.
+ * @property {number} SO_PROTOCOL - Retrieves the protocol number.
+ * @property {number} SO_RCVBUF - Set the receive buffer size.
+ * @property {number} SO_RCVBUFFORCE - Set the receive buffer size forcefully.
+ * @property {number} SO_RCVLOWAT - Set the minimum number of bytes to process for input operations.
+ * @property {number} SO_RCVTIMEO - Set the timeout for receiving data.
+ * @property {number} SO_REUSEADDR - Allow the socket to be bound to an address that is already in use.
+ * @property {number} SO_REUSEPORT - Enable duplicate address and port bindings.
+ * @property {number} SO_RXQ_OVFL - Reports if the receive queue has overflown.
+ * @property {number} SO_SNDBUF - Set the send buffer size.
+ * @property {number} SO_SNDBUFFORCE - Set the send buffer size forcefully.
+ * @property {number} SO_SNDLOWAT - Set the minimum number of bytes to process for output operations.
+ * @property {number} SO_SNDTIMEO - Set the timeout for sending data.
+ * @property {number} SO_TIMESTAMP - Enable receiving of timestamps.
+ * @property {number} SO_TIMESTAMPNS - Enable receiving of nanosecond timestamps.
+ * @property {number} SO_TYPE - Retrieves the type of the socket (e.g., SOCK_STREAM).
+ */
+ ADD_CONST(SOL_SOCKET);
+ ADD_CONST(SO_ACCEPTCONN);
+ ADD_CONST(SO_BROADCAST);
+ ADD_CONST(SO_DEBUG);
+ ADD_CONST(SO_DONTROUTE);
+ ADD_CONST(SO_ERROR);
+ ADD_CONST(SO_KEEPALIVE);
+ ADD_CONST(SO_LINGER);
+ ADD_CONST(SO_OOBINLINE);
+ ADD_CONST(SO_RCVBUF);
+ ADD_CONST(SO_RCVLOWAT);
+ ADD_CONST(SO_RCVTIMEO);
+ ADD_CONST(SO_REUSEADDR);
+ ADD_CONST(SO_REUSEPORT);
+ ADD_CONST(SO_SNDBUF);
+ ADD_CONST(SO_SNDLOWAT);
+ ADD_CONST(SO_SNDTIMEO);
+ ADD_CONST(SO_TIMESTAMP);
+ ADD_CONST(SO_TYPE);
+#if defined(__linux__)
+ ADD_CONST(SO_ATTACH_BPF);
+ ADD_CONST(SO_ATTACH_FILTER);
+ ADD_CONST(SO_ATTACH_REUSEPORT_CBPF);
+ ADD_CONST(SO_ATTACH_REUSEPORT_EBPF);
+ ADD_CONST(SO_BINDTODEVICE);
+ ADD_CONST(SO_BUSY_POLL);
+ ADD_CONST(SO_DETACH_BPF);
+ ADD_CONST(SO_DETACH_FILTER);
+ ADD_CONST(SO_DOMAIN);
+ ADD_CONST(SO_INCOMING_CPU);
+ ADD_CONST(SO_INCOMING_NAPI_ID);
+ ADD_CONST(SO_LOCK_FILTER);
+ ADD_CONST(SO_MARK);
+ ADD_CONST(SO_PASSCRED);
+ ADD_CONST(SO_PASSSEC);
+ ADD_CONST(SO_PEEK_OFF);
+ ADD_CONST(SO_PEERCRED);
+ ADD_CONST(SO_PEERSEC);
+ ADD_CONST(SO_PRIORITY);
+ ADD_CONST(SO_PROTOCOL);
+ ADD_CONST(SO_RCVBUFFORCE);
+ ADD_CONST(SO_RXQ_OVFL);
+ ADD_CONST(SO_SNDBUFFORCE);
+ ADD_CONST(SO_TIMESTAMPNS);
+#endif
+
+ /**
+ * @typedef
+ * @name TCP Protocol Constants
+ * @description
+ * The `IPPROTO_TCP` constant specifies the TCP protocol number and may be
+ * passed as third argument to {@link module:socket#create|create()} as well
+ * as *level* argument value to {@link module:socket.socket#getopt|getopt()}
+ * and {@link module:socket.socket#setopt|setopt()}.
+ *
+ * The `TCP_*` constants are *option* argument values recognized by
+ * {@link module:socket.socket#getopt|getopt()}
+ * and {@link module:socket.socket#setopt|setopt()}, in conjunction with
+ * the `IPPROTO_TCP` socket level.
+ * @property {number} IPPROTO_TCP - TCP protocol.
+ * @property {number} TCP_CONGESTION - Set the congestion control algorithm.
+ * @property {number} TCP_CORK - Delay packet transmission until full-sized packets are available.
+ * @property {number} TCP_DEFER_ACCEPT - Delay accepting incoming connections until data arrives.
+ * @property {number} TCP_FASTOPEN - Enable TCP Fast Open.
+ * @property {number} TCP_FASTOPEN_CONNECT - Perform TFO connect.
+ * @property {number} TCP_INFO - Retrieve TCP statistics.
+ * @property {number} TCP_KEEPCNT - Number of keepalive probes.
+ * @property {number} TCP_KEEPIDLE - Time before keepalive probes begin.
+ * @property {number} TCP_KEEPINTVL - Interval between keepalive probes.
+ * @property {number} TCP_LINGER2 - Lifetime of orphaned FIN_WAIT2 state sockets.
+ * @property {number} TCP_MAXSEG - Maximum segment size.
+ * @property {number} TCP_NODELAY - Disable Nagle's algorithm.
+ * @property {number} TCP_QUICKACK - Enable quick ACKs.
+ * @property {number} TCP_SYNCNT - Number of SYN retransmits.
+ * @property {number} TCP_USER_TIMEOUT - Set the user timeout.
+ * @property {number} TCP_WINDOW_CLAMP - Set the maximum window.
+ */
+ ADD_CONST(IPPROTO_TCP);
+ ADD_CONST(TCP_FASTOPEN);
+ ADD_CONST(TCP_KEEPCNT);
+ ADD_CONST(TCP_KEEPINTVL);
+ ADD_CONST(TCP_MAXSEG);
+ ADD_CONST(TCP_NODELAY);
+#if defined(__linux__)
+ ADD_CONST(TCP_CONGESTION);
+ ADD_CONST(TCP_CORK);
+ ADD_CONST(TCP_DEFER_ACCEPT);
+ ADD_CONST(TCP_FASTOPEN_CONNECT);
+ ADD_CONST(TCP_INFO);
+ ADD_CONST(TCP_KEEPIDLE);
+ ADD_CONST(TCP_LINGER2);
+ ADD_CONST(TCP_QUICKACK);
+ ADD_CONST(TCP_SYNCNT);
+ ADD_CONST(TCP_USER_TIMEOUT);
+ ADD_CONST(TCP_WINDOW_CLAMP);
+#endif
+
+ /**
+ * @typedef
+ * @name UDP Protocol Constants
+ * @description
+ * The `IPPROTO_UDP` constant specifies the UDP protocol number and may be
+ * passed as third argument to {@link module:socket#create|create()} as well
+ * as *level* argument value to {@link module:socket.socket#getopt|getopt()}
+ * and {@link module:socket.socket#setopt|setopt()}.
+ *
+ * The `UDP_*` constants are *option* argument values recognized by
+ * {@link module:socket.socket#getopt|getopt()}
+ * and {@link module:socket.socket#setopt|setopt()}, in conjunction with
+ * the `IPPROTO_UDP` socket level.
+ * @property {number} IPPROTO_UDP - UDP protocol.
+ * @property {number} UDP_CORK - Cork data until flush.
+ */
+ ADD_CONST(IPPROTO_UDP);
+#if defined(__linux__)
+ ADD_CONST(UDP_CORK);
+#endif
+
+ /**
+ * @typedef
+ * @name Shutdown Constants
+ * @description
+ * The `SHUT_*` constants are passed as argument to the
+ * {@link module:socket.socket#shutdown|shutdown()} function to specify
+ * which direction of a full duplex connection to shut down.
+ * @property {number} SHUT_RD - Disallow further receptions.
+ * @property {number} SHUT_WR - Disallow further transmissions.
+ * @property {number} SHUT_RDWR - Disallow further receptions and transmissions.
+ */
+ ADD_CONST(SHUT_RD);
+ ADD_CONST(SHUT_WR);
+ ADD_CONST(SHUT_RDWR);
+
+ /**
+ * @typedef
+ * @name Address Info Flags
+ * @description
+ * The `AI_*` flags may be passed as bitwise OR-ed number in the *flags*
+ * property of the *hints* dictionary argument of
+ * {@link module:socket#addrinfo|addrinfo()}.
+ * @property {number} AI_ADDRCONFIG - Address configuration flag.
+ * @property {number} AI_ALL - Return IPv4 and IPv6 socket addresses.
+ * @property {number} AI_CANONIDN - Canonicalize using the IDNA standard.
+ * @property {number} AI_CANONNAME - Fill in the canonical name field.
+ * @property {number} AI_IDN - Enable IDN encoding.
+ * @property {number} AI_NUMERICHOST - Prevent hostname resolution.
+ * @property {number} AI_NUMERICSERV - Prevent service name resolution.
+ * @property {number} AI_PASSIVE - Use passive socket.
+ * @property {number} AI_V4MAPPED - Map IPv6 addresses to IPv4-mapped format.
+ */
+ ADD_CONST(AI_ADDRCONFIG);
+ ADD_CONST(AI_ALL);
+ ADD_CONST(AI_CANONIDN);
+ ADD_CONST(AI_CANONNAME);
+ ADD_CONST(AI_IDN);
+ ADD_CONST(AI_NUMERICHOST);
+ ADD_CONST(AI_NUMERICSERV);
+ ADD_CONST(AI_PASSIVE);
+ ADD_CONST(AI_V4MAPPED);
+
+ /**
+ * @typedef
+ * @name Name Info Constants
+ * @description
+ * The `NI_*` flags may be passed as bitwise OR-ed number via the *flags*
+ * argument of {@link module:socket#nameinfo|nameinfo()}.
+ * @property {number} NI_DGRAM - Datagram socket type.
+ * @property {number} NI_IDN - Enable IDN encoding.
+ * @property {number} NI_NAMEREQD - Hostname resolution required.
+ * @property {number} NI_NOFQDN - Do not force fully qualified domain name.
+ * @property {number} NI_NUMERICHOST - Return numeric form of the hostname.
+ * @property {number} NI_NUMERICSERV - Return numeric form of the service name.
+ */
+ ADD_CONST(NI_DGRAM);
+ ADD_CONST(NI_IDN);
+ ADD_CONST(NI_MAXHOST);
+ ADD_CONST(NI_MAXSERV);
+ ADD_CONST(NI_NAMEREQD);
+ ADD_CONST(NI_NOFQDN);
+ ADD_CONST(NI_NUMERICHOST);
+ ADD_CONST(NI_NUMERICSERV);
+
+ /**
+ * @typedef
+ * @name Poll Event Constants
+ * @description
+ * The following constants represent event types for polling operations and
+ * are set or returned as part of a
+ * {@link module:socket.PollSpec|PollSpec} tuple by the
+ * {@link module:socket#poll|poll()} function. When passed via an argument
+ * PollSpec to `poll()`, they specify the I/O events to watch for on the
+ * corresponding handle. When appearing in a PollSpec returned by `poll()`,
+ * they specify the I/O events that occurred on a watched handle.
+ * @property {number} POLLIN - Data available to read.
+ * @property {number} POLLPRI - Priority data available to read.
+ * @property {number} POLLOUT - Writable data available.
+ * @property {number} POLLERR - Error condition.
+ * @property {number} POLLHUP - Hang up.
+ * @property {number} POLLNVAL - Invalid request.
+ * @property {number} POLLRDHUP - Peer closed or shutdown writing.
+ */
+ ADD_CONST(POLLIN);
+ ADD_CONST(POLLPRI);
+ ADD_CONST(POLLOUT);
+ ADD_CONST(POLLERR);
+ ADD_CONST(POLLHUP);
+ ADD_CONST(POLLNVAL);
+#if defined(__linux__)
+ ADD_CONST(POLLRDHUP);
+#endif
+
+ uc_type_declare(vm, "socket", socket_fns, close_socket);
+}