#include <unistd.h>
#include <string.h>
#include <errno.h>
+#include <fcntl.h>
#define countof(x) ( sizeof(x) / sizeof(x[0]) )
#define libc_error(ctx) ( JS_ThrowInternalError(ctx, "%s", strerror(errno)) )
-// use struct addrinfo in socket functions
-#define ai_socket(a) ( socket( (a)->ai_family, \
- (a)->ai_socktype, \
- (a)->ai_protocol \
- ) )
-#define ai_bind(s, a) ( bind((s), (a)->ai_addr, (a)->ai_addrlen) )
-#define ai_connect(s, a) ( connect((s), (a)->ai_addr, (a)->ai_addrlen) )
-
static JSValue js_os_socket(
JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv
) {
};
static JSClassID socket_cid;
+static double net_timeout = -1;
typedef struct {
int fd;
int type;
+ int connected;
double timeout;
struct sockaddr_storage bind;
struct sockaddr_storage peer;
SocketData * data = JS_GetOpaque2(ctx, this, socket_cid);
struct timeval t; double delay;
- if ( JS_IsNull(val)
+ if (!JS_IsNumber(val)
|| JS_ToFloat64(ctx, &delay, val) || delay < 0) {
t.tv_sec = 0; t.tv_usec = 0;
delay = -1;
if ( setsockopt(data->fd, SOL_SOCKET, SO_SNDTIMEO, &t, sizeof(t))
|| setsockopt(data->fd, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(t))
) return libc_error(ctx);
+
data->timeout = delay;
+ if ( delay == 0
+ && fcntl(data->fd, F_SETFL, O_NONBLOCK)
+ ) return libc_error(ctx);
+ else if ( delay != 0
+ && fcntl(data->fd, F_SETFL, 0)
+ ) return libc_error(ctx);
+
+
return JS_UNDEFINED;
}
static JSValue sock_get_timeout( JSContext *ctx, JSValueConst this ) {
else return JS_NewFloat64(ctx, data->timeout);
}
+static JSValue sock_get_fd( JSContext *ctx, JSValueConst this ) {
+ SocketData * data = JS_GetOpaque2(ctx, this, socket_cid);
+ return JS_NewInt32(ctx, data->fd);
+}
+static JSValue sock_get_family( JSContext *ctx, JSValueConst this ) {
+ SocketData * data = JS_GetOpaque2(ctx, this, socket_cid);
+ return JS_NewInt32(ctx, data->bind.ss_family);
+}
+static JSValue sock_get_type( JSContext *ctx, JSValueConst this ) {
+ SocketData * data = JS_GetOpaque2(ctx, this, socket_cid);
+ return JS_NewInt32(ctx, data->type);
+}
+static JSValue sock_get_connected( JSContext *ctx, JSValueConst this ) {
+ SocketData * data = JS_GetOpaque2(ctx, this, socket_cid);
+ return JS_NewBool(ctx, data->connected);
+}
+
static JSValue sock_accept(
JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv
) {
if ( newdata
&& ( newdata->fd =
- accept( data->fd, (struct sockaddr *) &(newdata->peer), &ps)
+ accept( data->fd, (struct sockaddr *) &(newdata->peer), &ps )
) >= 0
) {
memcpy(&(newdata->bind), &(data->bind), sizeof(data->bind));
+ newdata->connected = 1;
JS_SetOpaque(new, newdata);
if (data->timeout >= 0)
(void) sock_set_timeout(ctx, new, JS_NewFloat64(ctx, data->timeout));
"expected ArrayBuffer or String"
);
- if ((int)len < 0) return libc_error(ctx);
- else return JS_NewInt32(ctx, len);
+ if ((int)len < 0) {
+ if (errno == EPIPE) data->connected = 0;
+ return libc_error(ctx);
+ } else return JS_NewInt32(ctx, len);
}
// like in quickjs.c, needed for direct ArrayBuffer in sock_recv()
) {
// resize/clear buffer in case of short read,
if (len) js_realloc(ctx, buf, len);
- else { js_free(ctx, buf); buf = NULL; }
+ else {
+ // XXX: does a 0 return alway indicate eof?
+ data->connected = 0;
+ js_free(ctx, buf); buf = NULL;
+ }
// XXX: what does the opaque field in NewArryBuffer do? Is it important?
if (str) {
) {
SocketData *data = JS_GetOpaque2(ctx, this, socket_cid);
+ if (data->connected && data->type == SOCK_STREAM)
+ shutdown(data->fd, SHUT_RDWR);
+ data->connected = 0;
+
if ( close(data->fd) ) return libc_error(ctx);
else return JS_UNDEFINED;
}
JS_CGETSET_MAGIC_DEF("peerName", sock_get_addr, NULL, 2),
JS_CGETSET_MAGIC_DEF("peerPort", sock_get_addr, NULL, 3),
JS_CGETSET_DEF("timeout", sock_get_timeout, sock_set_timeout),
- // JS_CFUNC_DEF("reconnect", ) // TODO
+ JS_CGETSET_DEF("fd", sock_get_fd, NULL),
+ JS_CGETSET_DEF("family", sock_get_family, NULL),
+ JS_CGETSET_DEF("type", sock_get_type, NULL),
+ JS_CGETSET_DEF("connected", sock_get_connected, NULL),
};
static JSClassDef socket_class = {
"Socket", .finalizer = sock_destroy
};
-static JSValue ip_listen(
+static int net_addrinfo(
+ int family, int type, const char *host, const char *port,
+ struct sockaddr_storage * addr
+) {
+ struct addrinfo hints = { .ai_family = family, .ai_socktype = type };
+ struct addrinfo * info;
+ int gai_error;
+
+ if (!(gai_error = getaddrinfo( host, port, &hints, &info ))) {
+ memcpy(addr, info->ai_addr, info->ai_addrlen);
+ }
+ freeaddrinfo(info);
+ return gai_error;
+}
+
+static int net_ip_listen(SocketData *so, int type) {
+ if (!so) return 1;
+
+ if ( (so->fd = socket(so->bind.ss_family, type, 0)) >= 0
+ && !bind(so->fd, (struct sockaddr *) &(so->bind), sizeof(so->bind))
+ && (type == SOCK_DGRAM || !listen(so->fd, 1))
+ ){
+ so->type = type;
+ so->timeout = -1;
+ return 0;
+ } else {
+ if (so->fd >= 0) close(so->fd);
+ return 1;
+ }
+}
+
+static JSValue js_net_ip_listen(
JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv, int magic
) {
- struct addrinfo hints = {
- .ai_family = (magic&2) ? AF_INET6 : AF_INET,
- .ai_socktype = (magic&1) ? SOCK_STREAM : SOCK_DGRAM,
- };
- struct addrinfo * self = NULL;
+ int family = (magic&2) ? AF_INET6 : AF_INET;
+ int type = (magic&1) ? SOCK_STREAM : SOCK_DGRAM;
const char *host, *port; int gai_err;
SocketData *data = js_mallocz(ctx, sizeof(*data));
JSValue new = JS_NewObjectClass(ctx, socket_cid);
port = JS_ToCString(ctx, argv[1]);
if ( data && (data->fd = -1)
- && !(gai_err = getaddrinfo( host, port, &hints, &self))
- && (data->fd = ai_socket(self)) >= 0
- && !ai_bind(data->fd, self)
- && ( hints.ai_socktype == SOCK_DGRAM || !listen(data->fd, 1))
+ && !(gai_err = net_addrinfo(family, type, host, port, &(data->bind)))
+ && !net_ip_listen(data, type)
) {
- data->type = self->ai_socktype;
- data->timeout = -1;
- memcpy(&(data->bind), self->ai_addr, sizeof(struct sockaddr));
JS_SetOpaque(new, data);
} else {
- if (data && data->fd >= 0) close(data->fd);
if (!data) new = JS_EXCEPTION;
else if (!gai_err) new = libc_error(ctx);
else new = JS_ThrowInternalError(ctx, "%s", gai_strerror(gai_err));
js_free(ctx, data);
JS_FreeValue(ctx, new);
}
- freeaddrinfo(self);
JS_FreeCString(ctx, host); JS_FreeCString(ctx, port);
return new;
}
-static JSValue ip_connect(
+static int net_ip_connect(SocketData *so, int type) {
+ socklen_t ps = sizeof(struct sockaddr_storage);
+ if (!so) return 1;
+
+ if ( (so->fd = socket(so->peer.ss_family, type, 0)) >= 0
+ && !connect(so->fd, (struct sockaddr *) &(so->peer), ps)
+ && !getsockname(so->fd, (struct sockaddr *) &(so->bind), &ps)
+ ){
+ so->type = type;
+ so->timeout = -1;
+ so->connected = 1;
+ return 0;
+ } else {
+ if (so->fd >= 0) close(so->fd);
+ return 1;
+ }
+}
+
+static JSValue js_net_ip_connect(
JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv, int magic
) {
- struct addrinfo hints = {
- .ai_family = (magic&2) ? AF_INET6 : AF_INET,
- .ai_socktype = (magic&1) ? SOCK_STREAM : SOCK_DGRAM,
- };
- struct addrinfo * peer = NULL;
+ int family = (magic&2) ? AF_INET6 : AF_INET;
+ int type = (magic&1) ? SOCK_STREAM : SOCK_DGRAM;
const char *host, *port; int gai_err;
SocketData *data = js_mallocz(ctx, sizeof(*data));
JSValue new = JS_NewObjectClass(ctx, socket_cid);
port = JS_ToCString(ctx, argv[1]);
if ( data && (data->fd = -1)
- && !(gai_err = getaddrinfo( host, port, &hints, &peer))
- && (data->fd = ai_socket(peer)) >= 0
- && !ai_connect(data->fd, peer)
+ && !(gai_err = net_addrinfo(family, type, host, port, &(data->peer)))
+ && !(net_ip_connect(data, type))
) {
- data->type = peer->ai_socktype;
- data->timeout = -1;
- memcpy(&(data->peer), peer->ai_addr, sizeof(struct sockaddr));
JS_SetOpaque(new, data);
} else {
- if (data && data->fd >= 0) close(data->fd);
if (!data) new = JS_EXCEPTION;
else if (!gai_err) new = libc_error(ctx);
else new = JS_ThrowInternalError(ctx, "%s", gai_strerror(gai_err));
js_free(ctx, data);
JS_FreeValue(ctx, new);
}
- freeaddrinfo(peer);
JS_FreeCString(ctx, host); JS_FreeCString(ctx, port);
return new;
}
-static JSValue unix_bind(
+static int net_unix_bind(SocketData *so, const char *path, size_t plen, int con) {
+ struct sockaddr_un * addr;
+ if (!so) return 1;
+
+ addr = (struct sockaddr_un *) (con ? &(so->peer) : &(so->bind));
+ addr->sun_family = AF_UNIX;
+ if (plen < sizeof(addr->sun_path)) strncpy(addr->sun_path, path, plen);
+
+ if ( (so->fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0
+ && !( con ? connect(so->fd,(struct sockaddr *) addr ,sizeof(*addr))
+ : bind(so->fd,(struct sockaddr *) addr ,sizeof(*addr))
+ ) && (con || !listen(so->fd, 1))
+ ) {
+ so->type = SOCK_STREAM;
+ so->connected = con;
+ so->timeout = -1;
+ return 0;
+ } else {
+ if (so->fd >= 0) close(so->fd);
+ return 1;
+ }
+}
+
+static JSValue js_net_unix_bind(
JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv, int c
) {
SocketData *data = js_mallocz(ctx, sizeof(*data));
path = JS_ToCStringLen(ctx, &plen, argv[0]);
- if ( data && (data->fd = -1)
- && (addr = (struct sockaddr_un *) (c ? &(data->peer) : &(data->bind)))
- && (addr->sun_family = AF_UNIX)
- && (plen < sizeof(addr->sun_path))
- && strncpy(addr->sun_path, path, plen)
- && (data->fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0
- && !( c ?connect(data->fd,(struct sockaddr *) &(data->peer),sizeof(*addr))
- : bind(data->fd,(struct sockaddr *) &(data->bind),sizeof(*addr))
- ) && (c || !listen(data->fd, 1))
+ if ( data && (plen < sizeof(addr->sun_path))
+ && !net_unix_bind(data, path, plen, c)
) {
- data->type = SOCK_STREAM;
- data->timeout = -1;
JS_SetOpaque(new, data);
} else {
- if (data && data->fd >= 0) close(data->fd);
if (!data) {
new = JS_EXCEPTION;
} else if (plen < sizeof(addr->sun_path)) {
return new;
}
+static JSValue net_set_timeout(
+ JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv
+) {
+ if ( ! JS_ToFloat64(ctx, &net_timeout, argv[0])) return argv[0];
+ else return JS_EXCEPTION;
+}
+static JSValue net_get_timeout(
+ JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv
+) {
+ if (net_timeout < 0) return JS_UNDEFINED;
+ return JS_NewFloat64(ctx, net_timeout);
+}
+
static const JSCFunctionListEntry net_funcs[] = {
JS_PROP_INT32_DEF("AF_UNIX", AF_UNIX, JS_PROP_CONFIGURABLE),
JS_PROP_INT32_DEF("AF_INET", AF_INET, JS_PROP_CONFIGURABLE),
JS_PROP_INT32_DEF("AF_INET6", AF_INET6, JS_PROP_CONFIGURABLE),
JS_PROP_INT32_DEF("SOCK_STREAM", SOCK_STREAM, JS_PROP_CONFIGURABLE),
JS_PROP_INT32_DEF("SOCK_DGRAM", SOCK_DGRAM, JS_PROP_CONFIGURABLE),
- JS_CFUNC_MAGIC_DEF("udpListen", 2, ip_listen, 0),
- JS_CFUNC_MAGIC_DEF("tcpListen", 2, ip_listen, 1),
- JS_CFUNC_MAGIC_DEF("udp6Listen", 2, ip_listen, 2),
- JS_CFUNC_MAGIC_DEF("tcp6Listen", 2, ip_listen, 3),
- JS_CFUNC_MAGIC_DEF("udpConnect", 2, ip_connect, 0),
- JS_CFUNC_MAGIC_DEF("tcpConnect", 2, ip_connect, 1),
- JS_CFUNC_MAGIC_DEF("udp6Connect", 2, ip_connect, 2),
- JS_CFUNC_MAGIC_DEF("tcp6Connect", 2, ip_connect, 3),
- JS_CFUNC_MAGIC_DEF("unixListen", 1, unix_bind, 0),
- JS_CFUNC_MAGIC_DEF("unixConnect", 1, unix_bind, 1),
+ JS_CFUNC_MAGIC_DEF("udpListen", 2, js_net_ip_listen, 0),
+ JS_CFUNC_MAGIC_DEF("tcpListen", 2, js_net_ip_listen, 1),
+ JS_CFUNC_MAGIC_DEF("udp6Listen", 2, js_net_ip_listen, 2),
+ JS_CFUNC_MAGIC_DEF("tcp6Listen", 2, js_net_ip_listen, 3),
+ JS_CFUNC_MAGIC_DEF("udpConnect", 2, js_net_ip_connect, 0),
+ JS_CFUNC_MAGIC_DEF("tcpConnect", 2, js_net_ip_connect, 1),
+ JS_CFUNC_MAGIC_DEF("udp6Connect", 2, js_net_ip_connect, 2),
+ JS_CFUNC_MAGIC_DEF("tcp6Connect", 2, js_net_ip_connect, 3),
+ JS_CFUNC_MAGIC_DEF("unixListen", 1, js_net_unix_bind, 0),
+ JS_CFUNC_MAGIC_DEF("unixConnect", 1, js_net_unix_bind, 1),
+
+ // XXX: How does one use a get/set property in a module provided object?
+ // JS_CGETSET_DEF("timeout", net_get_timeout, net_set_timeout),
+ JS_CFUNC_DEF("setConnectTimeout", 1, net_set_timeout),
+ JS_CFUNC_DEF("getConnectTimeout", 1, net_get_timeout),
};
// static const JSCFunctionListEntry net_obj[] = {
JS_SetPropertyFunctionList(ctx, proto, socket_ptype, countof(socket_ptype));
JS_SetClassProto(ctx, socket_cid, proto);
- JS_SetModuleExportList(ctx, mod, net_funcs, countof(net_funcs));
- return 0;
+ return JS_SetModuleExportList(ctx, mod, net_funcs, countof(net_funcs));
}
JSModuleDef *js_init_module(JSContext *ctx, const char *name) {