diff --git a/CMakeLists.txt b/CMakeLists.txt index ade8aac4..084c4417 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,7 @@ option(RESOLV_SUPPORT "NS resolve plugin support" ON) option(STRUCT_SUPPORT "Struct plugin support" ON) option(ULOOP_SUPPORT "Uloop plugin support" ${DEFAULT_ULOOP_SUPPORT}) option(LOG_SUPPORT "Log plugin support" ON) +option(SOCKET_SUPPORT "Socket plugin support" ON) set(LIB_SEARCH_PATH "${CMAKE_INSTALL_PREFIX}/lib/ucode/*.so:${CMAKE_INSTALL_PREFIX}/share/ucode/*.uc:./*.so:./*.uc" CACHE STRING "Default library search path") string(REPLACE ":" "\", \"" LIB_SEARCH_DEFINE "${LIB_SEARCH_PATH}") @@ -266,6 +267,13 @@ if(LOG_SUPPORT) endif() endif() +if(SOCKET_SUPPORT) + set(LIBRARIES ${LIBRARIES} socket_lib) + add_library(socket_lib MODULE lib/socket.c) + set_target_properties(socket_lib PROPERTIES OUTPUT_NAME socket PREFIX "") + target_link_options(socket_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS}) +endif() + if(UNIT_TESTING) enable_testing() add_definitions(-DUNIT_TESTING) diff --git a/lib/socket.c b/lib/socket.c new file mode 100644 index 00000000..6ec5809d --- /dev/null +++ b/lib/socket.c @@ -0,0 +1,3541 @@ +/* + * Copyright (C) 2024 Jo-Philipp Wich + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ucode/module.h" +#include "ucode/platform.h" + +#if defined(__APPLE__) +# include + +# 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); +}