Synchronet now requires the libarchive development package (e.g. libarchive-dev on Debian-based Linux distros, libarchive.org for more info) to build successfully.

Commit d9448cbf authored by deuce's avatar deuce

Add new ConnectedSocket() and ListeningSocket() constructors.

These have a large number of optional parameters, so these are placed in
a separate argument as an object:

ie: var s = new ConnectedSocket("synchro.net", "finger", {type:SOCK_DGRAM});
ie: var s = new ListeningSocket(["::","0.0.0.0"], "printer", "spooler", {retry_count:15});
parent 984a03e5
......@@ -2195,6 +2195,32 @@ JSClass js_socket_class = {
,js_finalize_socket /* finalize */
};
JSClass js_connected_socket_class = {
"ConnectedSocket" /* name */
,JSCLASS_HAS_PRIVATE /* flags */
,JS_PropertyStub /* addProperty */
,JS_PropertyStub /* delProperty */
,js_socket_get /* getProperty */
,js_socket_set /* setProperty */
,js_socket_enumerate /* enumerate */
,js_socket_resolve /* resolve */
,JS_ConvertStub /* convert */
,js_finalize_socket /* finalize */
};
JSClass js_listening_socket_class = {
"ListeningSocket" /* name */
,JSCLASS_HAS_PRIVATE /* flags */
,JS_PropertyStub /* addProperty */
,JS_PropertyStub /* delProperty */
,js_socket_get /* getProperty */
,js_socket_set /* setProperty */
,js_socket_enumerate /* enumerate */
,js_socket_resolve /* resolve */
,JS_ConvertStub /* convert */
,js_finalize_socket /* finalize */
};
static BOOL js_DefineSocketOptionsArray(JSContext *cx, JSObject *obj, int type)
{
size_t i;
......@@ -2268,6 +2294,410 @@ JSObject* DLLCALL js_CreateSocketObjectWithoutParent(JSContext* cx, SOCKET sock,
return(obj);
}
static JSBool
js_connected_socket_constructor(JSContext *cx, uintN argc, jsval *arglist)
{
JSObject *obj;
jsval *argv=JS_ARGV(cx, arglist);
int32 type=SOCK_STREAM; /* default = TCP */
int32 domain = PF_UNSPEC; /* default = IPvAny */
int32 proto = IPPROTO_IP;
char* protocol=NULL;
uintN i;
js_socket_private_t* p = NULL;
jsval v;
struct addrinfo hints;
struct addrinfo *res=NULL;
struct addrinfo *cur;
char *host = NULL;
uint16_t port;
char pstr[6];
jsrefcount rc;
int nonblock;
struct timeval tv;
fd_set wfd;
fd_set efd;
scfg_t *scfg;
char error[256];
scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx));
if (scfg == NULL) {
JS_ReportError(cx, "Unable to get private runtime");
return JS_FALSE;
}
if (argc < 2) {
JS_ReportError(cx, "At least two arguments required (hostname and port)");
return JS_FALSE;
}
// Optional arguments in an object...
if (argc > 2) {
if (!JS_ValueToObject(cx, argv[2], &obj)) {
JS_ReportError(cx, "Invalid third argument");
return JS_FALSE;
}
if (JS_GetProperty(cx, obj, "domain", &v) && !JSVAL_IS_VOID(v)) {
if (!JS_ValueToInt32(cx, v, &domain)) {
JS_ReportError(cx, "Invalid domain property");
return JS_FALSE;
}
}
if (JS_GetProperty(cx, obj, "type", &v) && !JSVAL_IS_VOID(v)) {
if (!JS_ValueToInt32(cx, v, &type)) {
JS_ReportError(cx, "Invalid type property");
return JS_FALSE;
}
}
if (JS_GetProperty(cx, obj, "proto", &v) && !JSVAL_IS_VOID(v)) {
if (!JS_ValueToInt32(cx, v, &proto)) {
JS_ReportError(cx, "Invalid proto property");
return JS_FALSE;
}
}
if (JS_GetProperty(cx, obj, "protocol", &v) && !JSVAL_IS_VOID(v)) {
JSVALUE_TO_MSTRING(cx, v, protocol, NULL);
HANDLE_PENDING(cx, protocol);
}
}
JSVALUE_TO_MSTRING(cx, argv[0], host, NULL);
HANDLE_PENDING(cx, host);
port = js_port(cx, argv[1], type);
if (port == 0) {
JS_ReportError(cx, "Invalid port");
goto fail;
}
obj=JS_NewObject(cx, &js_socket_class, NULL, NULL);
JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(obj));
if((p=(js_socket_private_t*)malloc(sizeof(js_socket_private_t)))==NULL) {
JS_ReportError(cx,"malloc failed");
goto fail;
}
memset(p,0,sizeof(js_socket_private_t));
rc = JS_SUSPENDREQUEST(cx);
sprintf(pstr, "%hu", port);
memset(&hints, 0, sizeof(hints));
hints.ai_family = domain;
hints.ai_socktype = type;
hints.ai_protocol = proto;
hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV;
if((i = getaddrinfo(host, pstr, &hints, &res)) != 0) {
JS_RESUMEREQUEST(cx, rc);
JS_ReportError(cx, gai_strerror(i));
goto fail;
}
p->sock = INVALID_SOCKET;
for(cur=res; cur && p->sock == INVALID_SOCKET; cur=cur->ai_next) {
if(p->sock==INVALID_SOCKET) {
p->sock=socket(cur->ai_family, cur->ai_socktype, cur->ai_protocol);
if(p->sock==INVALID_SOCKET)
continue;
if (set_socket_options(scfg, p->sock, protocol, error, sizeof(error))) {
JS_RESUMEREQUEST(cx, rc);
JS_ReportError(cx, error);
goto fail;
}
/* Set to non-blocking for the connect */
nonblock=-1;
ioctlsocket(p->sock, FIONBIO, &nonblock);
}
if(connect(p->sock, cur->ai_addr, cur->ai_addrlen)) {
switch(ERROR_VALUE) {
case EINPROGRESS:
case EINTR:
case EAGAIN:
#if (EAGAIN!=EWOULDBLOCK)
case EWOULDBLOCK:
#endif
for(;p->sock!=INVALID_SOCKET;) {
// TODO: Do clever timeout stuff here.
tv.tv_sec=1;
tv.tv_usec=0;
FD_ZERO(&wfd);
FD_SET(p->sock, &wfd);
FD_ZERO(&efd);
FD_SET(p->sock, &efd);
switch(select(p->sock+1, NULL, &wfd, &efd, &tv)) {
case 0:
// TODO: Check timeout here...
break;
case -1:
closesocket(p->sock);
p->sock=INVALID_SOCKET;
continue;
case 1:
if(FD_ISSET(p->sock, &efd)) {
closesocket(p->sock);
p->sock=INVALID_SOCKET;
continue;
}
else {
if(socket_check(p->sock, NULL, NULL, 0))
goto connected;
closesocket(p->sock);
p->sock=INVALID_SOCKET;
continue;
}
default:
break;
}
}
connected:
memcpy(&p->remote_addr, cur->ai_addr, cur->ai_addrlen);
break;
default:
closesocket(p->sock);
p->sock=INVALID_SOCKET;
continue;
}
}
}
freeaddrinfo(res);
JS_RESUMEREQUEST(cx, rc);
if (p->sock == INVALID_SOCKET) {
JS_ReportError(cx, "Unable to connect");
goto fail;
}
ioctlsocket(p->sock,FIONBIO,(ulong*)&(p->nonblocking));
call_socket_open_callback(TRUE);
if(protocol)
free(protocol);
p->hostname = host;
p->type = type;
p->network_byte_order = TRUE;
p->session=-1;
p->unflushed = 0;
p->is_connected = TRUE;
if(!JS_SetPrivate(cx, obj, p)) {
JS_ReportError(cx,"JS_SetPrivate failed");
free(p);
return(JS_FALSE);
}
if(!js_DefineSocketOptionsArray(cx, obj, type))
return(JS_FALSE);
#ifdef BUILD_JSDOCS
js_DescribeSyncObject(cx,obj,"Class used for outgoing TCP/IP socket communications",317);
js_DescribeSyncConstructor(cx,obj,"To create a new ConnectedSocket object: "
"<tt>load('sockdefs.js'); var s = new ConnectedSocket(<i>hostname</i>, <i>port</i>, {domain:<i>domain</i>, proto:<i>proto</i> ,type:<i>type</i>, protocol:<i>protocol</i>)</tt><br>"
"where <i>domain</i> (optional) = <tt>PF_UNSPEC</tt> (default) for IPv4 or IPv6, <tt>PF_INET</tt> for IPv4, or <tt>PF_INET6</tt> for IPv6<br>"
"<i>proto</i> (optional) = <tt>IPPROTO_IP</tt> (default)<br>"
"<i>type</i> (optional) = <tt>SOCK_STREAM</tt> for TCP (default) or <tt>SOCK_DGRAM</tt> for UDP<br>"
"and <i>protocol</i> (optional) = the name of the protocol or service the socket is to be used for<br>"
);
JS_DefineProperty(cx,obj,"_dont_document",JSVAL_TRUE,NULL,NULL,JSPROP_READONLY);
#endif
dbprintf(FALSE, p, "object constructed");
return(JS_TRUE);
fail:
if (p)
free(p);
if (protocol)
free(protocol);
if (host)
free(host);
return JS_FALSE;
}
struct ls_cb_data {
char *protocol;
scfg_t *scfg;
};
static void
ls_cb(SOCKET sock, void *cbptr)
{
struct ls_cb_data *cb = cbptr;
char error[256];
call_socket_open_callback(TRUE);
if(set_socket_options(cb->scfg, sock, cb->protocol, error, sizeof(error)))
lprintf(LOG_ERR,"%04d !ERROR %s", sock, error);
}
static JSBool
js_listening_socket_constructor(JSContext *cx, uintN argc, jsval *arglist)
{
JSObject *obj;
jsval *argv=JS_ARGV(cx, arglist);
int32 type=SOCK_STREAM; /* default = TCP */
int32 domain = PF_UNSPEC; /* default = IPvAny */
int32 proto = IPPROTO_IP;
int retry_count = 0;
int retry_delay = 15;
char* protocol=NULL;
char *interface=NULL;
js_socket_private_t* p = NULL;
jsval v;
uint16_t port;
jsrefcount rc;
scfg_t *scfg;
struct xpms_set *set;
struct ls_cb_data cb;
jsuint count;
int i;
scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx));
if (scfg == NULL) {
JS_ReportError(cx, "Unable to get private runtime");
goto fail;
}
cb.scfg = scfg;
if (argc < 3) {
JS_ReportError(cx, "At least two arguments required (interfaces, port, and protocol)");
goto fail;
}
if (argc > 3) {
if (!JS_ValueToObject(cx, argv[3], &obj)) {
JS_ReportError(cx, "Invalid fourth argument");
goto fail;
}
if (JS_GetProperty(cx, obj, "domain", &v) && !JSVAL_IS_VOID(v)) {
if (!JS_ValueToInt32(cx, v, &domain)) {
JS_ReportError(cx, "Invalid domain property");
goto fail;
}
}
if (JS_GetProperty(cx, obj, "type", &v) && !JSVAL_IS_VOID(v)) {
if (!JS_ValueToInt32(cx, v, &type)) {
JS_ReportError(cx, "Invalid type property");
goto fail;
}
}
if (JS_GetProperty(cx, obj, "proto", &v) && !JSVAL_IS_VOID(v)) {
if (!JS_ValueToInt32(cx, v, &proto)) {
JS_ReportError(cx, "Invalid proto property");
goto fail;
}
}
if (JS_GetProperty(cx, obj, "retry_count", &v) && !JSVAL_IS_VOID(v)) {
if (!JS_ValueToInt32(cx, v, &retry_count)) {
JS_ReportError(cx, "Invalid retry_count property");
goto fail;
}
}
if (JS_GetProperty(cx, obj, "retry_delay", &v) && !JSVAL_IS_VOID(v)) {
if (!JS_ValueToInt32(cx, v, &retry_delay)) {
JS_ReportError(cx, "Invalid retry_delay property");
goto fail;
}
}
}
obj = NULL;
port = js_port(cx, argv[1], type);
JSVALUE_TO_MSTRING(cx, argv[2], protocol, NULL);
HANDLE_PENDING(cx, protocol);
cb.protocol = protocol;
if (JSVAL_IS_OBJECT(argv[0])) {
obj = JSVAL_TO_OBJECT(argv[0]);
if (obj == NULL || !JS_IsArrayObject(cx, obj)) {
JS_ReportError(cx, "Invalid interface list");
goto fail;
}
}
set = xpms_create(retry_count, retry_delay, lprintf);
if (set == NULL) {
JS_ReportError(cx, "Unable to create socket set");
goto fail;
}
if (obj == NULL) {
JSVALUE_TO_MSTRING(cx, argv[0], interface, NULL);
HANDLE_PENDING(cx, interface);
rc = JS_SUSPENDREQUEST(cx);
if (!xpms_add(set, domain, type, proto, interface, port, protocol, ls_cb, NULL, &cb)) {
JS_RESUMEREQUEST(cx, rc);
JS_ReportError(cx, "Unable to add host to socket set");
goto fail;
}
JS_RESUMEREQUEST(cx, rc);
}
else {
if (!JS_GetArrayLength(cx, obj, &count)) {
free(protocol);
return JS_FALSE;
}
for (i = 0; i < count; i++) {
if (!JS_GetElement(cx, obj, i, &v)) {
lprintf(LOG_WARNING, "Unable to get element %d from interface array", i);
continue;
}
JSVALUE_TO_MSTRING(cx, v, interface, NULL);
HANDLE_PENDING(cx, interface);
rc = JS_SUSPENDREQUEST(cx);
if (!xpms_add(set, domain, type, proto, interface, port, protocol, ls_cb, NULL, &cb)) {
free(interface);
JS_RESUMEREQUEST(cx, rc);
JS_ReportError(cx, "Unable to add host to socket set");
goto fail;
}
free(interface);
JS_RESUMEREQUEST(cx, rc);
}
}
obj=JS_NewObject(cx, &js_socket_class, NULL, NULL);
JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(obj));
if((p=(js_socket_private_t*)malloc(sizeof(js_socket_private_t)))==NULL) {
JS_ReportError(cx,"malloc failed");
if(protocol)
free(protocol);
return(JS_FALSE);
}
memset(p,0,sizeof(js_socket_private_t));
p->type = type;
p->set = set;
p->sock = INVALID_SOCKET;
p->external = TRUE;
p->network_byte_order = TRUE;
p->session=-1;
p->unflushed = 0;
if(!JS_SetPrivate(cx, obj, p)) {
JS_ReportError(cx,"JS_SetPrivate failed");
free(p);
return(JS_FALSE);
}
if(!js_DefineSocketOptionsArray(cx, obj, type))
return(JS_FALSE);
#ifdef BUILD_JSDOCS
js_DescribeSyncObject(cx,obj,"Class used for incoming TCP/IP socket communications",317);
js_DescribeSyncConstructor(cx,obj,"To create a new ListeningSocket object: "
"<tt>load('sockdefs.js'); var s = new ListeningSocket(<i>interface</i>, <i>port</i> ,<i>protocol</i>, {domain:<i>domain</i>, type:<i>type</i>, proto:<i>proto</i>, retry_count:<i>retry_count</i>, retry_delay:<i>retry_delay</i>)</tt><br>"
"where <i>interface</i> = A array or strings or a single string of hostnames or address optionally including a :port suffix<br>"
"<i>port</i> = a port to use when the interface doesn't specify one<br>"
"<i>protocol</i> = protocol name, used for socket options and logging.<br>"
"<i>domain</i> (optional) = <tt>PF_UNSPEC</tt> (default) for IPv4 or IPv6, <tt>PF_INET</tt> for IPv4, or <tt>PF_INET6</tt> for IPv6<br>"
"<i>proto</i> (optional) = <tt>IPPROTO_IP</tt> (default)<br>"
"<i>type</i> (optional) = <tt>SOCK_STREAM</tt> for TCP (default) or <tt>SOCK_DGRAM</tt> for UDP<br>"
"<i>retry_count</i> (optional) = <tt>0</tt> (default) number of times to retry binding<br>"
"and <i>retry_delay</i> (optional) = <tt>15</tt> (default) seconds to wait before a retry<br>"
);
JS_DefineProperty(cx,obj,"_dont_document",JSVAL_TRUE,NULL,NULL,JSPROP_READONLY);
#endif
dbprintf(FALSE, p, "object constructed");
return(JS_TRUE);
fail:
if (protocol)
free(protocol);
return JS_FALSE;
}
static JSBool
js_socket_constructor(JSContext *cx, uintN argc, jsval *arglist)
{
......@@ -2371,6 +2801,9 @@ js_socket_constructor(JSContext *cx, uintN argc, jsval *arglist)
JSObject* DLLCALL js_CreateSocketClass(JSContext* cx, JSObject* parent)
{
JSObject* sockobj;
JSObject* sockproto;
JSObject* csockobj;
JSObject* lsockobj;
sockobj = JS_InitClass(cx, parent, NULL
,&js_socket_class
......@@ -2379,6 +2812,23 @@ JSObject* DLLCALL js_CreateSocketClass(JSContext* cx, JSObject* parent)
,NULL /* props, specified in constructor */
,NULL /* funcs, specified in constructor */
,NULL,NULL);
if (sockobj == NULL)
return sockobj;
sockproto = JS_GetPrototype(cx, sockobj);
csockobj = JS_InitClass(cx, parent, sockproto
,&js_connected_socket_class
,js_connected_socket_constructor
,2 /* number of constructor args */
,NULL /* props, specified in constructor */
,NULL /* funcs, specified in constructor */
,NULL,NULL);
lsockobj = JS_InitClass(cx, parent, sockproto
,&js_listening_socket_class
,js_listening_socket_constructor
,2 /* number of constructor args */
,NULL /* props, specified in constructor */
,NULL /* funcs, specified in constructor */
,NULL,NULL);
return(sockobj);
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment