Skip to content
Snippets Groups Projects
js_socket.c 41.3 KiB
Newer Older
	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
		JS_ReportError(cx,getprivate_failure,WHERE);
deuce's avatar
deuce committed
	if((opt = getSocketOptionByName(JS_GetStringBytes(JS_ValueToString(cx,argv[0])),&level)) == -1) {
	if(opt == SO_LINGER) {
		vp=&linger;
		len=sizeof(linger);
	}
	if(getsockopt(p->sock, level, opt, vp, &len)==0) {
		if(opt == SO_LINGER) {
			if(linger.l_onoff==TRUE)
				val = linger.l_linger;
			else
				val = 0;
		}
		JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(val));
		dbprintf(TRUE, p, "error %d getting option %d"
js_setsockopt(JSContext *cx, uintN argc, jsval *arglist)
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
	LINGER		linger;
	void*		vp=&val;
	socklen_t	len=sizeof(val);
deuce's avatar
deuce committed
	jsrefcount	rc;
	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
		JS_ReportError(cx,getprivate_failure,WHERE);
	opt = getSocketOptionByName(JS_GetStringBytes(JS_ValueToString(cx,argv[0])),&level);
deuce's avatar
deuce committed
	if(argv[1]!=JSVAL_VOID) {
	if(opt == SO_LINGER) {
		if(val) {
			linger.l_onoff = TRUE;
			linger.l_linger = (ushort)val;
		} else {
			ZERO_VAR(linger);
		}
		vp=&linger;
		len=sizeof(linger);
	}

	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(
		setsockopt(p->sock, level, opt, vp, len)==0));
js_ioctlsocket(JSContext *cx, uintN argc, jsval *arglist)
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
rswindell's avatar
rswindell committed
	int32		cmd=0;
deuce's avatar
deuce committed
	jsrefcount	rc;
	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
		JS_ReportError(cx,getprivate_failure,WHERE);
		return(JS_FALSE);
	if(argv[0]!=JSVAL_VOID)
		JS_ValueToInt32(cx,argv[0],&cmd);
	if(argc>1 && argv[1]!=JSVAL_VOID)
		JS_ValueToInt32(cx,argv[1],&arg);
deuce's avatar
deuce committed
	if(ioctlsocket(p->sock,cmd,(ulong*)&arg)==0) {
		JS_SET_RVAL(cx, arglist,INT_TO_JSVAL(arg));
		JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(-1));

	p->last_error=ERROR_VALUE;

	return(JS_TRUE);
}

js_poll(JSContext *cx, uintN argc, jsval *arglist)
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
	fd_set*	rd_set=NULL;
	fd_set*	wr_set=NULL;
	uintN	argn;
deuce's avatar
deuce committed
	jsrefcount	rc;
	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
		JS_ReportError(cx,getprivate_failure,WHERE);
	if(p->sock==INVALID_SOCKET) {
		dbprintf(TRUE, p, "INVALID SOCKET in call to poll");
		JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(-1));
	for(argn=0;argn<argc;argn++) {
		if(JSVAL_IS_BOOLEAN(argv[argn]))
			poll_for_write=JSVAL_TO_BOOLEAN(argv[argn]);
		else if(JSVAL_IS_NUMBER(argv[argn]))
			js_timeval(cx,argv[argn],&tv);
	FD_ZERO(&socket_set);
	FD_SET(p->sock,&socket_set);
	if(poll_for_write)
		wr_set=&socket_set;
	else
		rd_set=&socket_set;
	result = select(p->sock+1,rd_set,wr_set,NULL,&tv);
	dbprintf(FALSE, p, "poll: select returned %d (errno %d)"
		,result,p->last_error);

	JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(result));
rswindell's avatar
rswindell committed
/* Socket Object Properites */
enum {
	 SOCK_PROP_LAST_ERROR
	,SOCK_PROP_IS_CONNECTED
	,SOCK_PROP_DATA_WAITING
	,SOCK_PROP_NREAD
	,SOCK_PROP_DEBUG
	,SOCK_PROP_DESCRIPTOR
	,SOCK_PROP_NONBLOCKING
	,SOCK_PROP_LOCAL_IP
	,SOCK_PROP_LOCAL_PORT
	,SOCK_PROP_REMOTE_IP
	,SOCK_PROP_REMOTE_PORT
static char* socket_prop_desc[] = {
	 "error status for the last socket operation that failed - <small>READ ONLY</small>"
	,"<i>true</i> if socket is in a connected state - <small>READ ONLY</small>"
	,"<i>true</i> if socket can accept written data - Setting to false will shutdown the write end of the socket."
	,"<i>true</i> if data is waiting to be read from socket - <small>READ ONLY</small>"
	,"number of bytes waiting to be read - <small>READ ONLY</small>"
	,"enable debug logging"
rswindell's avatar
rswindell committed
	,"socket descriptor (advanced uses only)"
	,"use non-blocking operation (default is <i>false</i>)"
	,"local IP address (string in dotted-decimal format)"
	,"local TCP or UDP port number"
	,"remote IP address (string in dotted-decimal format)"
	,"remote TCP or UDP port number"
rswindell's avatar
rswindell committed
	,"socket type, <tt>SOCK_STREAM</tt> (TCP) or <tt>SOCK_DGRAM</tt> (UDP)"
	,"<i>true</i> if binary data is to be sent in Network Byte Order (big end first), default is <i>true</i>"
rswindell's avatar
rswindell committed
	/* statically-defined properties: */
	,"array of socket option names supported by the current platform"
	,NULL
rswindell's avatar
rswindell committed
};
#endif
rswindell's avatar
rswindell committed

static JSBool js_socket_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
rswindell's avatar
rswindell committed
{
deuce's avatar
deuce committed
	jsrefcount	rc;
	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
		// Prototype access
		return(JS_TRUE);
    JS_IdToValue(cx, id, &idval);
    tiny = JSVAL_TO_INT(idval);
	dbprintf(FALSE, p, "setting property %d",tiny);

	switch(tiny) {
		case SOCK_PROP_DEBUG:
			JS_ValueToBoolean(cx,*vp,&(p->debug));
		case SOCK_PROP_DESCRIPTOR:
			JS_ValueToInt32(cx,*vp,(int32*)&(p->sock));
			JS_ValueToInt32(cx,*vp,(int32*)&(p->last_error));
		case SOCK_PROP_NONBLOCKING:
			JS_ValueToBoolean(cx,*vp,&(p->nonblocking));
rswindell's avatar
rswindell committed
			ioctlsocket(p->sock,FIONBIO,(ulong*)&(p->nonblocking));
		case SOCK_PROP_NETWORK_ORDER:
			JS_ValueToBoolean(cx,*vp,&(p->network_byte_order));
			break;
		case SOCK_PROP_IS_WRITEABLE:
			JS_ValueToBoolean(cx,*vp,&b);
			if(!b)
				shutdown(p->sock,SHUT_WR);
			break;
rswindell's avatar
rswindell committed
	return(JS_TRUE);
}

static JSBool js_socket_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
rswindell's avatar
rswindell committed
{
rswindell's avatar
rswindell committed
    jsint       tiny;
	JSString*	js_str;
	SOCKADDR_IN	addr;
	socklen_t	len=sizeof(addr);
deuce's avatar
deuce committed
	jsrefcount	rc;
rswindell's avatar
rswindell committed

	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
		// Protoype access
		return(JS_TRUE);
rswindell's avatar
rswindell committed

    JS_IdToValue(cx, id, &idval);
    tiny = JSVAL_TO_INT(idval);
rswindell's avatar
rswindell committed

#if 0 /* just too much */
rswindell's avatar
rswindell committed
	dbprintf(FALSE, p, "getting property %d",tiny);
rswindell's avatar
rswindell committed
	switch(tiny) {
		case SOCK_PROP_LAST_ERROR:
rswindell's avatar
rswindell committed
			break;
		case SOCK_PROP_IS_CONNECTED:
				*vp = BOOLEAN_TO_JSVAL(socket_check(p->sock,NULL,NULL,0));
rswindell's avatar
rswindell committed
			break;
		case SOCK_PROP_IS_WRITEABLE:
			socket_check(p->sock,NULL,&wr,0);
			*vp = BOOLEAN_TO_JSVAL(wr);
			break;
		case SOCK_PROP_DATA_WAITING:
			*vp = BOOLEAN_TO_JSVAL(rd);
			break;
		case SOCK_PROP_NREAD:
			cnt=0;
deuce's avatar
deuce committed
			if(ioctlsocket(p->sock, FIONREAD, &cnt)==0) {
				*vp=DOUBLE_TO_JSVAL((double)cnt);
				*vp = JSVAL_ZERO;
		case SOCK_PROP_DEBUG:
		case SOCK_PROP_DESCRIPTOR:
		case SOCK_PROP_NONBLOCKING:
			*vp = BOOLEAN_TO_JSVAL(p->nonblocking);
			break;
			if(p->sock != INVALID_SOCKET) {
				if(getsockname(p->sock, (struct sockaddr *)&addr,&len)!=0)
					return(JS_FALSE);
				if((js_str=JS_NewStringCopyZ(cx,inet_ntoa(addr.sin_addr)))==NULL)
					return(JS_FALSE);
				*vp = STRING_TO_JSVAL(js_str);
			if(p->sock != INVALID_SOCKET) {
				if(getsockname(p->sock, (struct sockaddr *)&addr,&len)!=0)
					return(JS_FALSE);
				if((js_str=JS_NewStringCopyZ(cx,inet_ntoa(addr.sin_addr)))==NULL)
					return(JS_FALSE);

				*vp = INT_TO_JSVAL(ntohs(addr.sin_port));
			break;
		case SOCK_PROP_REMOTE_IP:
				if((js_str=JS_NewStringCopyZ(cx,inet_ntoa(p->remote_addr.sin_addr)))==NULL)
					return(JS_FALSE);
				*vp = STRING_TO_JSVAL(js_str);
			break;
		case SOCK_PROP_REMOTE_PORT:
				*vp = INT_TO_JSVAL(ntohs(p->remote_addr.sin_port));
			else
				*vp=JSVAL_VOID;
		case SOCK_PROP_TYPE:
			*vp = INT_TO_JSVAL(p->type);
			break;
		case SOCK_PROP_NETWORK_ORDER:
			*vp = BOOLEAN_TO_JSVAL(p->network_byte_order);
rswindell's avatar
rswindell committed
	return(TRUE);
}

#define SOCK_PROP_FLAGS JSPROP_ENUMERATE|JSPROP_READONLY
rswindell's avatar
rswindell committed

static jsSyncPropertySpec js_socket_properties[] = {
/*		 name				,tinyid					,flags,				ver	*/

	{	"error"				,SOCK_PROP_LAST_ERROR	,SOCK_PROP_FLAGS,	311 },
	{	"last_error"		,SOCK_PROP_LAST_ERROR	,JSPROP_READONLY,	310 },	/* alias */
	{	"is_connected"		,SOCK_PROP_IS_CONNECTED	,SOCK_PROP_FLAGS,	310 },
	{	"is_writeable"		,SOCK_PROP_IS_WRITEABLE	,JSPROP_ENUMERATE,	311 },
	{	"is_writable"		,SOCK_PROP_IS_WRITEABLE	,JSPROP_ENUMERATE,	312 },	/* alias */
	{	"data_waiting"		,SOCK_PROP_DATA_WAITING	,SOCK_PROP_FLAGS,	310 },
	{	"nread"				,SOCK_PROP_NREAD		,SOCK_PROP_FLAGS,	310 },
	{	"debug"				,SOCK_PROP_DEBUG		,JSPROP_ENUMERATE,	310 },
	{	"descriptor"		,SOCK_PROP_DESCRIPTOR	,JSPROP_ENUMERATE,	310 },
	{	"nonblocking"		,SOCK_PROP_NONBLOCKING	,JSPROP_ENUMERATE,	310 },
	{	"local_ip_address"	,SOCK_PROP_LOCAL_IP		,SOCK_PROP_FLAGS,	310 },
	{	"local_port"		,SOCK_PROP_LOCAL_PORT	,SOCK_PROP_FLAGS,	310 },
	{	"remote_ip_address"	,SOCK_PROP_REMOTE_IP	,SOCK_PROP_FLAGS,	310 },
	{	"remote_port"		,SOCK_PROP_REMOTE_PORT	,SOCK_PROP_FLAGS,	310 },
	{	"type"				,SOCK_PROP_TYPE			,SOCK_PROP_FLAGS,	310 },
	{	"network_byte_order",SOCK_PROP_NETWORK_ORDER,JSPROP_ENUMERATE,	311 },
static jsSyncMethodSpec js_socket_functions[] = {
	{"close",		js_close,		0,	JSTYPE_VOID,	""
	,JSDOCSTR("close (shutdown) the socket immediately")
	{"bind",		js_bind,		0,	JSTYPE_BOOLEAN,	JSDOCSTR("[port] [,ip_address]")
	,JSDOCSTR("bind socket to a TCP or UDP <i>port</i> (number or service name), "
		"optionally specifying a network interface (via <i>ip_address</i>)")
	,311
	{"connect",     js_connect,     2,	JSTYPE_BOOLEAN,	JSDOCSTR("host, port [,timeout=<tt>10.0</tt>]")
	,JSDOCSTR("connect to a remote port (number or service name) on the specified host (IP address or host name)"
	", default <i>timeout</i> value is <i>10.0</i> (seconds)")
	{"listen",		js_listen,		0,	JSTYPE_BOOLEAN,	JSDOCSTR("")
	,JSDOCSTR("place socket in a state to listen for incoming connections (use before an accept)")
	{"accept",		js_accept,		0,	JSTYPE_OBJECT,	JSDOCSTR("")					
	,JSDOCSTR("accept an incoming connection, returns a new <i>Socket</i> object representing the new connection")
	{"write",		js_send,		1,	JSTYPE_ALIAS },
	{"send",		js_send,		1,	JSTYPE_BOOLEAN,	JSDOCSTR("data")
	,JSDOCSTR("send a string (AKA write)")
	{"sendto",		js_sendto,		3,	JSTYPE_BOOLEAN,	JSDOCSTR("data, address, port")
	,JSDOCSTR("send data to a specific host (IP address or host name) and port (number or service name), for UDP sockets")
	{"sendfile",	js_sendfile,	1,	JSTYPE_BOOLEAN,	JSDOCSTR("path/filename")
	,JSDOCSTR("send an entire file over the socket")
	{"writeBin",	js_sendbin,		1,	JSTYPE_ALIAS },
	{"sendBin",		js_sendbin,		1,	JSTYPE_BOOLEAN,	JSDOCSTR("value [,bytes=<tt>4</tt>]")
	,JSDOCSTR("send a binary integer over the socket, default number of bytes is 4 (32-bits)")
	{"read",		js_recv,		1,	JSTYPE_ALIAS },
	{"recv",		js_recv,		1,	JSTYPE_STRING,	JSDOCSTR("[maxlen=<tt>512</tt>]")
	,JSDOCSTR("receive a string, default maxlen is 512 characters (AKA read)")
	{"peek",		js_peek,		0,	JSTYPE_STRING,	JSDOCSTR("[maxlen=<tt>512</tt>]")
	,JSDOCSTR("receive a string, default maxlen is 512 characters, leaves string in receive buffer")
	{"readline",	js_recvline,	0,	JSTYPE_ALIAS },
	{"readln",		js_recvline,	0,	JSTYPE_ALIAS },
	{"recvline",	js_recvline,	0,	JSTYPE_STRING,	JSDOCSTR("[maxlen=<tt>512</tt>] [,timeout=<tt>30.0</tt>]")
	,JSDOCSTR("receive a line-feed terminated string, default maxlen is 512 characters, default timeout is 30 seconds (AKA readline and readln)")
	{"recvfrom",	js_recvfrom,	0,	JSTYPE_OBJECT,	JSDOCSTR("[binary=<tt>false</tt>] [,maxlen=<tt>512</tt> or int_size=<tt>4</tt>]")
	,JSDOCSTR("receive data (string or integer) from a socket (typically UDP)"
rswindell's avatar
rswindell committed
	"<p>returns object with <i>ip_address</i> and <i>port</i> of sender along with <i>data</i> properties"
	"<p><i>binary</i> defaults to <i>false</i>, <i>maxlen</i> defaults to 512 chars, <i>int_size</i> defaults to 4 bytes (32-bits)")
	},
	{"readBin",		js_recvbin,		0,	JSTYPE_ALIAS },
	{"recvBin",		js_recvbin,		0,	JSTYPE_NUMBER,	JSDOCSTR("[bytes=<tt>4</tt>]")
	,JSDOCSTR("receive a binary integer from the socket, default number of bytes is 4 (32-bits)")
	{"getoption",	js_getsockopt,	1,	JSTYPE_NUMBER,	JSDOCSTR("option")
	,JSDOCSTR("get socket option value, option may be socket option name "
	"(see <tt>sockopts</tt> in <tt>sockdefs.js</tt>) or number")
	{"setoption",	js_setsockopt,	2,	JSTYPE_BOOLEAN,	JSDOCSTR("option, value")
	,JSDOCSTR("set socket option value, option may be socket option name "
	"(see <tt>sockopts</tt> in <tt>sockdefs.js</tt>) or number")
	{"ioctl",		js_ioctlsocket,	1,	JSTYPE_NUMBER,	JSDOCSTR("command [,argument=<tt>0</tt>]")
	,JSDOCSTR("send socket IOCTL (advanced)")					
	{"poll",		js_poll,		1,	JSTYPE_NUMBER,	JSDOCSTR("[timeout=<tt>0</tt>] [,write=<tt>false</tt>]")
	,JSDOCSTR("poll socket for read or write ability (default is <i>read</i>), "
	"default timeout value is 0.0 seconds (immediate timeout)")
static JSBool js_socket_resolve(JSContext *cx, JSObject *obj, jsid id)
{
	char*			name=NULL;

deuce's avatar
deuce committed
	if(id != JSID_VOID && id != JSID_EMPTY) {
		jsval idval;
		
		JS_IdToValue(cx, id, &idval);
		name=JS_GetStringBytes(JSVAL_TO_STRING(idval));
	}

	return(js_SyncResolve(cx, obj, name, js_socket_properties, js_socket_functions, NULL, 0));
}

static JSBool js_socket_enumerate(JSContext *cx, JSObject *obj)
{
deuce's avatar
deuce committed
	return(js_socket_resolve(cx, obj, JSID_VOID));
}

static JSClass js_socket_class = {
     "Socket"				/* 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;
	size_t		count=0;
	jsval		val;
	JSObject*	array;
	socket_option_t* options;

	if((options=getSocketOptionList())==NULL)
		return(FALSE);

	if((array=JS_NewArrayObject(cx, 0, NULL))==NULL) 
		return(FALSE);

	if(!JS_DefineProperty(cx, obj, "option_list", OBJECT_TO_JSVAL(array)
		, NULL, NULL, JSPROP_ENUMERATE))
		return(FALSE);

	for(i=0; options[i].name!=NULL; i++) {
		if(options[i].type && options[i].type!=type)
			continue;
		val=STRING_TO_JSVAL(JS_NewStringCopyZ(cx,options[i].name));
		JS_SetElement(cx, array, count++, &val);
	}
	return(TRUE);
}

/* Socket Constructor (creates socket descriptor) */

static JSBool
js_socket_constructor(JSContext *cx, uintN argc, jsval *arglist)
	JSObject *obj;
	jsval *argv=JS_ARGV(cx, arglist);
	int32	type=SOCK_STREAM;	/* default = TCP */
	obj=JS_NewObject(cx, &js_socket_class, NULL, NULL);
	JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(obj));

	for(i=0;i<argc;i++) {
		if(JSVAL_IS_NUMBER(argv[i]))
			JS_ValueToInt32(cx,argv[i],&type);
		else if(protocol==NULL)
			protocol=JS_GetStringBytes(JS_ValueToString(cx,argv[i]));
	if((p=(private_t*)malloc(sizeof(private_t)))==NULL) {
		JS_ReportError(cx,"malloc failed");
	if((p->sock=open_socket(type,protocol))==INVALID_SOCKET) {
		JS_ReportError(cx,"open_socket failed with error %d",ERROR_VALUE);
	p->network_byte_order = TRUE;
		JS_ReportError(cx,"JS_SetPrivate failed");
	if(!js_DefineSocketOptionsArray(cx, obj, type))
		return(JS_FALSE);

	js_DescribeSyncObject(cx,obj,"Class used for TCP/IP socket communications",310);
	js_DescribeSyncConstructor(cx,obj,"To create a new Socket object: "
rswindell's avatar
rswindell committed
		"<tt>load('sockdefs.js'); var s = new Socket(<i>type</i>, <i>protocol</i>)</tt><br>"
		"where <i>type</i> = <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"
		);
	js_CreateArrayOfStrings(cx, obj, "_property_desc_list", socket_prop_desc, JSPROP_READONLY);
#endif

	dbprintf(FALSE, p, "object constructed");
	return(JS_TRUE);
}

rswindell's avatar
rswindell committed
JSObject* DLLCALL js_CreateSocketClass(JSContext* cx, JSObject* parent)
{
	JSObject*	sockobj;

	sockobj = JS_InitClass(cx, parent, NULL
		,&js_socket_class
		,js_socket_constructor
		,0	/* number of constructor args */
		,NULL /* props, specified in constructor */
		,NULL /* funcs, specified in constructor */
rswindell's avatar
rswindell committed
		,NULL,NULL);

	return(sockobj);
}

rswindell's avatar
rswindell committed
JSObject* DLLCALL js_CreateSocketObject(JSContext* cx, JSObject* parent, char *name, SOCKET sock)
{
	obj = JS_DefineObject(cx, parent, name, &js_socket_class, NULL
		,JSPROP_ENUMERATE|JSPROP_READONLY);
rswindell's avatar
rswindell committed

	if(obj==NULL)
		return(NULL);

	len = sizeof(type);
	getsockopt(sock,SOL_SOCKET,SO_TYPE,(void*)&type,&len);

	if(!js_DefineSocketOptionsArray(cx, obj, type))
		return(NULL);

	if((p=(private_t*)malloc(sizeof(private_t)))==NULL)
		return(NULL);
	memset(p,0,sizeof(private_t));
	p->network_byte_order = TRUE;
	if(getpeername(p->sock, (struct sockaddr *)&p->remote_addr,&len)==0)
		p->is_connected=TRUE;
	if(!JS_SetPrivate(cx, obj, p)) {
		dbprintf(TRUE, p, "JS_SetPrivate failed");
rswindell's avatar
rswindell committed
		return(NULL);
	}

	dbprintf(FALSE, p, "object created");
#endif	/* JAVSCRIPT */