/* Synchronet JavaScript "Socket" Object */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
 *																			*
 * This program is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU General Public License				*
 * as published by the Free Software Foundation; either version 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include <cryptlib.h>

#include "sbbs.h"
#include "js_cryptcert.h"
#include "js_socket.h"
#include "js_request.h"
#include "multisock.h"
#include "ssl.h"

#define TLS_ERROR_LEVEL LOG_WARNING // It'd be nice if this was configurable

// TODO: All log output (lprintf calls) go to the terminal server's lprintf (!)

#ifdef JAVASCRIPT

static bool do_CryptFlush(js_socket_private_t *p);
static int do_cryptAttribute(const CRYPT_CONTEXT session, CRYPT_ATTRIBUTE_TYPE attr, int val);
static int do_cryptAttributeString(const CRYPT_CONTEXT session, CRYPT_ATTRIBUTE_TYPE attr, void *val, int len);
static void do_js_close(JSContext *cx, js_socket_private_t *p, bool finalize);
static bool js_DefineSocketOptionsArray(JSContext *cx, JSObject *obj, int type);
static JSBool js_accept(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_bind(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_close(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_connect(JSContext *cx, uintN argc, jsval *arglist);
static void js_finalize_socket(JSContext *cx, JSObject *obj);
static JSBool js_ioctlsocket(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_listen(JSContext *cx, uintN argc, jsval *arglist);
static js_callback_t * js_get_callback(JSContext *cx);
static JSBool js_getsockopt(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_peek(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_poll(JSContext *cx, uintN argc, jsval *arglist);
static ushort js_port(JSContext* cx, jsval val, int type);
static JSBool js_recv(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_recvbin(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_recvfrom(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_recvline(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_send(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_sendbin(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_sendfile(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_sendline(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_sendto(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_setsockopt(JSContext *cx, uintN argc, jsval *arglist);
static int js_sock_read_check(js_socket_private_t *p, time_t start, int32 timeout, int i);
static JSBool js_socket_constructor(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_socket_enumerate(JSContext *cx, JSObject *obj);
static bool js_socket_peek_byte(JSContext *cx, js_socket_private_t *p);
static JSBool js_socket_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp);
static ptrdiff_t js_socket_recv(JSContext *cx, js_socket_private_t *p, void *buf, size_t len, int flags, int timeout);
static JSBool js_socket_resolve(JSContext *cx, JSObject *obj, jsid id);
static off_t js_socket_sendfilesocket(js_socket_private_t *p, int file);
static ptrdiff_t js_socket_sendsocket(js_socket_private_t *p, const void *msg, size_t len, int flush);
static JSBool js_socket_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp);
static JSBool js_install_event(JSContext *cx, uintN argc, jsval *arglist, bool once);
static JSBool js_on(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_once(JSContext *cx, uintN argc, jsval *arglist);

static void store_socket_error(js_socket_private_t* p, int error_num, const char* error_str)
{
	p->last_error = error_num;
	if (error_str != NULL)
		strlcpy(p->last_error_str, error_str, sizeof p->last_error_str);
	else
		socket_strerror(error_num, p->last_error_str, sizeof p->last_error_str);
}

static int do_cryptAttribute(const CRYPT_CONTEXT session, CRYPT_ATTRIBUTE_TYPE attr, int val)
{
	int   ret;
	int   level;
	char *estr;
	char  action[32];

	/* Force "sane" values (requirements) */
	switch (attr) {
		case CRYPT_OPTION_NET_READTIMEOUT:
			if (val < 0)
				val = 0;
			if (val > 300)
				val = 300;
			break;
		default:
			break;
	}

	ret = cryptSetAttribute(session, attr, val);
	if (ret != CRYPT_OK) {
		sprintf(action, "setting attribute %d", attr);
		get_crypt_error_string(ret, session, &estr, action, &level);
		if (estr) {
			if (level < TLS_ERROR_LEVEL)
				level = TLS_ERROR_LEVEL;
			lprintf(level, "TLS %s", estr);
			free_crypt_attrstr(estr);
		}
	}
	return ret;
}

static int do_cryptAttributeString(const CRYPT_CONTEXT session, CRYPT_ATTRIBUTE_TYPE attr, void *val, int len)
{
	int   level;
	char *estr;
	char  action[48];

	int   ret = cryptSetAttributeString(session, attr, val, len);
	if (ret != CRYPT_OK) {
		sprintf(action, "setting attribute string %d", attr);
		get_crypt_error_string(ret, session, &estr, "setting attribute string", &level);
		if (estr) {
			if (level < TLS_ERROR_LEVEL)
				level = TLS_ERROR_LEVEL;
			lprintf(level, "TLS %s", estr);
			free_crypt_attrstr(estr);
		}
	}
	return ret;
}

#define GCES(status, pdata, estr, action) do {                                               \
			int GCES_level;                                                                      \
			get_crypt_error_string(status, pdata->session, &estr, action, &GCES_level); \
			if (estr) {                                                                          \
				if (GCES_level < TLS_ERROR_LEVEL)                                                \
				GCES_level = TLS_ERROR_LEVEL;                                                \
				lprintf(GCES_level, "%04d TLS %s", p->sock, estr);                               \
				free_crypt_attrstr(estr);                                                                  \
			}                                                                                    \
} while (0)

#define GCESH(status, socket, handle, estr, action) do {                                     \
			int GCESH_level;                                                                     \
			get_crypt_error_string(status, handle, &estr, action, &GCESH_level);                 \
			if (estr) {                                                                          \
				if (GCESH_level < TLS_ERROR_LEVEL)                                               \
				GCESH_level = TLS_ERROR_LEVEL;                                               \
				lprintf(GCESH_level, "%04d TLS %s", socket, estr);                               \
				free_crypt_attrstr(estr);                                                                  \
			}                                                                                    \
} while (0)

static bool do_CryptFlush(js_socket_private_t *p)
{
	int   ret;
	char *estr;

	if (p->unflushed) {
		ret = cryptFlushData(p->session);

		if (ret == CRYPT_OK) {
			p->unflushed = 0;
			return true;
		}
		if (ret != CRYPT_ERROR_COMPLETE)
			GCES(ret, p, estr, "flushing data");
		return false;
	}
	return true;
}

static void
remove_js_socket_event(JSContext *cx, js_callback_t *cb, SOCKET sock)
{
	struct js_event_list *ev;
	struct js_event_list *nev;

	if (!cb->events_supported) {
		return;
	}

	for (ev = cb->events; ev; ev = nev) {
		nev = ev->next;
		if (ev->type == JS_EVENT_SOCKET_READABLE || ev->type == JS_EVENT_SOCKET_READABLE_ONCE
		    || ev->type == JS_EVENT_SOCKET_WRITABLE || ev->type == JS_EVENT_SOCKET_WRITABLE_ONCE) {
			if (ev->data.sock == sock) {
				if (ev->next)
					ev->next->prev = ev->prev;
				if (ev->prev)
					ev->prev->next = ev->next;
				else
					cb->events = ev->next;
				JS_RemoveObjectRoot(cx, &ev->cx);
				free(ev);
			}
		}
		else if (ev->type == JS_EVENT_SOCKET_CONNECT) {
			if (ev->data.connect.sock == sock) {
				if (ev->next)
					ev->next->prev = ev->prev;
				if (ev->prev)
					ev->prev->next = ev->next;
				else
					cb->events = ev->next;
				closesocket(ev->data.connect.sv[0]);
				JS_RemoveObjectRoot(cx, &ev->cx);
				free(ev);
			}
		}
	}
}

static void do_js_close(JSContext *cx, js_socket_private_t *p, bool finalize)
{
	size_t i;

	if (p->session != -1) {
		if (p->tls_server || p->tls_clientauth)
			destroy_session(lprintf, p->session);
		else
			cryptDestroySession(p->session);
		p->session = -1;
	}

	// Delete any event handlers for the socket
	if (p->js_cb) {
		if (p->set) {
			for (i = 0; i < p->set->sock_count; i++) {
				if (p->set->socks[i].sock != INVALID_SOCKET)
					remove_js_socket_event(cx, p->js_cb, p->set->socks[i].sock);
			}
		}
		else {
			if (p->sock != INVALID_SOCKET)
				remove_js_socket_event(cx, p->js_cb, p->sock);
		}
	}

	if (p->sock == INVALID_SOCKET) {
		p->is_connected = false;
		return;
	}
	if (p->external == false) {
		close_socket(p->sock);
		store_socket_error(p, SOCKET_ERRNO, NULL);
	}
	else {
		if (!finalize)
			shutdown(p->sock, SHUT_RDWR);
	}

	// This is a lie for external sockets... don't tell anyone.
	p->sock = INVALID_SOCKET;
	p->is_connected = false;
}

static bool js_socket_peek_byte(JSContext *cx, js_socket_private_t *p)
{
	if (do_cryptAttribute(p->session, CRYPT_OPTION_NET_READTIMEOUT, 0) != CRYPT_OK)
		return false;
	if (p->peeked)
		return true;
	if (js_socket_recv(cx, p, &p->peeked_byte, 1, 0, 0) == 1) {
		p->peeked = true;
		return true;
	}
	return false;
}

/* Returns > 0 upon successful data received (even if there was an error or disconnection) */
/* Returns -1 upon error (and no data received) */
/* Returns 0 upon timeout or disconnection (and no data received) */
static ptrdiff_t js_socket_recv(JSContext *cx, js_socket_private_t *p, void *buf, size_t len, int flags, int timeout)
{
	ptrdiff_t total = 0;
	int       copied, ret;
	char *    estr;
	time_t    now = time(NULL);
	int       status;

	if (len == 0)
		return total;
	if (p->session != -1) {
		if (flags & MSG_PEEK)
			js_socket_peek_byte(cx, p);
		if (p->peeked) {
			*(uint8_t *)buf = p->peeked_byte;
			buf = ((uint8_t *)buf) + 1;
			if (!(flags & MSG_PEEK))
				p->peeked = false;
			total++;
			len--;
			if (len == 0)
				return total;
		}
		if (flags & MSG_PEEK)
			return total;
		if (do_cryptAttribute(p->session, CRYPT_OPTION_NET_READTIMEOUT, p->nonblocking?0:timeout) != CRYPT_OK)
			return -1;
	}
	do {
		if (p->session == -1) {
			if (p->sock == INVALID_SOCKET)
				ret = -1;
			else {
				ret = 0;
				if (socket_readable(p->sock, timeout * 1000))
					ret = recv(p->sock, static_cast<char *>(buf), len, flags);
			}
		}
		else {
			status = cryptPopData(p->session, buf, len, &copied);
			if (cryptStatusOK(status))
				ret = copied;
			else {
				ret = -1;
				if (status == CRYPT_ERROR_TIMEOUT)
					ret = 0;
				else if (status != CRYPT_ERROR_COMPLETE) {
					GCES(ret, p, estr, "popping data");
					do_js_close(cx, p, false);
				}
			}
		}
		if (ret == -1) {
			if (total > 0)
				return total;
			return ret;
		}
		total += ret;
		if ((!(flags & MSG_WAITALL)) || p->nonblocking)
			return total;
		if (ret >= (ptrdiff_t)len)
			return total;
		len -= ret;
		buf = ((uint8_t *)buf) + ret;

		if (!socket_check(p->sock, NULL, NULL, 0)) {
			if (total > 0)
				return total;
			return -1;
		}
		if (now + timeout > time(NULL))
			return total;
	} while (len);
	return total;
}

static ptrdiff_t js_socket_sendsocket(js_socket_private_t *p, const void *msg, size_t len, int flush)
{
	ptrdiff_t total = 0;
	int       copied = 0, ret;
	char *    estr;

	if (p->session == -1)
		return sendsocket(p->sock, static_cast<const char *>(msg), len);
	do {
		// If we don't limit this, we occasionally get errors on large sends...
		if ((ret = cryptPushData(p->session, msg, len > 0x2000 ? 0x2000 : len, &copied)) == CRYPT_OK) {
			p->unflushed += copied;
			if (flush)
				do_CryptFlush(p);
			if (p->nonblocking)
				return copied;
			total += copied;
			if (copied >= (ptrdiff_t)len)
				return total;
			do_CryptFlush(p);
			len -= copied;
			msg = ((uint8_t *)msg) + copied;
		}
		else {
			if (ret != CRYPT_ERROR_COMPLETE)
				GCES(ret, p, estr, "pushing data");
			if (flush)
				do_CryptFlush(p);
			return total;
		}
		if (!socket_check(p->sock, NULL, NULL, 0))
			break;
	} while (len);
	if (flush)
		do_CryptFlush(p);
	return total;
}

static off_t js_socket_sendfilesocket(js_socket_private_t *p, int file)
{
	char  buf[1024 * 16];
	off_t total = 0;

	for (;;) {
		ssize_t rd = read(file, buf, sizeof(buf));
		if (rd < 0) {
			if (p->session != -1)
				do_CryptFlush(p);
			return -1;
		}
		if (rd == 0)
			break;
		ssize_t sent = 0;
		while (sent < rd) {
			ptrdiff_t wr = js_socket_sendsocket(p, buf + sent, rd - sent, false);
			if (wr > 0) {
				sent += wr;
			}
			else if (wr == SOCKET_ERROR && SOCKET_ERRNO == EWOULDBLOCK) {
				wr = 0;
				SLEEP(1);
			}
			else {
				if (p->session != -1)
					do_CryptFlush(p);
				return wr;
			}
		}
		if (sent != rd) {
			if (p->session != -1)
				do_CryptFlush(p);
			return -1;
		}
		total += rd;
	}

	if (p->session != -1)
		do_CryptFlush(p);
	return total;
}

static void dbprintf(bool error, js_socket_private_t* p, const char* fmt, ...)
{
	va_list argptr;
	char    sbuf[1024];

	if (p == NULL || (!p->debug /*&& !error */))
		return;

	va_start(argptr, fmt);
	vsnprintf(sbuf, sizeof(sbuf), fmt, argptr);
	sbuf[sizeof(sbuf) - 1] = 0;
	va_end(argptr);
	lprintf(LOG_DEBUG, "%04d Socket %s%s", p->sock, error ? "ERROR: ":"", sbuf);
}

/* Socket Destructor */

static void js_finalize_socket(JSContext *cx, JSObject *obj)
{
	js_socket_private_t* p;

	if ((p = (js_socket_private_t*)JS_GetPrivate(cx, obj)) == NULL)
		return;

	if (p->tls_psk)
		JS_RemoveObjectRoot(cx, &p->tls_psk);

	do_js_close(cx, p, true);

	if (!p->external)
		free(p->set);
	free(p->hostname);
	free(p);

	JS_SetPrivate(cx, obj, NULL);
}


/* Socket Object Methods */

extern JSClass js_socket_class;
static JSBool
js_close(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	js_socket_private_t* p;
	jsrefcount           rc;

	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
		return JS_FALSE;
	}

	rc = JS_SUSPENDREQUEST(cx);
	do_js_close(cx, p, false);
	dbprintf(false, p, "closed");
	JS_RESUMEREQUEST(cx, rc);

	return JS_TRUE;
}

static ushort js_port(JSContext* cx, jsval val, int type)
{
	char*           cp;
	JSString*       str;
	int32           i = 0;
	struct servent* serv;
	jsrefcount      rc;

	if (JSVAL_IS_NUMBER(val)) {
		JS_ValueToInt32(cx, val, &i);
		return (ushort)i;
	}
	if (JSVAL_IS_STRING(val)) {
		str = JS_ValueToString(cx, val);
		JSSTRING_TO_ASTRING(cx, str, cp, 16, NULL);
		if (cp != NULL) {
			if (IS_DIGIT(*cp))
				return (ushort)strtol(cp, NULL, 0);
			rc = JS_SUSPENDREQUEST(cx);
			serv = getservbyname(cp, type == SOCK_STREAM ? "tcp":"udp");
			JS_RESUMEREQUEST(cx, rc);
			if (serv != NULL)
				return htons(serv->s_port);
		}
	}
	return 0;
}

SOCKET js_socket(JSContext *cx, jsval val)
{
	void*    vp;
	JSClass* cl;
	SOCKET   sock = INVALID_SOCKET;

	if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val) && (cl = JS_GetClass(cx, JSVAL_TO_OBJECT(val))) != NULL) {
		if (cl->flags & JSCLASS_HAS_PRIVATE)
			if ((vp = JS_GetInstancePrivate(cx, JSVAL_TO_OBJECT(val), &js_socket_class, NULL)) != NULL)
				sock = *(SOCKET*)vp;
	} else if (val != JSVAL_VOID) {
		int32 i;
		if (JS_ValueToInt32(cx, val, &i))
			sock = i;
	}

	return sock;
}

#ifdef PREFER_POLL
size_t js_socket_numsocks(JSContext *cx, jsval val)
{
	js_socket_private_t *p;
	JSClass*             cl;
	SOCKET               sock = INVALID_SOCKET;
	size_t               i;
	int32_t              intval;
	size_t               ret = 0;

	if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val) && (cl = JS_GetClass(cx, JSVAL_TO_OBJECT(val))) != NULL) {
		if (cl->flags & JSCLASS_HAS_PRIVATE) {
			if ((p = (js_socket_private_t *)JS_GetInstancePrivate(cx, JSVAL_TO_OBJECT(val), &js_socket_class, NULL)) != NULL) {
				if (p->set) {
					for (i = 0; i < p->set->sock_count; i++) {
						if (p->set->socks[i].sock == INVALID_SOCKET)
							continue;
						ret++;
					}
				}
				else {
					sock = p->sock;
					if (sock != INVALID_SOCKET)
						ret = 1;
				}
			}
		}
	} else if (val != JSVAL_VOID) {
		if (JS_ValueToInt32(cx, val, &intval)) {
			if (intval != INVALID_SOCKET)
				ret = 1;
		}
	}
	return ret;
}

size_t js_socket_add(JSContext *cx, jsval val, struct pollfd *fds, short events)
{
	js_socket_private_t *p;
	JSClass*             cl;
	SOCKET               sock = INVALID_SOCKET;
	size_t               i;
	int32_t              intval;
	size_t               ret = 0;

	if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val) && (cl = JS_GetClass(cx, JSVAL_TO_OBJECT(val))) != NULL) {
		if (cl->flags & JSCLASS_HAS_PRIVATE) {
			if ((p = (js_socket_private_t *)JS_GetInstancePrivate(cx, JSVAL_TO_OBJECT(val), &js_socket_class, NULL)) != NULL) {
				if (p->set) {
					for (i = 0; i < p->set->sock_count; i++) {
						if (p->set->socks[i].sock == INVALID_SOCKET)
							continue;
						fds[ret].events = events;
						fds[ret++].fd = p->set->socks[i].sock;
					}
				}
				else {
					sock = p->sock;
					if (sock != INVALID_SOCKET) {
						fds[ret].events = events;
						fds[ret++].fd = sock;
					}
				}
			}
		}
	} else if (val != JSVAL_VOID) {
		if (JS_ValueToInt32(cx, val, &intval)) {
			sock = intval;
			if (sock != INVALID_SOCKET) {
				fds[ret].events = events;
				fds[ret++].fd = sock;
			}
		}
	}
	return ret;
}
#else
SOCKET js_socket_add(JSContext *cx, jsval val, fd_set *fds)
{
	js_socket_private_t *p;
	JSClass*             cl;
	SOCKET               sock = INVALID_SOCKET;
	size_t               i;
	int32_t              intval;

	if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val) && (cl = JS_GetClass(cx, JSVAL_TO_OBJECT(val))) != NULL) {
		if (cl->flags & JSCLASS_HAS_PRIVATE) {
			if ((p = (js_socket_private_t *)JS_GetInstancePrivate(cx, JSVAL_TO_OBJECT(val), &js_socket_class, NULL)) != NULL) {
				if (p->set) {
					for (i = 0; i < p->set->sock_count; i++) {
						if (p->set->socks[i].sock == INVALID_SOCKET)
							continue;
						FD_SET(p->set->socks[i].sock, fds);
						if (p->set->socks[i].sock > sock)
							sock = p->set->socks[i].sock;
					}
				}
				else {
					sock = p->sock;
					if (sock != INVALID_SOCKET)
						FD_SET(p->sock, fds);
				}
			}
		}
	} else if (val != JSVAL_VOID) {
		if (JS_ValueToInt32(cx, val, &intval)) {
			sock = intval;
			FD_SET(sock, fds);
		}
	}
	return sock;
}

bool js_socket_isset(JSContext *cx, jsval val, fd_set *fds)
{
	js_socket_private_t *p;
	JSClass*             cl;
	size_t               i;
	int                  intval;

	if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val) && (cl = JS_GetClass(cx, JSVAL_TO_OBJECT(val))) != NULL) {
		if (cl->flags & JSCLASS_HAS_PRIVATE) {
			if ((p = (js_socket_private_t *)JS_GetInstancePrivate(cx, JSVAL_TO_OBJECT(val), &js_socket_class, NULL)) != NULL) {
				if (p->set) {
					for (i = 0; i < p->set->sock_count; i++) {
						if (p->set->socks[i].sock == INVALID_SOCKET)
							continue;
						if (FD_ISSET(p->set->socks[i].sock, fds))
							return true;
					}
				}
				else {
					if (p->sock == INVALID_SOCKET)
						return true;
					else {
						if (FD_ISSET(p->sock, fds))
							return true;
					}
				}
			}
		}
	} else if (val != JSVAL_VOID) {
		if (JS_ValueToInt32(cx, val, &intval)) {
			if (FD_ISSET(intval, fds))
				return true;
		}
	}
	return false;
}

void js_timeval(JSContext* cx, jsval val, struct timeval* tv)
{
	jsdouble jsd;

	if (JSVAL_IS_INT(val))
		tv->tv_sec = JSVAL_TO_INT(val);
	else if (JSVAL_IS_DOUBLE(val)) {
		if (JS_ValueToNumber(cx, val, &jsd)) {
			tv->tv_sec = (int)jsd;
			tv->tv_usec = (int)(jsd * 1000000.0) % 1000000;
		}
	}
}
#endif

int js_polltimeout(JSContext* cx, jsval val)
{
	jsdouble jsd;

	if (JSVAL_IS_INT(val))
		return JSVAL_TO_INT(val) * 1000;

	if (JSVAL_IS_DOUBLE(val)) {
		if (JS_ValueToNumber(cx, val, &jsd))
			return (int)(jsd * 1000);
	}

	return 0;
}

static JSBool
js_bind(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	jsval *              argv = JS_ARGV(cx, arglist);
	js_socket_private_t* p;
	ushort               port = 0;
	union xp_sockaddr    addr;
	jsrefcount           rc;
	char *               cstr = NULL;
	char                 portstr[6];
	struct addrinfo      hints, *res, *tres;
	int                  ret;

	JS_SET_RVAL(cx, arglist, JSVAL_FALSE);

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
		return JS_FALSE;
	}

	memset(&addr, 0, sizeof(addr));
	memset(&hints, 0, sizeof(hints));

	if (argc)
		port = js_port(cx, argv[0], p->type);
	if (argc > 1 && argv[1] != JSVAL_VOID) {
		JSVALUE_TO_ASTRING(cx, argv[1], cstr, INET6_ADDRSTRLEN, NULL);
	}

	hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICHOST | AI_NUMERICSERV | AI_PASSIVE;
	hints.ai_socktype = p->type;

	/* We need servname to be non-NULL so we can use a NULL hostname */
	sprintf(portstr, "%hu", port);

	rc = JS_SUSPENDREQUEST(cx);
	if ((ret = getaddrinfo(cstr, portstr, &hints, &res)) != 0) {
		JS_RESUMEREQUEST(cx, rc);
		dbprintf(true, p, "getaddrinfo(%s, %s) failed with error %d", cstr, portstr, ret);
		store_socket_error(p, ret, gai_strerror(ret));
		return JS_TRUE;
	}
	for (tres = res; tres; tres = tres->ai_next) {
		if (bind(p->sock, tres->ai_addr, tres->ai_addrlen) != 0) {
			if (tres->ai_next == NULL) {
				store_socket_error(p, SOCKET_ERRNO, NULL);
				dbprintf(true, p, "bind failed with error %d", SOCKET_ERRNO);
				freeaddrinfo(res);
				JS_RESUMEREQUEST(cx, rc);
				return JS_TRUE;
			}
		}
		else
			break;
	}
	freeaddrinfo(res);

	dbprintf(false, p, "bound to port %u", port);
	JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
	JS_RESUMEREQUEST(cx, rc);
	return JS_TRUE;
}

static JSBool
js_listen(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	jsval *              argv = JS_ARGV(cx, arglist);
	js_socket_private_t* p;
	int32                backlog = 1;
	jsrefcount           rc;

	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
		return JS_FALSE;
	}

	if (argc && argv[0] != JSVAL_VOID)
		backlog = JS_ValueToInt32(cx, argv[0], &backlog);

	rc = JS_SUSPENDREQUEST(cx);
	if (listen(p->sock, backlog) != 0) {
		store_socket_error(p, SOCKET_ERRNO, NULL);
		dbprintf(true, p, "listen failed with error %d", SOCKET_ERRNO);
		JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
		JS_RESUMEREQUEST(cx, rc);
		return JS_TRUE;
	}

	dbprintf(false, p, "listening, backlog=%d", backlog);
	JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
	JS_RESUMEREQUEST(cx, rc);
	return JS_TRUE;
}

static JSBool
js_accept(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	js_socket_private_t* p;
	js_socket_private_t* new_p;
	JSObject*            sockobj;
	SOCKET               new_socket;
	socklen_t            addrlen;
	jsrefcount           rc;

	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
		return JS_FALSE;
	}

	addrlen = sizeof(p->remote_addr);

	rc = JS_SUSPENDREQUEST(cx);
	if (p->set) {
		if ((new_socket = xpms_accept(p->set, &(p->remote_addr), &addrlen, XPMS_FOREVER, XPMS_FLAGS_NONE, NULL)) == INVALID_SOCKET) {
			store_socket_error(p, SOCKET_ERRNO, NULL);
			dbprintf(true, p, "accept failed with error %d", SOCKET_ERRNO);
			JS_RESUMEREQUEST(cx, rc);
			return JS_TRUE;
		}
		call_socket_open_callback(true);
	}
	else {
		if ((new_socket = accept_socket(p->sock, &(p->remote_addr), &addrlen)) == INVALID_SOCKET) {
			store_socket_error(p, SOCKET_ERRNO, NULL);
			dbprintf(true, p, "accept failed with error %d", SOCKET_ERRNO);
			JS_RESUMEREQUEST(cx, rc);
			return JS_TRUE;
		}
	}

	if ((sockobj = js_CreateSocketObject(cx, obj, "new_socket", new_socket, -1)) == NULL) {
		closesocket(new_socket);
		JS_RESUMEREQUEST(cx, rc);
		JS_ReportError(cx, "Error creating new socket object");
		return JS_FALSE;
	}
	if ((new_p = (js_socket_private_t*)JS_GetPrivate(cx, sockobj)) == NULL) {
		JS_RESUMEREQUEST(cx, rc);
		return JS_FALSE;
	}

	new_p->type = p->type;
	if (new_p->type == 0)
		new_p->type = SOCK_STREAM;
	new_p->debug = p->debug;
	new_p->nonblocking = p->nonblocking;
	new_p->external = false;      /* let destructor close socket */
	new_p->is_connected = true;

	dbprintf(false, p, "accepted connection");
	JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(sockobj));
	JS_RESUMEREQUEST(cx, rc);
	return JS_TRUE;
}

struct js_connect_event_args {
	SOCKET sv[2];
	SOCKET sock;
	int socktype;
	char *host;
	bool nonblocking;
	ushort port;
};

static void
js_connect_event_thread(void *args)
{
	struct js_connect_event_args *a = static_cast<js_connect_event_args *>(args);
	struct addrinfo               hints, *res = NULL, *cur;
	int                           result;
	ulong                         val;
	char                          sresult;

	SetThreadName("sbbs/jsConnect");
	memset(&hints, 0, sizeof(hints));
	hints.ai_socktype = a->socktype;
	hints.ai_flags = AI_ADDRCONFIG;
	result = getaddrinfo(a->host, NULL, &hints, &res);
	if (result != 0)
		goto done;
	/* always set to blocking here */
	val = 0;
	ioctlsocket(a->sock, FIONBIO, &val);
	result = SOCKET_ERROR;
	for (cur = res; cur != NULL; cur = cur->ai_next) {
		inet_setaddrport((xp_sockaddr *)cur->ai_addr, a->port);

		result = connect(a->sock, cur->ai_addr, cur->ai_addrlen);
		if (result == 0)
			break;
	}
	sresult = result;
	/* Restore original setting here */
	ioctlsocket(a->sock, FIONBIO, (ulong*)&(a->nonblocking));
	send(a->sv[1], &sresult, 1, 0);

done:
	closesocket(a->sv[1]);
	freeaddrinfo(res);
	free(a);
}

static JSBool
js_connect_event(JSContext *cx, uintN argc, jsval *arglist, js_socket_private_t *p, ushort port, JSObject *obj)
{
	SOCKET                        sv[2];
	struct js_event_list *        ev;
	JSFunction *                  ecb;
	js_callback_t *               cb = js_get_callback(cx);
	struct js_connect_event_args *args;
	jsval *                       argv = JS_ARGV(cx, arglist);

	if (p->sock == INVALID_SOCKET) {
		JS_ReportError(cx, "invalid socket");
		return JS_FALSE;
	}

	if (cb == NULL) {
		return JS_FALSE;
	}

	if (!cb->events_supported) {
		JS_ReportError(cx, "events not supported");
		return JS_FALSE;
	}

	ecb = JS_ValueToFunction(cx, argv[2]);
	if (ecb == NULL) {
		return JS_FALSE;
	}

	// Create socket pair...
#ifdef _WIN32
	if (socketpair(AF_INET, SOCK_STREAM, 0, sv) == -1) {
#else
	if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) {
#endif
		JS_ReportError(cx, "Error %d creating socket pair", SOCKET_ERRNO);
		return JS_FALSE;
	}

	// Create event
	ev = static_cast<js_event_list *>(malloc(sizeof(*ev)));
	if (ev == NULL) {
		JS_ReportError(cx, "error allocating %lu bytes", sizeof(*ev));
		closesocket(sv[0]);
		closesocket(sv[1]);
		return JS_FALSE;
	}
	ev->prev = NULL;
	ev->next = cb->events;
	if (ev->next)
		ev->next->prev = ev;
	ev->type = JS_EVENT_SOCKET_CONNECT;
	ev->cx = obj;
	JS_AddObjectRoot(cx, &ev->cx);
	ev->cb = ecb;
	ev->data.connect.sv[0] = sv[0];
	ev->data.connect.sv[1] = sv[1];
	ev->data.connect.sock = p->sock;
	ev->id = cb->next_eid++;
	p->js_cb = cb;
	cb->events = ev;

	// Start thread
	args = static_cast<js_connect_event_args *>(malloc(sizeof(*args)));
	if (args == NULL) {
		JS_ReportError(cx, "error allocating %lu bytes", sizeof(*args));
		closesocket(sv[0]);
		closesocket(sv[1]);
		return JS_FALSE;
	}
	args->sv[0] = sv[0];
	args->sv[1] = sv[1];
	args->sock = p->sock;
	args->socktype = p->type;
	args->host = strdup(p->hostname);
	args->port = port;
	args->nonblocking = p->nonblocking;
	if (args->host == NULL) {
		JS_ReportError(cx, "error duplicating hostname");
		closesocket(sv[0]);
		closesocket(sv[1]);
		free(args);
		return JS_FALSE;
	}
	_beginthread(js_connect_event_thread, 0 /* Can be smaller... */, args);

	// Success!
	p->is_connected = true;
	JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
	return JS_TRUE;
}

static JSBool
js_connect(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	jsval *              argv = JS_ARGV(cx, arglist);
	int                  result;
	ulong                val;
	ushort               port;
	JSString*            str;
	js_socket_private_t* p;
	jsrefcount           rc;
	char                 ip_str[256];
	struct addrinfo      hints, *res, *cur;
	int                  timeout = 10000; /* Default time-out */

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
		return JS_FALSE;
	}

	str = JS_ValueToString(cx, argv[0]);
	free(p->hostname);
	JSSTRING_TO_MSTRING(cx, str, p->hostname, NULL);
	port = js_port(cx, argv[1], p->type);
	rc = JS_SUSPENDREQUEST(cx);

	if (argc > 2 && JSVAL_IS_OBJECT(argv[2]) && !JSVAL_IS_NULL(argv[2]) && JS_ObjectIsFunction(cx, JSVAL_TO_OBJECT(argv[2]))) {
		JSBool bgr = js_connect_event(cx, argc, arglist, p, port, obj);
		JS_RESUMEREQUEST(cx, rc);
		return bgr;
	}

	dbprintf(false, p, "resolving hostname: %s", p->hostname);

	memset(&hints, 0, sizeof(hints));
	hints.ai_socktype = p->type;
	hints.ai_flags = AI_ADDRCONFIG;
	result = getaddrinfo(p->hostname, NULL, &hints, &res);
	if (result != 0) {
		JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
		store_socket_error(p, result, gai_strerror(result));
		dbprintf(true, p, "getaddrinfo(%s) failed with error %d", p->hostname, result);
		JS_RESUMEREQUEST(cx, rc);
		return JS_TRUE;
	}
	/* always set to nonblocking here */
	val = 1;
	ioctlsocket(p->sock, FIONBIO, &val);
	result = SOCKET_ERROR;
	for (cur = res; cur != NULL; cur = cur->ai_next) {
		if (argc > 2)  /* time-out value specified */
			timeout = js_polltimeout(cx, argv[2]);

		inet_addrtop((xp_sockaddr *)cur->ai_addr, ip_str, sizeof(ip_str));
		dbprintf(false, p, "connecting to %s on port %u at %s", ip_str, port, p->hostname);
		inet_setaddrport((xp_sockaddr *)cur->ai_addr, port);

		result = connect(p->sock, cur->ai_addr, cur->ai_addrlen);

		if (result == SOCKET_ERROR) {
			result = SOCKET_ERRNO;
			if (result == EWOULDBLOCK || result == EINPROGRESS) {
				result = ETIMEDOUT;
				if (socket_writable(p->sock, timeout)) {
					int       so_error = -1;
					socklen_t optlen = sizeof(so_error);
					if (getsockopt(p->sock, SOL_SOCKET, SO_ERROR, (char*)&so_error, &optlen) == 0 && so_error == 0)
						result = 0; /* success */
					else
						result = so_error;
				}
			}
		}
		if (result == 0)
			break;
	}
	/* Restore original setting here */
	ioctlsocket(p->sock, FIONBIO, (ulong*)&(p->nonblocking));

	if (result != 0) {
		freeaddrinfo(res);
		store_socket_error(p, result, NULL);
		dbprintf(true, p, "connect failed with error %d", result);
		JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
		JS_RESUMEREQUEST(cx, rc);
		return JS_TRUE;
	}
	memcpy(&p->remote_addr, cur->ai_addr, cur->ai_addrlen);
	freeaddrinfo(res);

	p->is_connected = true;
	JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
	dbprintf(false, p, "connected to %s on port %u at %s", ip_str, port, p->hostname);
	JS_RESUMEREQUEST(cx, rc);

	return JS_TRUE;
}

static JSBool
js_send(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	jsval *              argv = JS_ARGV(cx, arglist);
	char*                cp = NULL;
	size_t               len;
	JSString*            str;
	js_socket_private_t* p;
	jsrefcount           rc;
	int                  ret;

	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
		return JS_FALSE;
	}

	str = JS_ValueToString(cx, argv[0]);
	JSSTRING_TO_MSTRING(cx, str, cp, &len);
	HANDLE_PENDING(cx, cp);
	if (cp == NULL)
		return JS_TRUE;

	rc = JS_SUSPENDREQUEST(cx);
	ret = js_socket_sendsocket(p, cp, len, true);
	if (ret >= 0) {
		dbprintf(false, p, "sent %d of %lu bytes", ret, len);
		JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(ret));
	} else {
		store_socket_error(p, SOCKET_ERRNO, NULL);
		dbprintf(true, p, "send of %lu bytes failed", len);
	}
	free(cp);
	JS_RESUMEREQUEST(cx, rc);

	return JS_TRUE;
}

static JSBool
js_sendline(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	jsval *              argv = JS_ARGV(cx, arglist);
	char*                cp = NULL;
	size_t               len;
	JSString*            str;
	js_socket_private_t* p;
	jsrefcount           rc;

	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
		return JS_FALSE;
	}

	JS_SET_RVAL(cx, arglist, JSVAL_FALSE);

	str = JS_ValueToString(cx, argv[0]);
	JSSTRING_TO_MSTRING(cx, str, cp, &len);
	HANDLE_PENDING(cx, cp);
	if (cp == NULL)
		return JS_TRUE;

	rc = JS_SUSPENDREQUEST(cx);
	if (js_socket_sendsocket(p, cp, len, false) == static_cast<ptrdiff_t>(len) && js_socket_sendsocket(p, "\r\n", 2, true) == 2) {
		dbprintf(false, p, "sent %lu bytes", len + 2);
		JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
	} else {
		store_socket_error(p, SOCKET_ERRNO, NULL);
		dbprintf(true, p, "send of %lu bytes failed", len + 2);
	}
	free(cp);
	JS_RESUMEREQUEST(cx, rc);

	return JS_TRUE;
}

static JSBool
js_sendto(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	jsval *              argv = JS_ARGV(cx, arglist);
	char*                cp = NULL;
	size_t               len;
	ushort               port;
	JSString*            data_str;
	JSString*            ip_str;
	js_socket_private_t* p;
	jsrefcount           rc;
	struct addrinfo      hints, *res, *cur;
	int                  result;
	char                 ip_addr[INET6_ADDRSTRLEN];

	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
		return JS_FALSE;
	}

	JS_SET_RVAL(cx, arglist, JSVAL_FALSE);

	/* data */
	data_str = JS_ValueToString(cx, argv[0]);
	JSSTRING_TO_MSTRING(cx, data_str, cp, &len);
	HANDLE_PENDING(cx, cp);
	if (cp == NULL)
		return JS_TRUE;

	/* address */
	ip_str = JS_ValueToString(cx, argv[1]);
	free(p->hostname);
	JSSTRING_TO_MSTRING(cx, ip_str, p->hostname, NULL);
	if (JS_IsExceptionPending(cx)) {
		free(cp);
		return JS_FALSE;
	}
	if (p->hostname == NULL) {
		free(cp);
		return JS_TRUE;
	}
	port = js_port(cx, argv[2], p->type);
	rc = JS_SUSPENDREQUEST(cx);

	memset(&hints, 0, sizeof(hints));
	hints.ai_socktype = p->type;
	hints.ai_flags = AI_ADDRCONFIG;
	dbprintf(false, p, "resolving hostname: %s", p->hostname);

	if ((result = getaddrinfo(p->hostname, NULL, &hints, &res) != 0)) {
		store_socket_error(p, result, gai_strerror(result));
		dbprintf(true, p, "getaddrinfo(%s) failed with error %d", p->hostname, result);
		JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
		free(cp);
		JS_RESUMEREQUEST(cx, rc);
		return JS_TRUE;
	}

	for (cur = res; cur; cur = cur->ai_next) {
		inet_addrtop((xp_sockaddr *)cur->ai_addr, ip_addr, sizeof(ip_addr));
		dbprintf(false, p, "sending %lu bytes to %s port %u at %s"
		         , len, ip_addr, port, p->hostname);
		inet_setaddrport((xp_sockaddr *)cur->ai_addr, port);
		if (sendto(p->sock, cp, len, 0 /* flags */, cur->ai_addr, cur->ai_addrlen) == (int)len) {
			dbprintf(false, p, "sent %lu bytes", len);
			JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
		} else {
			store_socket_error(p, SOCKET_ERRNO, NULL);
			dbprintf(true, p, "send of %lu bytes failed to %s", len, ip_addr);
		}
	}
	free(cp);
	freeaddrinfo(res);
	JS_RESUMEREQUEST(cx, rc);

	return JS_TRUE;
}


static JSBool
js_sendfile(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	jsval *              argv = JS_ARGV(cx, arglist);
	char*                fname = NULL;
	off_t                len;
	int                  file;
	js_socket_private_t* p;
	jsrefcount           rc;

	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
		return JS_FALSE;
	}

	JS_SET_RVAL(cx, arglist, JSVAL_FALSE);

	JSVALUE_TO_MSTRING(cx, argv[0], fname, NULL);
	HANDLE_PENDING(cx, fname);
	if (fname == NULL) {
		JS_ReportError(cx, "Failure reading filename");
		return JS_FALSE;
	}

	rc = JS_SUSPENDREQUEST(cx);
	if ((file = nopen(fname, O_RDONLY | O_BINARY)) == -1) {
		free(fname);
		JS_RESUMEREQUEST(cx, rc);
		return JS_TRUE;
	}

	len = js_socket_sendfilesocket(p, file);
	close(file);
	if (len > 0) {
		dbprintf(false, p, "sent %" PRIdOFF " bytes", len);
		JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
	} else {
		store_socket_error(p, SOCKET_ERRNO, NULL);
		dbprintf(true, p, "send of %s failed", fname);
	}
	free(fname);

	JS_RESUMEREQUEST(cx, rc);
	return JS_TRUE;
}

static JSBool
js_sendbin(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	jsval *              argv = JS_ARGV(cx, arglist);
	BYTE                 b;
	WORD                 w;
	DWORD                l;
	int32                val = 0;
	size_t               wr = 0;
	int32                size = sizeof(DWORD);
	js_socket_private_t* p;
	jsrefcount           rc;

	JS_SET_RVAL(cx, arglist, JSVAL_FALSE);

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
		return JS_FALSE;
	}

	if (argc && argv[0] != JSVAL_VOID)
		JS_ValueToInt32(cx, argv[0], &val);
	if (argc > 1 && argv[1] != JSVAL_VOID)
		JS_ValueToInt32(cx, argv[1], &size);

	rc = JS_SUSPENDREQUEST(cx);
	switch (size) {
		case sizeof(BYTE):
			b = (BYTE)val;
			wr = js_socket_sendsocket(p, &b, size, true);
			break;
		case sizeof(WORD):
			w = (WORD)val;
			if (p->network_byte_order)
				w = htons(w);
			wr = js_socket_sendsocket(p, (BYTE*)&w, size, true);
			break;
		case sizeof(DWORD):
			l = val;
			if (p->network_byte_order)
				l = htonl(l);
			wr = js_socket_sendsocket(p, (BYTE*)&l, size, true);
			break;
		default:
			/* unknown size */
			dbprintf(true, p, "unsupported binary write size: %d", size);
			break;
	}
	if (wr == static_cast<size_t>(size)) {
		dbprintf(false, p, "sent %u bytes (binary)", size);
		JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
	} else {
		store_socket_error(p, SOCKET_ERRNO, NULL);
		dbprintf(true, p, "send of %u bytes (binary) failed", size);
	}

	JS_RESUMEREQUEST(cx, rc);
	return JS_TRUE;
}


static JSBool
js_recv(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	jsval *              argv = JS_ARGV(cx, arglist);
	char*                buf;
	int32                len = 512;
	int32                timeout = 120;
	JSString*            str;
	jsrefcount           rc;
	js_socket_private_t* p;

	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
		return JS_FALSE;
	}

	if (argc && argv[0] != JSVAL_VOID) {
		JS_ValueToInt32(cx, argv[0], &len);

		if (argc > 1 && argv[1] != JSVAL_VOID) {
			JS_ValueToInt32(cx, argv[1], &timeout);
		}
	}

	if ((buf = (char*)malloc(len + 1)) == NULL) {
		JS_ReportError(cx, "Error allocating %u bytes", len + 1);
		return JS_FALSE;
	}

	rc = JS_SUSPENDREQUEST(cx);
	len = js_socket_recv(cx, p, buf, len, 0, timeout);
	JS_RESUMEREQUEST(cx, rc);
	if (len < 0) {
		store_socket_error(p, SOCKET_ERRNO, NULL);
		JS_SET_RVAL(cx, arglist, JSVAL_NULL);
		free(buf);
		return JS_TRUE;
	}
	buf[len] = 0;

	str = JS_NewStringCopyN(cx, buf, len);
	if (str == NULL) {
		free(buf);
		return JS_FALSE;
	}

	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
	rc = JS_SUSPENDREQUEST(cx);
	dbprintf(false, p, "received %u bytes", len);
	JS_RESUMEREQUEST(cx, rc);

	free(buf);
	return JS_TRUE;
}

static JSBool
js_recvfrom(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	jsval *              argv = JS_ARGV(cx, arglist);
	char*                buf;
	char                 ip_addr[INET6_ADDRSTRLEN];
	char                 port[32];
	int                  rd = 0;
	int32                len = 512;
	uintN                n;
	bool                 binary = false;
	BYTE                 b;
	WORD                 w;
	DWORD                l;
	jsval                data_val = JSVAL_NULL;
	JSString*            str;
	JSObject*            retobj;
	union xp_sockaddr    addr;
	socklen_t            addrlen;
	jsrefcount           rc;
	js_socket_private_t* p;

	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
		return JS_FALSE;
	}

	JS_SET_RVAL(cx, arglist, JSVAL_NULL);

	for (n = 0; n < argc; n++) {
		if (JSVAL_IS_BOOLEAN(argv[n])) {
			binary = JSVAL_TO_BOOLEAN(argv[n]);
			if (binary)
				len = sizeof(DWORD);
		} else if (argv[n] != JSVAL_VOID)
			JS_ValueToInt32(cx, argv[n], &len);
	}

	addrlen = sizeof(addr.addr);

	if (binary) {    /* Binary/Integer Data */

		rc = JS_SUSPENDREQUEST(cx);
		switch (len) {
			case sizeof(BYTE):
				if ((rd = recvfrom(p->sock, (char *)&b, len, 0, &addr.addr, &addrlen)) == len)
					data_val = INT_TO_JSVAL(b);
				break;
			case sizeof(WORD):
				if ((rd = recvfrom(p->sock, (char *)&w, len, 0, &addr.addr, &addrlen)) == len) {
					if (p->network_byte_order)
						w = ntohs(w);
					data_val = INT_TO_JSVAL(w);
				}
				break;
			default:
			case sizeof(DWORD):
				if ((rd = recvfrom(p->sock, (char *)&l, len, 0, &addr.addr, &addrlen)) == len) {
					if (p->network_byte_order)
						l = ntohl(l);
					data_val = UINT_TO_JSVAL(l);
				}
				break;
		}

		JS_RESUMEREQUEST(cx, rc);

		if (rd != len) {
			store_socket_error(p, SOCKET_ERRNO, NULL);
			return JS_TRUE;
		}

	} else {        /* String Data */

		if ((buf = (char*)malloc(len + 1)) == NULL) {
			JS_ReportError(cx, "Error allocating %u bytes", len + 1);
			return JS_FALSE;
		}

		rc = JS_SUSPENDREQUEST(cx);
		len = recvfrom(p->sock, buf, len, 0, &addr.addr, &addrlen);
		JS_RESUMEREQUEST(cx, rc);
		if (len < 0) {
			store_socket_error(p, SOCKET_ERRNO, NULL);
			free(buf);
			return JS_TRUE;
		}
		buf[len] = 0;

		str = JS_NewStringCopyN(cx, buf, len);
		free(buf);

		if (str == NULL)
			return JS_FALSE;

		data_val = STRING_TO_JSVAL(str);
	}


	if ((retobj = JS_NewObject(cx, NULL, NULL, obj)) == NULL) {
		JS_ReportError(cx, "JS_NewObject failed");
		return JS_FALSE;
	}

	JS_DefineProperty(cx, retobj, "data"
	                  , data_val
	                  , NULL, NULL, JSPROP_ENUMERATE);

	sprintf(port, "%u", inet_addrport(&addr));
	if ((str = JS_NewStringCopyZ(cx, port)) == NULL)
		return JS_FALSE;
	JS_DefineProperty(cx, retobj, "port"
	                  , STRING_TO_JSVAL(str)
	                  , NULL, NULL, JSPROP_ENUMERATE);

	inet_addrtop(&addr, ip_addr, sizeof(ip_addr));
	if ((str = JS_NewStringCopyZ(cx, ip_addr)) == NULL)
		return JS_FALSE;
	JS_DefineProperty(cx, retobj, "ip_address"
	                  , STRING_TO_JSVAL(str)
	                  , NULL, NULL, JSPROP_ENUMERATE);

	JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(retobj));

	rc = JS_SUSPENDREQUEST(cx);
	dbprintf(false, p, "received %u bytes from %s:%s", len, ip_addr, port);
	JS_RESUMEREQUEST(cx, rc);

	return JS_TRUE;
}


static JSBool
js_peek(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	jsval *              argv = JS_ARGV(cx, arglist);
	char*                buf;
	int32                len = 512;
	JSString*            str;
	jsrefcount           rc;
	js_socket_private_t* p;

	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
		return JS_FALSE;
	}

	if (argc && argv[0] != JSVAL_VOID)
		JS_ValueToInt32(cx, argv[0], &len);

	if ((buf = (char*)malloc(len + 1)) == NULL) {
		JS_ReportError(cx, "Error allocating %u bytes", len + 1);
		return JS_FALSE;
	}
	rc = JS_SUSPENDREQUEST(cx);
	if (p->session == -1)
		len = js_socket_recv(cx, p, buf, len, MSG_PEEK, 120);
	else
		len = 0;
	JS_RESUMEREQUEST(cx, rc);
	if (len < 0) {
		store_socket_error(p, SOCKET_ERRNO, NULL);
		JS_SET_RVAL(cx, arglist, JSVAL_NULL);
		free(buf);
		return JS_TRUE;
	}
	buf[len] = 0;

	str = JS_NewStringCopyN(cx, buf, len);
	free(buf);
	if (str == NULL)
		return JS_FALSE;

	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
	rc = JS_SUSPENDREQUEST(cx);
	dbprintf(false, p, "received %u bytes, lasterror=%d"
	         , len, SOCKET_ERRNO);
	JS_RESUMEREQUEST(cx, rc);

	return JS_TRUE;
}

/* Returns 0 if there is rx data waiting */
/* Returns 1 if the 'timeout' period has elapsed (with no data waiting) */
/* Returns 2 if the socket has been disconnected (regardless of any data waiting) */
/* Returns 3 if there was no rx data waiting and the 'timeout' period has not yet elapsed */
static int
js_sock_read_check(js_socket_private_t *p, time_t start, int32 timeout, int i)
{
	bool rd;

	if (!socket_check(p->sock, &rd, NULL, 1000)) {
		store_socket_error(p, SOCKET_ERRNO, NULL);
		return 2;
	}

	if (!rd) {
		if (time(NULL) - start > timeout) {
			dbprintf(false, p, "recvline timeout (received: %d)", i);
			return 1;
		}
		return 3;
	}
	return 0;
}

/* This method is to return null on error/timeout, not void/undefined */
static JSBool
js_recvline(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	jsval *              argv = JS_ARGV(cx, arglist);
	char                 ch;
	char*                buf;
	int                  i, got;
	int32                len = 512;
	time_t               start;
	int32                timeout = 30; /* seconds */
	JSString*            str;
	js_socket_private_t* p;
	jsrefcount           rc;

	JS_SET_RVAL(cx, arglist, JSVAL_NULL);

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
		return JS_FALSE;
	}

	if (argc && argv[0] != JSVAL_VOID)
		JS_ValueToInt32(cx, argv[0], &len);

	if ((buf = (char*)malloc(len + 1)) == NULL) {
		JS_ReportError(cx, "Error allocating %u bytes", len + 1);
		return JS_FALSE;
	}

	if (argc > 1 && argv[1] != JSVAL_VOID)
		JS_ValueToInt32(cx, argv[1], &timeout);

	start = time(NULL);
	rc = JS_SUSPENDREQUEST(cx);
	for (i = 0; i < len;) {
		if (p->session == -1) {
			switch (js_sock_read_check(p, start, timeout, i)) {
				case 1: // time-out */
				case 2: // disconnected
					if (i) {     // some data was received before the error/disconnection
						len = 0;  // so break the loop
						continue;
					}
					// no data received, so just return null
					JS_RESUMEREQUEST(cx, rc);
					free(buf);
					return JS_TRUE;
				case 3: // no data and no time-out... yet
					continue;
			}
		}

		if ((got = js_socket_recv(cx, p, &ch, 1, 0, i?1:timeout)) != 1) {
			if (p->session == -1)
				store_socket_error(p, SOCKET_ERRNO, NULL);
			if (i == 0) {           // no data received
				JS_RESUMEREQUEST(cx, rc);
				free(buf);          // so return null (not an empty string)
				return JS_TRUE;
			}
			break;
		}

		if (ch == '\n' /* && i>=1 */) /* Mar-9-2003: terminate on sole LF */
			break;

		buf[i++] = ch;
	}
	if (i > 0 && buf[i - 1] == '\r')
		buf[i - 1] = 0;
	else
		buf[i] = 0;

	JS_RESUMEREQUEST(cx, rc);
	str = JS_NewStringCopyZ(cx, buf);
	free(buf);
	if (str == NULL)
		return JS_FALSE;

	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
	rc = JS_SUSPENDREQUEST(cx);
	dbprintf(false, p, "received %u bytes (recvline) lasterror=%d"
	         , i, SOCKET_ERRNO);
	JS_RESUMEREQUEST(cx, rc);

	return JS_TRUE;
}

static JSBool
js_recvbin(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	jsval *              argv = JS_ARGV(cx, arglist);
	BYTE                 b;
	WORD                 w;
	DWORD                l;
	int32                size = sizeof(DWORD);
	int                  rd = 0;
	js_socket_private_t* p;
	jsrefcount           rc;

	JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(-1));

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
		return JS_FALSE;
	}

	if (argc && argv[0] != JSVAL_VOID)
		JS_ValueToInt32(cx, argv[0], &size);

	rc = JS_SUSPENDREQUEST(cx);
	switch (size) {
		case sizeof(BYTE):
			if ((rd = js_socket_recv(cx, p, &b, size, MSG_WAITALL, 120)) == size)
				JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(b));
			break;
		case sizeof(WORD):
			if ((rd = js_socket_recv(cx, p, (BYTE*)&w, size, MSG_WAITALL, 120)) == size) {
				if (p->network_byte_order)
					w = ntohs(w);
				JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(w));
			}
			break;
		case sizeof(DWORD):
			if ((rd = js_socket_recv(cx, p, (BYTE*)&l, size, MSG_WAITALL, 120)) == size) {
				if (p->network_byte_order)
					l = ntohl(l);
				JS_SET_RVAL(cx, arglist, UINT_TO_JSVAL(l));
			}
			break;
	}

	if (rd != size)
		store_socket_error(p, SOCKET_ERRNO, NULL);

	JS_RESUMEREQUEST(cx, rc);
	return JS_TRUE;
}


static JSBool
js_getsockopt(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	jsval *              argv = JS_ARGV(cx, arglist);
	int                  opt;
	int                  level = 0;
	int                  val = 0;
	js_socket_private_t* p;
	LINGER               linger;
	void*                vp = &val;
	socklen_t            len = sizeof(val);
	jsrefcount           rc;
	char *               cstr;

	JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(-1));

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
		return JS_FALSE;
	}

	rc = JS_SUSPENDREQUEST(cx);
	JSVALUE_TO_ASTRING(cx, argv[0], cstr, 64, NULL);
	if ((opt = getSocketOptionByName(cstr, &level)) == -1) {
		JS_RESUMEREQUEST(cx, rc);
		return JS_TRUE;
	}

	if (opt == SO_LINGER) {
		vp = &linger;
		len = sizeof(linger);
	}
	if (getsockopt(p->sock, level, opt, static_cast<char *>(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));
	} else {
		store_socket_error(p, SOCKET_ERRNO, NULL);
		dbprintf(true, p, "error %d getting option %d"
		         , SOCKET_ERRNO, opt);
	}

	JS_RESUMEREQUEST(cx, rc);
	return JS_TRUE;
}


static JSBool
js_setsockopt(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	jsval *              argv = JS_ARGV(cx, arglist);
	int                  opt;
	int                  level;
	int32                val = 1;
	js_socket_private_t* p;
	LINGER               linger;
	void*                vp = &val;
	socklen_t            len = sizeof(val);
	jsrefcount           rc;
	char *               optname;

	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
		return JS_FALSE;
	}
	if (p->sock == INVALID_SOCKET)
		return JS_TRUE;

	JSVALUE_TO_ASTRING(cx, argv[0], optname, 64, NULL);
	rc = JS_SUSPENDREQUEST(cx);
	opt = getSocketOptionByName(optname, &level);
	if (argv[1] != JSVAL_VOID) {
		JS_RESUMEREQUEST(cx, rc);
		JS_ValueToInt32(cx, argv[1], &val);
		rc = JS_SUSPENDREQUEST(cx);
	}

	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, static_cast<const char*>(vp), len) == 0));
	store_socket_error(p, SOCKET_ERRNO, NULL);

	JS_RESUMEREQUEST(cx, rc);
	return JS_TRUE;
}

static JSBool
js_ioctlsocket(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	jsval *              argv = JS_ARGV(cx, arglist);
	int32                cmd = 0;
	int32                arg = 0;
	js_socket_private_t* p;
	jsrefcount           rc;

	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
		return JS_FALSE;
	}

	if (argc && argv[0] != JSVAL_VOID)
		JS_ValueToInt32(cx, argv[0], &cmd);
	if (argc > 1 && argv[1] != JSVAL_VOID)
		JS_ValueToInt32(cx, argv[1], &arg);

	rc = JS_SUSPENDREQUEST(cx);
	if (ioctlsocket(p->sock, cmd, (ulong*)&arg) == 0) {
		JS_RESUMEREQUEST(cx, rc);
		JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(arg));
	}
	else {
		JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(-1));
		JS_RESUMEREQUEST(cx, rc);
	}

	store_socket_error(p, SOCKET_ERRNO, NULL);

	return JS_TRUE;
}

static JSBool
js_poll(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	jsval *              argv = JS_ARGV(cx, arglist);
	js_socket_private_t* p;
	bool                 poll_for_write = false;
	uintN                argn;
	int                  result;
	jsrefcount           rc;
#ifdef PREFER_POLL
	int                  timeout = 0;
	struct pollfd *      fds;
	nfds_t               nfds;
	jsval                objval = OBJECT_TO_JSVAL(obj);
#else
	size_t               i;
	SOCKET               high = 0;
	fd_set               socket_set;
	fd_set*              rd_set = NULL;
	fd_set*              wr_set = NULL;
	struct  timeval      tv = {0, 0};
#endif

	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
		return JS_FALSE;
	}

	if (p->sock == INVALID_SOCKET && p->set == NULL) {
		dbprintf(true, p, "INVALID SOCKET in call to poll");
		JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(-1));
		return JS_TRUE;
	}

	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])) {
#ifdef PREFER_POLL
			timeout = js_polltimeout(cx, argv[argn]);
#else
			js_timeval(cx, argv[argn], &tv);
#endif
		}
	}

	rc = JS_SUSPENDREQUEST(cx);
#ifdef PREFER_POLL
	if (p->peeked && !poll_for_write) {
		result = 1;
	}
	else {
		nfds = js_socket_numsocks(cx, objval);
		fds = static_cast<pollfd *>(calloc(nfds, sizeof(*fds)));
		if (fds == NULL) {
			JS_RESUMEREQUEST(cx, rc);
			JS_ReportError(cx, "Error allocating %d elements of %lu bytes at %s:%d"
			               , nfds, sizeof(*fds), getfname(__FILE__), __LINE__);
			return JS_FALSE;
		}
		nfds = js_socket_add(cx, objval, fds, poll_for_write ? POLLOUT : POLLIN);
		result = poll(fds, nfds, timeout);
		free(fds);
	}
#else
	FD_ZERO(&socket_set);
	if (p->set) {
		for (i = 0; i < p->set->sock_count; i++) {
			FD_SET(p->set->socks[i].sock, &socket_set);
			if (p->set->socks[i].sock > high)
				high = p->set->socks[i].sock;
		}
	}
	else {
		high = p->sock;
		FD_SET(p->sock, &socket_set);
	}
	if (poll_for_write)
		wr_set = &socket_set;
	else
		rd_set = &socket_set;

	if (p->peeked && !poll_for_write)
		result = 1;
	else
		result = select(high + 1, rd_set, wr_set, NULL, &tv);
#endif

	store_socket_error(p, SOCKET_ERRNO, NULL);

	dbprintf(false, p, "poll: select/poll returned %d (errno %d)"
	         , result, p->last_error);

	JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(result));
	JS_RESUMEREQUEST(cx, rc);

	return JS_TRUE;
}

static js_callback_t *
js_get_callback(JSContext *cx)
{
	JSObject* scope = JS_GetScopeChain(cx);
	JSObject* pscope;
	jsval     val = JSVAL_NULL;
	JSObject *pjs_obj;

	pscope = scope;
	while ((!JS_LookupProperty(cx, pscope, "js", &val) || val == JSVAL_VOID || !JSVAL_IS_OBJECT(val)) && pscope != NULL) {
		pscope = JS_GetParent(cx, pscope);
		if (pscope == NULL) {
			JS_ReportError(cx, "Walked to global, no js object!");
			return NULL;
		}
	}
	pjs_obj = JSVAL_TO_OBJECT(val);
	if (pjs_obj == NULL)
		return NULL;
	return static_cast<js_callback_t *>(JS_GetPrivate(cx, pjs_obj));
}

static void
js_install_one_socket_event(JSContext *cx, JSObject *obj, JSFunction *ecb, js_callback_t *cb, js_socket_private_t *p, SOCKET sock, enum js_event_type et)
{
	struct js_event_list *ev;

	ev = static_cast<js_event_list *>(malloc(sizeof(*ev)));
	if (ev == NULL) {
		JS_ReportError(cx, "error allocating %lu bytes", sizeof(*ev));
		return;
	}
	ev->prev = NULL;
	ev->next = cb->events;
	if (ev->next)
		ev->next->prev = ev;
	ev->type = et;
	ev->cx = obj;
	JS_AddObjectRoot(cx, &ev->cx);
	ev->cb = ecb;
	ev->data.sock = sock;
	ev->id = cb->next_eid;
	p->js_cb = cb;
	cb->events = ev;
}

static JSBool
js_install_event(JSContext *cx, uintN argc, jsval *arglist, bool once)
{
	jsval *              argv = JS_ARGV(cx, arglist);
	js_callback_t*       cb;
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	JSFunction *         ecb;
	js_socket_private_t* p;
	size_t               i;
	char                 operation[16];
	enum js_event_type   et;
	size_t               slen;

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
		return JS_FALSE;
	}

	/*
	 * NOTE: If you allow a thisObj here, you'll need to deal with js_GetClassPrivate
	 *       in js_internal.c where the object is assumed to be a socket.
	 */
	if (argc != 2) {
		JS_ReportError(cx, "js.on() and js.once() require exactly two parameters");
		return JS_FALSE;
	}
	ecb = JS_ValueToFunction(cx, argv[1]);
	if (ecb == NULL) {
		return JS_FALSE;
	}
	JSVALUE_TO_STRBUF(cx, argv[0], operation, sizeof(operation), &slen);
	HANDLE_PENDING(cx, NULL);
	if (strcmp(operation, "read") == 0) {
		if (once)
			et = JS_EVENT_SOCKET_READABLE_ONCE;
		else
			et = JS_EVENT_SOCKET_READABLE;
	}
	else if (strcmp(operation, "write") == 0) {
		if (once)
			et = JS_EVENT_SOCKET_WRITABLE_ONCE;
		else
			et = JS_EVENT_SOCKET_WRITABLE;
	}
	else {
		JS_ReportError(cx, "event parameter must be 'read' or 'write'");
		return JS_FALSE;
	}

	cb = js_get_callback(cx);
	if (cb == NULL) {
		return JS_FALSE;
	}
	if (!cb->events_supported) {
		JS_ReportError(cx, "events not supported");
		return JS_FALSE;
	}


	if (p->set) {
		for (i = 0; i < p->set->sock_count; i++) {
			if (p->set->socks[i].sock != INVALID_SOCKET)
				js_install_one_socket_event(cx, obj, ecb, cb, p, p->set->socks[i].sock, et);
		}
	}
	else {
		if (p->sock != INVALID_SOCKET)
			js_install_one_socket_event(cx, obj, ecb, cb, p, p->sock, et);
	}
	JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(cb->next_eid));
	cb->next_eid++;

	if (JS_IsExceptionPending(cx))
		return JS_FALSE;
	return JS_TRUE;
}

static JSBool
js_clear_socket_event(JSContext *cx, uintN argc, jsval *arglist, bool once)
{
	jsval *            argv = JS_ARGV(cx, arglist);
	enum js_event_type et;
	char               operation[16];
	size_t             slen;
	js_callback_t*     cb;

	if (argc != 2) {
		JS_ReportError(cx, "js.clearOn() and js.clearOnce() require exactly two parameters");
		return JS_FALSE;
	}
	JSVALUE_TO_STRBUF(cx, argv[0], operation, sizeof(operation), &slen);
	HANDLE_PENDING(cx, NULL);
	if (strcmp(operation, "read") == 0) {
		if (once)
			et = JS_EVENT_SOCKET_READABLE_ONCE;
		else
			et = JS_EVENT_SOCKET_READABLE;
	}
	else if (strcmp(operation, "write") == 0) {
		if (once)
			et = JS_EVENT_SOCKET_WRITABLE_ONCE;
		else
			et = JS_EVENT_SOCKET_WRITABLE;
	}
	else {
		JS_SET_RVAL(cx, arglist, JSVAL_VOID);
		return JS_TRUE;
	}

	cb = js_get_callback(cx);
	if (cb == NULL) {
		return JS_FALSE;
	}
	return js_clear_event(cx, arglist, cb, et, 1);
}

static JSBool
js_once(JSContext *cx, uintN argc, jsval *arglist)
{
	return js_install_event(cx, argc, arglist, true);
}

static JSBool
js_clearOnce(JSContext *cx, uintN argc, jsval *arglist)
{
	return js_clear_socket_event(cx, argc, arglist, true);
}

static JSBool
js_on(JSContext *cx, uintN argc, jsval *arglist)
{
	return js_install_event(cx, argc, arglist, false);
}

static JSBool
js_clearOn(JSContext *cx, uintN argc, jsval *arglist)
{
	return js_clear_socket_event(cx, argc, arglist, false);
}

/* Socket Object Properties */
enum {
	SOCK_PROP_LAST_ERROR
	, SOCK_PROP_ERROR_STR
	, SOCK_PROP_IS_CONNECTED
	, SOCK_PROP_IS_WRITEABLE
	, 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
	, SOCK_PROP_TYPE
	, SOCK_PROP_FAMILY
	, SOCK_PROP_NETWORK_ORDER
	, SOCK_PROP_SSL_SESSION
	, SOCK_PROP_SSL_SERVER
	, SOCK_PROP_TLS_MINVER
	, SOCK_PROP_TLS_PSK
	, SOCK_PROP_TLS_PSK_ID
	, SOCK_PROP_TLS_NAMEVERIFY
	, SOCK_PROP_TLS_CERTVERIFY
	, SOCK_PROP_TLS_CLIENTAUTH
	, SOCK_PROP_TLS_ENHANCED_CERTCHECK

};

#ifdef BUILD_JSDOCS
static const char* socket_prop_desc[] = {
	/* Regular properties */
	"Error status for the last socket operation that failed - <small>READ ONLY</small>"
	, "Error description for the last socket operation that failed - <small>READ ONLY</small>"
	, "<tt>true</tt> if socket is in a connected state - <small>READ ONLY</small>"
	, "<tt>true</tt> if socket can accept written data - Setting to false will shutdown the write end of the socket."
	, "Alias for is_writeable"
	, "<tt>true</tt> if data is waiting to be read from socket - <small>READ ONLY</small>"
	, "Number of bytes waiting to be read - TLS sockets will never return more than 1 - <small>READ ONLY</small>"
	, "Enable debug logging"
	, "Socket descriptor (advanced uses only)"
	, "Use non-blocking operation (default is <tt>false</tt>)"
	, "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"
	, "Socket type, <tt>SOCK_STREAM</tt> (TCP) or <tt>SOCK_DGRAM</tt> (UDP)"
	, "Socket protocol family, <tt>PF_INET</tt> (IPv4) or <tt>PF_INET6</tt> (IPv6)"
	, "<tt>true</tt> if binary data is to be sent in Network Byte Order (big end first), default is <tt>true</tt>"
	, "Set to <tt>true</tt> to enable SSL as a client on the socket"
	, "Set to <tt>true</tt> to enable SSL as a server on the socket"
	, "Set to 100 to support TLS 1.0, 101 to support TLS 1.1 and 102 (default) for TLS 1.2, must be set before enabling TLS"
	, "Set to an object with id: key pairs for TLS PSK auth, must be set before enabling TLS"
	, "If TLS PSK is used, indicates which entry in <i>tls_psk</i> was used by the remote"
	, "Enables/Disables TLS server name verification, must be set before enabling TLS"
	, "Enables/Disables TLS certificate verification, must be set before enabling TLS"
	, "Enables/Disables TLS client authentication, must be set before enabling TLS"
	, "Enables/Disables Enhanced check of TLS certificates"

	/* statically-defined properties: */
	, "Array of socket option names supported by the current platform"
	, NULL
};
#endif

static JSBool js_socket_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
{
	jsval                idval;
	jsint                tiny;
	js_socket_private_t* p;
	jsrefcount           rc;
	JSBool               b;
	int32                i;
	scfg_t *             scfg;
	char*                estr;
	JSObject*            pskobj;

	if ((p = (js_socket_private_t*)JS_GetPrivate(cx, obj)) == NULL) {
		// Prototype access
		return JS_TRUE;
	}

	JS_IdToValue(cx, id, &idval);
	tiny = JSVAL_TO_INT(idval);

	rc = JS_SUSPENDREQUEST(cx);
	dbprintf(false, p, "setting property %d", tiny);
	JS_RESUMEREQUEST(cx, rc);

	switch (tiny) {
		case SOCK_PROP_DEBUG:
			JS_ValueToBoolean(cx, *vp, &(p->debug));
			break;
		case SOCK_PROP_DESCRIPTOR:
			if (p->session != -1) {
				if (p->tls_server)
					destroy_session(lprintf, p->session);
				else
					cryptDestroySession(p->session);
				p->session = -1;
			}
			if (JS_ValueToInt32(cx, *vp, &i))
				p->sock = i;
			p->is_connected = true;
			break;
		case SOCK_PROP_LAST_ERROR:
			if (JS_ValueToInt32(cx, *vp, &i))
				p->last_error = i;
			break;
		case SOCK_PROP_NONBLOCKING:
			JS_ValueToBoolean(cx, *vp, &(p->nonblocking));
			rc = JS_SUSPENDREQUEST(cx);
			ioctlsocket(p->sock, FIONBIO, (ulong*)&(p->nonblocking));
			JS_RESUMEREQUEST(cx, rc);
			break;
		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;
		case SOCK_PROP_SSL_SERVER:
		case SOCK_PROP_SSL_SESSION:
			JS_ValueToBoolean(cx, *vp, &b);
			rc = JS_SUSPENDREQUEST(cx);
			if (b) {
				if (p->session == -1) {
					int ret = CRYPT_ERROR_NOTINITED;
					scfg = static_cast<scfg_t *>(JS_GetRuntimePrivate(JS_GetRuntime(cx)));

					if (ssl_sync(scfg, lprintf)) {
						if ((ret = cryptCreateSession(&p->session, CRYPT_UNUSED, tiny == SOCK_PROP_SSL_SESSION ? CRYPT_SESSION_TLS: CRYPT_SESSION_TLS_SERVER)) == CRYPT_OK) {
							ulong nb = 0;
							ioctlsocket(p->sock, FIONBIO, &nb);
							nb = 1;
							setsockopt(p->sock, IPPROTO_TCP, TCP_NODELAY, (char*)&nb, sizeof(nb));
							if ((ret = do_cryptAttribute(p->session, CRYPT_SESSINFO_NETWORKSOCKET, p->sock)) == CRYPT_OK) {
								int minver = CRYPT_TLSOPTION_MINVER_TLS12;
								if (p->tls_minver == 100)
									minver = CRYPT_TLSOPTION_MINVER_TLS10;
								else if (p->tls_minver == 101)
									minver = CRYPT_TLSOPTION_MINVER_TLS11;
								do_cryptAttribute(p->session, CRYPT_SESSINFO_TLS_OPTIONS, minver);
								// Reduced compliance checking... required for acme-staging-v02.api.letsencrypt.org
								if (!p->tls_enhanced_certcheck)
									do_cryptAttribute(p->session, CRYPT_OPTION_CERT_COMPLIANCELEVEL, CRYPT_COMPLIANCELEVEL_REDUCED);
								// Add TLS PSK pairs
								// Iterate object...
								if (p->tls_psk) {
									JSIdArray *ids = JS_Enumerate(cx, p->tls_psk);
									if (ids) {
										for (jsint k = 0; k < ids->length; k++) {
											jsval js_id;
											char *id;
											size_t id_sz;
											jsval js_key;
											char *key;
											size_t key_sz;
											JS_IdToValue(cx, ids->vector[k], &js_id);
											id = NULL;
											JSVALUE_TO_MSTRING(cx, js_id, id, &id_sz);
											if (id != NULL) {
												if (!JS_IsExceptionPending(cx)) {
													JS_GetProperty(cx, p->tls_psk, id, &js_key);
													JSVALUE_TO_MSTRING(cx, js_key, key, &key_sz);
													if (key != NULL) {
														if (!JS_IsExceptionPending(cx)) {
															if (do_cryptAttributeString(p->session, CRYPT_SESSINFO_USERNAME, id, id_sz) == CRYPT_OK)
																do_cryptAttributeString(p->session, CRYPT_SESSINFO_PASSWORD, key, key_sz);
														}
														free(key);
													}
												}
												free(id);
											}
										}
										JS_DestroyIdArray(cx, ids);
									}
								}
								if (p->tls_disable_certverify)
									do_cryptAttribute(p->session, CRYPT_SESSINFO_TLS_OPTIONS, CRYPT_TLSOPTION_DISABLE_CERTVERIFY);
								// Ensure key and value are both strings...
								// Set the strings.
								if (tiny == SOCK_PROP_SSL_SESSION) {
									// TODO: Make this configurable
									if (!p->tls_nameverify)
										do_cryptAttribute(p->session, CRYPT_SESSINFO_TLS_OPTIONS, CRYPT_TLSOPTION_DISABLE_NAMEVERIFY);
									if (p->hostname)
										ret = do_cryptAttributeString(p->session, CRYPT_SESSINFO_SERVER_NAME, p->hostname, strlen(p->hostname));
									p->tls_server = false;
								}
								if (tiny == SOCK_PROP_SSL_SERVER || p->tls_clientauth) {
									if (scfg == NULL) {
										ret = CRYPT_ERROR_NOTAVAIL;
									}
									else {
										ret = add_private_key(scfg, lprintf, p->session);
										if (ret != CRYPT_OK) {
											GCES(ret, p, estr, "setting private key");
										}
									}
								}
								if (tiny == SOCK_PROP_SSL_SERVER && p->tls_clientauth) {
									do_cryptAttribute(p->session, CRYPT_SESSINFO_TLS_OPTIONS, CRYPT_TLSOPTION_MANUAL_CERTCHECK);
								}
								if (ret == CRYPT_OK) {
									if ((ret = do_cryptAttribute(p->session, CRYPT_SESSINFO_ACTIVE, 1)) != CRYPT_OK) {
										if (ret == CRYPT_ENVELOPE_RESOURCE) {
											// Get the client cert/cert chain
											CRYPT_CERTIFICATE client_cert;
											ret = cryptGetAttribute(p->session, CRYPT_SESSINFO_RESPONSE, &client_cert);
											if (ret != CRYPT_OK)
												GCES(ret, p, estr, "getting remote certificate");
											else {
												// TODO: Do we need to do everything here?
												cryptSetAttribute(p->session, CRYPT_SESSINFO_AUTHRESPONSE, 1);
												// Create tls_client_cert object...
												JS_DefineProperty(cx, obj, "tls_remote_cert", OBJECT_TO_JSVAL(js_CreateCryptCertObject(cx, client_cert)), NULL, NULL, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY);
												if ((ret = do_cryptAttribute(p->session, CRYPT_SESSINFO_ACTIVE, 1)) != CRYPT_OK) {
													GCES(ret, p, estr, "getting remote certificate");
												}
											}
										}
										else
											GCES(ret, p, estr, "setting session active");
									}
									if (ret == CRYPT_OK && tiny == SOCK_PROP_SSL_SESSION) {
										// Get the client cert/cert chain
										CRYPT_CERTIFICATE client_cert;
										ret = cryptGetAttribute(p->session, CRYPT_SESSINFO_RESPONSE, &client_cert);
										if (ret != CRYPT_OK)
											GCES(ret, p, estr, "getting remote certificate");
										else {
											// TODO: CRLs?
											JS_DefineProperty(cx, obj, "tls_remote_cert", OBJECT_TO_JSVAL(js_CreateCryptCertObject(cx, client_cert)), NULL, NULL, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY);
										}
									}
								}
							}
						}
						else
							GCESH(ret, p->sock, CRYPT_UNUSED, estr, "creating session");
					}
					if (ret != CRYPT_OK) {
						if (p->session != -1)
							cryptDestroySession(p->session);
						p->session = -1;
						ioctlsocket(p->sock, FIONBIO, (ulong*)&(p->nonblocking));
						do_js_close(cx, p, false);
					}
				}
			}
			else {
				if (p->session != -1) {
					if (p->tls_server)
						destroy_session(lprintf, p->session);
					else
						cryptDestroySession(p->session);
					p->session = -1;
					ioctlsocket(p->sock, FIONBIO, (ulong*)&(p->nonblocking));
					do_js_close(cx, p, false);
				}
			}
			JS_RESUMEREQUEST(cx, rc);
			break;
		case SOCK_PROP_TLS_MINVER:
			if (JS_ValueToInt32(cx, *vp, &i)) {
				switch (i) {
					case 100:
					case 101:
					case 102:
						p->tls_minver = i;
						break;
				}
			}
			break;
		case SOCK_PROP_TLS_PSK:
			if (JS_ValueToObject(cx, *vp, &pskobj)) {
				if (p->tls_psk) {
					JS_RemoveObjectRoot(cx, &p->tls_psk);
					p->tls_psk = NULL;
				}
				p->tls_psk = pskobj;
				JS_AddObjectRoot(cx, &p->tls_psk);
			}
			break;
		case SOCK_PROP_TLS_NAMEVERIFY:
			JS_ValueToBoolean(cx, *vp, &b);
			p->tls_nameverify = b;
			break;
		case SOCK_PROP_TLS_CERTVERIFY:
			JS_ValueToBoolean(cx, *vp, &b);
			p->tls_disable_certverify = !b;
			break;
		case SOCK_PROP_TLS_CLIENTAUTH:
			if (p->session == -1) {
				JS_ValueToBoolean(cx, *vp, &b);
				p->tls_clientauth = b;
			}
			else {
				JS_ReportError(cx, "Cannot change TLS client auth after connection");
				return JS_FALSE;
			}
			break;
		case SOCK_PROP_TLS_ENHANCED_CERTCHECK:
			JS_ValueToBoolean(cx, *vp, &b);
			p->tls_enhanced_certcheck = b;
			break;
	}

	return JS_TRUE;
}

static JSBool js_socket_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
{
	jsval                idval;
	jsint                tiny;
	ulong                cnt;
	bool                 rd;
	bool                 wr;
	js_socket_private_t* p;
	JSString*            js_str;
	union xp_sockaddr    addr;
	socklen_t            len = sizeof(addr);
	jsrefcount           rc;
	char                 str[256];

	if ((p = (js_socket_private_t*)JS_GetPrivate(cx, obj)) == NULL) {
		// Protoype access
		return JS_TRUE;
	}

	JS_IdToValue(cx, id, &idval);
	tiny = JSVAL_TO_INT(idval);

	rc = JS_SUSPENDREQUEST(cx);
#if 0 /* just too much */
	dbprintf(false, p, "getting property %d", tiny);
#endif

	switch (tiny) {
		case SOCK_PROP_LAST_ERROR:
			*vp = INT_TO_JSVAL(p->last_error);
			break;
		case SOCK_PROP_ERROR_STR:
			if ((js_str = JS_NewStringCopyZ(cx, p->last_error_str)) == NULL)
				return JS_FALSE;
			*vp = STRING_TO_JSVAL(js_str);
			break;
		case SOCK_PROP_IS_CONNECTED:
			if (!p->is_connected)
				*vp = JSVAL_FALSE;
			else
				*vp = BOOLEAN_TO_JSVAL(socket_check(p->sock, NULL, NULL, 0));
			break;
		case SOCK_PROP_IS_WRITEABLE:
			if (p->sock == INVALID_SOCKET && p->set)
				wr = false;
			else
				socket_check(p->sock, NULL, &wr, 0);
			*vp = BOOLEAN_TO_JSVAL(wr);
			break;
		case SOCK_PROP_DATA_WAITING:
			if (p->sock == INVALID_SOCKET && p->set)
				rd = false;
			else {
				if (p->peeked)
					rd = true;
				else if (p->session != -1)
					rd = js_socket_peek_byte(cx, p);
				else
					socket_check(p->sock, &rd, NULL, 0);
			}
			*vp = BOOLEAN_TO_JSVAL(rd);
			break;
		case SOCK_PROP_NREAD:
			if (p->sock == INVALID_SOCKET && p->set) {
				*vp = JSVAL_ZERO;
				break;
			}
			cnt = 0;
			if (p->session != -1) {
				if (js_socket_peek_byte(cx, p))
					*vp = DOUBLE_TO_JSVAL((double)1);
				else
					*vp = JSVAL_ZERO;
			}
			else if (ioctlsocket(p->sock, FIONREAD, &cnt) == 0) {
				*vp = DOUBLE_TO_JSVAL((double)cnt);
			}
			else
				*vp = JSVAL_ZERO;
			break;
		case SOCK_PROP_DEBUG:
			*vp = INT_TO_JSVAL(p->debug);
			break;
		case SOCK_PROP_DESCRIPTOR:
			*vp = INT_TO_JSVAL(p->sock);
			break;
		case SOCK_PROP_NONBLOCKING:
			*vp = BOOLEAN_TO_JSVAL(p->nonblocking);
			break;
		case SOCK_PROP_LOCAL_IP:
			if (p->sock != INVALID_SOCKET) {
				if (getsockname(p->sock, (struct sockaddr *)&addr, &len) != 0)
					return JS_FALSE;
				JS_RESUMEREQUEST(cx, rc);
				inet_addrtop(&addr, str, sizeof(str));
				if ((js_str = JS_NewStringCopyZ(cx, str)) == NULL)
					return JS_FALSE;
				*vp = STRING_TO_JSVAL(js_str);
				rc = JS_SUSPENDREQUEST(cx);
			}
			else
				*vp = JSVAL_VOID;
			break;
		case SOCK_PROP_LOCAL_PORT:
			if (p->local_port != 0) {
				*vp = INT_TO_JSVAL(p->local_port);
			} else if (p->sock != INVALID_SOCKET) {
				if (getsockname(p->sock, &addr.addr, &len) != 0)
					return JS_FALSE;
				JS_RESUMEREQUEST(cx, rc);
				*vp = INT_TO_JSVAL(inet_addrport(&addr));
				rc = JS_SUSPENDREQUEST(cx);
			}
			else
				*vp = JSVAL_VOID;
			break;
		case SOCK_PROP_REMOTE_IP:
			if (p->is_connected) {
				JS_RESUMEREQUEST(cx, rc);
				inet_addrtop(&p->remote_addr, str, sizeof(str));
				if ((js_str = JS_NewStringCopyZ(cx, str)) == NULL)
					return JS_FALSE;
				*vp = STRING_TO_JSVAL(js_str);
				rc = JS_SUSPENDREQUEST(cx);
			}
			else
				*vp = JSVAL_VOID;
			break;
		case SOCK_PROP_REMOTE_PORT:
			if (p->is_connected)
				*vp = INT_TO_JSVAL(inet_addrport(&p->remote_addr));
			else
				*vp = JSVAL_VOID;
			break;
		case SOCK_PROP_TYPE:
			*vp = INT_TO_JSVAL(p->type);
			break;
		case SOCK_PROP_FAMILY:
			if (p->sock != INVALID_SOCKET) {
				JS_RESUMEREQUEST(cx, rc);
				if (getsockname(p->sock, &addr.addr, &len) != 0)
					return JS_FALSE;
				*vp = INT_TO_JSVAL(addr.addr.sa_family);
				rc = JS_SUSPENDREQUEST(cx);
			}
			else
				*vp = JSVAL_VOID;
			break;
		case SOCK_PROP_NETWORK_ORDER:
			*vp = BOOLEAN_TO_JSVAL(p->network_byte_order);
			break;
		case SOCK_PROP_SSL_SESSION:
			*vp = BOOLEAN_TO_JSVAL(p->session != -1);
			break;
		case SOCK_PROP_SSL_SERVER:
			*vp = BOOLEAN_TO_JSVAL(p->session != -1 && p->tls_server);
			break;
		case SOCK_PROP_TLS_MINVER:
			*vp = INT_TO_JSVAL(p->tls_minver);
			break;
		case SOCK_PROP_TLS_PSK:
			if (p->tls_psk == NULL)
				*vp = JSVAL_VOID;
			else
				*vp = OBJECT_TO_JSVAL(p->tls_psk);
			break;
		case SOCK_PROP_TLS_PSK_ID:
			if (p->tls_psk == NULL)
				*vp = JSVAL_VOID;
			else {
				int attrval;
				if ((cryptGetAttribute(p->session, CRYPT_SESSINFO_TLS_OPTIONS, &attrval) != CRYPT_OK)
				    || ((attrval & CRYPT_TLSOPTION_USED_PSK) == 0))
					*vp = JSVAL_VOID;
				else {
					if ((cryptGetAttributeString(p->session, CRYPT_SESSINFO_USERNAME, NULL, &attrval) == CRYPT_OK) && (attrval > 0)) {
						char *id = static_cast<char *>(malloc(attrval));
						if (id) {
							if (cryptGetAttributeString(p->session, CRYPT_SESSINFO_USERNAME, id, &attrval) == CRYPT_OK) {
								if ((js_str = JS_NewStringCopyN(cx, id, attrval)) == NULL) {
									free(id);
									return JS_FALSE;
								}
								*vp = STRING_TO_JSVAL(js_str);
							}
							free(id);
						}
					}
				}
			}
			break;
		case SOCK_PROP_TLS_NAMEVERIFY:
			*vp = BOOLEAN_TO_JSVAL(p->tls_nameverify);
			break;
		case SOCK_PROP_TLS_CERTVERIFY:
			*vp = BOOLEAN_TO_JSVAL(!p->tls_disable_certverify);
			break;
		case SOCK_PROP_TLS_CLIENTAUTH:
			*vp = BOOLEAN_TO_JSVAL(p->tls_clientauth);
			break;
		case SOCK_PROP_TLS_ENHANCED_CERTCHECK:
			*vp = BOOLEAN_TO_JSVAL(!p->tls_disable_certverify);
			break;
	}

	JS_RESUMEREQUEST(cx, rc);
	return true;
}

#define SOCK_PROP_FLAGS JSPROP_ENUMERATE | JSPROP_READONLY

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 */
	{   "error_str", SOCK_PROP_ERROR_STR, SOCK_PROP_FLAGS,   318 },
	{   "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 },
	{   "family", SOCK_PROP_FAMILY, SOCK_PROP_FLAGS,   318 },
	{   "network_byte_order", SOCK_PROP_NETWORK_ORDER, JSPROP_ENUMERATE,  311 },
	{   "ssl_session", SOCK_PROP_SSL_SESSION, JSPROP_ENUMERATE,  316 },
	{   "ssl_server", SOCK_PROP_SSL_SERVER, JSPROP_ENUMERATE,  316 },
	{   "tls_minver", SOCK_PROP_TLS_MINVER, JSPROP_ENUMERATE,  320 },
	{   "tls_psk", SOCK_PROP_TLS_PSK, JSPROP_ENUMERATE,  32002 },
	{   "tls_psk_id", SOCK_PROP_TLS_PSK_ID, JSPROP_ENUMERATE | JSPROP_READONLY,  32002 },
	{   "tls_nameverify", SOCK_PROP_TLS_NAMEVERIFY, JSPROP_ENUMERATE,  32101 },
	{   "tls_certverify", SOCK_PROP_TLS_CERTVERIFY, JSPROP_ENUMERATE,  32101 },
	{   "tls_client_auth", SOCK_PROP_TLS_CLIENTAUTH, JSPROP_ENUMERATE,  32101 },
	{   "tls_enhanced_certcheck", SOCK_PROP_TLS_ENHANCED_CERTCHECK, JSPROP_ENUMERATE,  32101 },
	{0}
};

static jsSyncMethodSpec js_socket_functions[] = {
	{"close",       js_close,       0,  JSTYPE_VOID,    ""
	 , JSDOCSTR("Close (shutdown) the socket immediately")
	 , 310},
	{"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=10] [,callback]")
	 , 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</i> (seconds).  Instead of a timeout, you can pass a <i>callback</i> "
		        "which is called when the connection completes with the Socket object as the only parameter.")
	 , 311},
	{"listen",      js_listen,      0,  JSTYPE_BOOLEAN, JSDOCSTR("")
	 , JSDOCSTR("Place socket in a state to listen for incoming connections (use before an accept)")
	 , 310},
	{"accept",      js_accept,      0,  JSTYPE_OBJECT,  JSDOCSTR("")
	 , JSDOCSTR("Accept an incoming connection, returns a new <i>Socket</i> object representing the new connection")
	 , 310},
	{"write",       js_send,        1,  JSTYPE_ALIAS },
	{"send",        js_send,        1,  JSTYPE_NUMBER,  JSDOCSTR("data")
	 , JSDOCSTR("Send a string (AKA write).  Returns the number of bytes sent or undefined if an error occured.  "
		        "Versions before 3.17 returned a bool true if all bytes were sent and false otherwise.")
	 , 310},
	{"writeln",     js_sendline,        1,  JSTYPE_ALIAS },
	{"sendline",    js_sendline,        1,  JSTYPE_BOOLEAN, JSDOCSTR("data")
	 , JSDOCSTR("Send a string (AKA write) with a carriage return line feed appended")
	 , 317},
	{"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")
	 , 310},
	{"sendfile",    js_sendfile,    1,  JSTYPE_BOOLEAN, JSDOCSTR("path/filename")
	 , JSDOCSTR("Send an entire file over the socket")
	 , 310},
	{"writeBin",    js_sendbin,     1,  JSTYPE_ALIAS },
	{"sendBin",     js_sendbin,     1,  JSTYPE_BOOLEAN, JSDOCSTR("value [,bytes=4]")
	 , JSDOCSTR("Send a binary integer over the socket, default number of bytes is 4 (32-bits)")
	 , 311},
	{"read",        js_recv,        1,  JSTYPE_ALIAS },
	{"recv",        js_recv,        1,  JSTYPE_STRING,  JSDOCSTR("[maxlen=512, [timeout_sec=120]]")
	 , JSDOCSTR("Receive a string, default maxlen is 512 characters (AKA read)")
	 , 310},
	{"peek",        js_peek,        0,  JSTYPE_STRING,  JSDOCSTR("[maxlen=512]")
	 , JSDOCSTR("Receive a string, default maxlen is 512 characters, leaves string in receive buffer (TLS sockets will never return more than one byte)")
	 , 310},
	{"readline",    js_recvline,    0,  JSTYPE_ALIAS },
	{"readln",      js_recvline,    0,  JSTYPE_ALIAS },
	{"recvline",    js_recvline,    0,  JSTYPE_STRING,  JSDOCSTR("[maxlen=512] [,timeout=30]")
	 , JSDOCSTR("Receive a line-feed terminated string, default maxlen is 512 characters, default timeout is 30 seconds (AKA readline and readln)")
	 , 310},
	{"recvfrom",    js_recvfrom,    0,  JSTYPE_OBJECT,  JSDOCSTR("[binary=false] [,maxlen=512 or int_size=4]")
	 , JSDOCSTR("Receive data (string or integer) from a socket (typically UDP)"
		        "<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 <tt>false</tt>, <i>maxlen</i> defaults to 512 chars, <i>int_size</i> defaults to 4 bytes (32-bits)")
	 , 311},
	{"readBin",     js_recvbin,     0,  JSTYPE_ALIAS },
	{"recvBin",     js_recvbin,     0,  JSTYPE_NUMBER,  JSDOCSTR("[bytes=4]")
	 , JSDOCSTR("Receive a binary integer from the socket, default number of bytes is 4 (32-bits)")
	 , 311},
	{"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")
	 , 310},
	{"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")
	 , 310},
	{"ioctl",       js_ioctlsocket, 1,  JSTYPE_NUMBER,  JSDOCSTR("command [,argument=0]")
	 , JSDOCSTR("Send socket IOCTL (advanced)")
	 , 310},
	{"poll",        js_poll,        1,  JSTYPE_NUMBER,  JSDOCSTR("[timeout=0] [,write=false]")
	 , JSDOCSTR("Poll socket for read or write ability (default is <i>read</i>), "
		        "default timeout value is 0.0 seconds (immediate timeout) "
		        "returns -1 on error, 0 if the timeout passes without the event triggering, and 1 if the socket is ready")
	 , 310},
	{"on",      js_on,      2,  JSTYPE_NUMBER,  JSDOCSTR("('read' | 'write'), callback")
	 , JSDOCSTR("Execute callback whenever socket is readable/writable.  Returns an id to be passed to <tt>js.clearOn()</tt>")
	 , 31900},
	{"once",    js_once,    2,  JSTYPE_NUMBER,  JSDOCSTR("('read' | 'write'), callback")
	 , JSDOCSTR("Execute callback next time socket is readable/writable  Returns and id to be passed to <tt>js.clearOnce()</tt>")
	 , 31900},
	{"clearOn", js_clearOn, 2,  JSTYPE_NUMBER,  JSDOCSTR("('read' | 'write'), id")
	 , JSDOCSTR("Remove callback installed by Socket.on()")
	 , 31900},
	{"clearOnce",   js_clearOnce,   2,  JSTYPE_NUMBER,  JSDOCSTR("('read' | 'write'), id")
	 , JSDOCSTR("Remove callback installed by Socket.once()")
	 , 31900},
	{0}
};

static JSBool js_socket_resolve(JSContext *cx, JSObject *obj, jsid id)
{
	char*  name = NULL;
	JSBool ret;

	if (id != JSID_VOID && id != JSID_EMPTY) {
		jsval idval;

		JS_IdToValue(cx, id, &idval);
		if (JSVAL_IS_STRING(idval)) {
			JSSTRING_TO_MSTRING(cx, JSVAL_TO_STRING(idval), name, NULL);
			HANDLE_PENDING(cx, name);
		}
	}

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

static JSBool js_socket_enumerate(JSContext *cx, JSObject *obj)
{
	return js_socket_resolve(cx, obj, JSID_VOID);
}

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		*/
};

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;
	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) */

JSObject* js_CreateSocketObjectWithoutParent(JSContext* cx, SOCKET sock, CRYPT_CONTEXT session)
{
	JSObject*            obj;
	js_socket_private_t* p;
	int                  type = SOCK_STREAM;
	socklen_t            len;

	obj = JS_NewObject(cx, &js_socket_class, NULL, NULL);

	if (obj == NULL)
		return NULL;

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

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

	if ((p = (js_socket_private_t*)malloc(sizeof(js_socket_private_t))) == NULL)
		return NULL;
	memset(p, 0, sizeof(js_socket_private_t));

	p->sock = sock;
	p->external = true;
	p->network_byte_order = true;
	p->session = session;
	p->unflushed = 0;
	p->tls_minver = 102;

	if (p->sock != INVALID_SOCKET) {
		len = sizeof(p->remote_addr);
		if (getpeername(p->sock, &p->remote_addr.addr, &len) == 0)
			p->is_connected = true;
	}

	if (!JS_SetPrivate(cx, obj, p)) {
		dbprintf(true, p, "JS_SetPrivate failed");
		return NULL;
	}

	dbprintf(false, p, "object created");

	return obj;
}

static bool
handle_addrs(char *host, struct sockaddr_in *addr4, socklen_t *addr4len, struct sockaddr_in6 *addr6, socklen_t *addr6len)
{
	in_addr_t         ia;
	union xp_sockaddr ia6;
	char *            p, *p2;
	struct addrinfo   hints;
	struct addrinfo * res = NULL;
	struct addrinfo * cur;
	int               i;

	// First, clean up for host:port style...
	p = strrchr(host, ':');
	/*
	 * If there isn't a [, and the first and last colons aren't the same
	 * it's assumed to be an IPv6 address
	 */
	if (host[0] != '[' && p != NULL && strchr(host, ':') != p)
		p = NULL;
	if (host[0] == '[') {
		host++;
		p2 = strrchr(host, ']');
		if (p2)
			*p2 = 0;
		if (p2 > p)
			p = NULL;
	}
	if (p != NULL)
		*p = 0;

	ia = parseIPv4Address(host);
	if (ia != INADDR_NONE) {
		if (*addr4len == 0) {
			addr4->sin_addr.s_addr = ia;
			*addr4len = sizeof(struct sockaddr_in);
		}
		return true;
	}

	if (inet_ptoaddr(host, &ia6, sizeof(ia6)) != NULL) {
		if (*addr6len == 0) {
			addr6->sin6_addr = ia6.in6.sin6_addr;
			*addr6len = sizeof(struct sockaddr_in6);
		}
		return true;
	}

	// So it's a hostname... resolve it into addr4 and addr6 if possible.
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	hints.ai_flags = AI_ADDRCONFIG | AI_PASSIVE;
	if ((i = getaddrinfo(host, NULL, &hints, &res)) != 0)
		return false;
	for (cur = res; cur && (*addr4len == 0 || *addr6len == 0); cur = cur->ai_next) {
		switch (cur->ai_family) {
			case AF_INET:
				if (*addr4len == 0) {
					addr4->sin_addr = ((struct sockaddr_in *)cur->ai_addr)->sin_addr;
					*addr4len = sizeof(struct sockaddr_in);
				}
				break;
			case AF_INET6:
				if (*addr6len == 0) {
					addr6->sin6_addr = ((struct sockaddr_in6 *)cur->ai_addr)->sin6_addr;
					*addr6len = sizeof(struct sockaddr_in6);
				}
				break;
		}
	}
	freeaddrinfo(res);

	return true;
}

static JSBool
js_connected_socket_constructor(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *           obj;
	JSObject *           ao;
	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;
	scfg_t *             scfg;
	char                 error[256];
	uint16_t             bindport = 0;
	struct sockaddr_in   addr4;
	struct sockaddr_in6  addr6;
	socklen_t            addr4len;
	socklen_t            addr6len;
	struct sockaddr *    addr;
	socklen_t *          addrlen;
	bool                 sockbind = false;
	jsuint               count;
	int32                timeout = 10;

	scfg = static_cast<scfg_t *>(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, "timeout", &v) && !JSVAL_IS_VOID(v)) {
			if (!JS_ValueToInt32(cx, v, &timeout)) {
				JS_ReportError(cx, "Invalid timeout 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);
		}
		if (JS_GetProperty(cx, obj, "bindport", &v) && !JSVAL_IS_VOID(v)) {
			bindport = js_port(cx, v, type);
			memset(&addr4, 0, sizeof(addr4));
			addr4.sin_family = AF_INET;
			addr4.sin_addr.s_addr = INADDR_ANY;
			addr4len = sizeof(addr4);
			addr4.sin_port = htons(bindport);
			memset(&addr6, 0, sizeof(addr6));
			addr6.sin6_family = AF_INET6;
			addr6.sin6_addr = in6addr_any;
			addr6len = sizeof(addr6);
			addr6.sin6_port = htons(bindport);
			sockbind = true;
		}
		if (JS_GetProperty(cx, obj, "bindaddrs", &v) && !JSVAL_IS_VOID(v)) {
			if (!sockbind) {
				memset(&addr4, 0, sizeof(addr4));
				addr4.sin_family = AF_INET;
				addr4.sin_addr.s_addr = INADDR_ANY;
				addr4.sin_port = htons(bindport);
				memset(&addr6, 0, sizeof(addr6));
				addr6.sin6_family = AF_INET6;
				addr6.sin6_addr = in6addr_any;
				addr6.sin6_port = htons(bindport);
				sockbind = true;
			}
			addr4len = 0;
			addr6len = 0;
			if (JSVAL_IS_OBJECT(v)) {
				ao = JSVAL_TO_OBJECT(v);
				if (ao == NULL || !JS_IsArrayObject(cx, ao)) {
					JS_ReportError(cx, "Invalid bindaddrs list");
					goto fail;
				}
				if (!JS_GetArrayLength(cx, ao, &count)) {
					JS_ReportError(cx, "Unable to get bindaddrs length");
					goto fail;
				}
				for (i = 0; i < count; i++) {
					if (!JS_GetElement(cx, ao, i, &v)) {
						JS_ReportError(cx, "Invalid bindaddrs entry");
						goto fail;
					}
					JSVALUE_TO_MSTRING(cx, v, host, NULL);
					HANDLE_PENDING(cx, host);
					rc = JS_SUSPENDREQUEST(cx);
					if (!handle_addrs(host, &addr4, &addr4len, &addr6, &addr6len)) {
						JS_RESUMEREQUEST(cx, rc);
						JS_ReportError(cx, "Unparsable bindaddrs entry");
						goto fail;
					}
					FREE_AND_NULL(host);
					JS_RESUMEREQUEST(cx, rc);
				}
			}
			else {
				JSVALUE_TO_MSTRING(cx, v, host, NULL);
				HANDLE_PENDING(cx, host);
				rc = JS_SUSPENDREQUEST(cx);
				if (!handle_addrs(host, &addr4, &addr4len, &addr6, &addr6len)) {
					JS_RESUMEREQUEST(cx, rc);
					JS_ReportError(cx, "Unparsable bindaddrs entry");
					goto fail;
				}
				FREE_AND_NULL(host);
				JS_RESUMEREQUEST(cx, rc);
			}
		}
	}
	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))) {
				freeaddrinfo(res);
				JS_RESUMEREQUEST(cx, rc);
				JS_ReportError(cx, error);
				goto fail;
			}
			if (sockbind) {
				addr = NULL;
				switch (cur->ai_family) {
					case PF_INET:
						addr = (struct sockaddr *)&addr4;
						addrlen = &addr4len;
						break;
					case PF_INET6:
						addr = (struct sockaddr *)&addr6;
						addrlen = &addr6len;
						break;
				}
				if (addr == NULL)
					continue;
				if (*addrlen == 0)
					continue;
				if (bind(p->sock, addr, *addrlen) != 0) {
					lprintf(LOG_WARNING, "Unable to bind to local address");
					closesocket(p->sock);
					p->sock = INVALID_SOCKET;
					continue;
				}
			}
			/* Set to non-blocking for the connect */
			nonblock = -1;
			ioctlsocket(p->sock, FIONBIO, (u_long *)&nonblock);
		}

		if (connect(p->sock, cur->ai_addr, cur->ai_addrlen)) {
			switch (SOCKET_ERRNO) {
				case EINPROGRESS:
				case EINTR:
				case EAGAIN:
#if (EAGAIN != EWOULDBLOCK)
				case EWOULDBLOCK:
#endif
					for (; p->sock != INVALID_SOCKET;) {
						// TODO: Do clever timeout stuff here.
						if (socket_writable(p->sock, timeout * 1000)) {
							if (!socket_recvdone(p->sock, 0))
								goto connected;
							closesocket(p->sock);
							p->sock = INVALID_SOCKET;
							continue;
						}
						else {
							closesocket(p->sock);
							p->sock = INVALID_SOCKET;
							continue;
						}
					}

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 to %s:%u", host, port);
		goto fail;
	}
	ioctlsocket(p->sock, FIONBIO, (ulong*)&(p->nonblocking));
	call_socket_open_callback(true);
	free(protocol);
	p->hostname = host;
	p->type = type;
	p->network_byte_order = true;
	p->session = -1;
	p->unflushed = 0;
	p->is_connected = true;
	p->tls_minver = 102;

	if (!JS_SetPrivate(cx, obj, p)) {
		JS_ReportError(cx, "JS_SetPrivate failed");
		closesocket(p->sock);
		free(p);
		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>, timeout:<i>timeout</i>, bindport:<i>port</i>, bindaddrs:<i>bindaddrs</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>"
	                           "<i>protocol</i> (optional) = the name of the protocol or service the socket is to be used for<br>"
	                           "<i>timeout</i> (optional) = 10 (default) the number of seconds to wait for each connect() call to complete.<br>"
	                           "<i>bindport</i> (optional) = the name or number of the source port to bind to<br>"
	                           "<i>bindaddrs</i> (optional) = the name or number of the source addresses to bind to.  The first of each IPv4 or IPv6 type is used for that family.<br>"
	                           );
	JS_DefineProperty(cx, obj, "_dont_document", JSVAL_TRUE, NULL, NULL, JSPROP_READONLY);
#endif

	if (!js_DefineSocketOptionsArray(cx, obj, type)) {
		JS_ReportError(cx, "js_DefineSocketOptionsArray failed");
		closesocket(p->sock);
		free(p);
		return JS_FALSE;
	}

	dbprintf(false, p, "object constructed");
	return JS_TRUE;

fail:
	free(p);
	free(protocol);
	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 = static_cast<ls_cb_data *>(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);
}

void sock_close_cb(SOCKET sock, void *cb_data)
{
	call_socket_open_callback(false);
}

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 = NULL;
	struct ls_cb_data    cb;
	jsuint               count;
	int                  i;

	scfg = static_cast<scfg_t *>(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 three 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, NULL, 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)) {
			JS_ReportError(cx, "zero-length array");
			goto fail;
		}
		for (i = 0; (jsuint)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, NULL, 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");
		free(protocol);
		xpms_destroy(set, sock_close_cb, nullptr);
		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;
	p->local_port = port;
	p->tls_minver = 102;

	if (!JS_SetPrivate(cx, obj, p)) {
		JS_ReportError(cx, "JS_SetPrivate failed");
		xpms_destroy(set, sock_close_cb, nullptr);
		free(p);
		return JS_FALSE;
	}

	if (!js_DefineSocketOptionsArray(cx, obj, type)) {
		JS_ReportError(cx, "js_DefineSocketOptionsArray failed");
		xpms_destroy(set, sock_close_cb, nullptr);
		free(p);
		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>AF_INET</tt> for IPv4, or <tt>AF_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:
	xpms_destroy(set, sock_close_cb, nullptr);
	free(protocol);
	return JS_FALSE;
}

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 */
	int                  domain = AF_INET; /* default = IPv4 */
	uintN                i;
	js_socket_private_t* p;
	char*                protocol = NULL;
	bool                 from_descriptor = false;

	i = 0;
	if (JSVAL_IS_BOOLEAN(argv[i]) && argc > 1) {
		from_descriptor = JSVAL_TO_BOOLEAN(argv[i]);
		i++;
		if (from_descriptor) {
			uint32 sock;
			if (!JS_ValueToECMAUint32(cx, argv[i], &sock)) {
				JS_ReportError(cx, "Failed to convert socket descriptor to uint32");
				return JS_FALSE;
			}
			obj = js_CreateSocketObjectWithoutParent(cx, sock, -1);
			if (obj == NULL) {
				JS_ReportError(cx, "Failed to create external socket object");
				return JS_FALSE;
			}
			JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(obj));
			FREE_AND_NULL(protocol);
			return JS_TRUE;
		}
	}

	for (; i < argc; i++) {
		if (JSVAL_IS_NUMBER(argv[i])) {
			JS_ValueToInt32(cx, argv[i], &type);
		}
		else if (JSVAL_IS_BOOLEAN(argv[i])) {
			if (argv[i] == JSVAL_TRUE)
				domain = AF_INET6;
		}
		else if (JSVAL_IS_STRING(argv[i])) {
			if (protocol == NULL) {
				JSVALUE_TO_MSTRING(cx, argv[i], protocol, NULL);
				HANDLE_PENDING(cx, protocol);
			}
		}
	}

	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");
		free(protocol);
		return JS_FALSE;
	}
	memset(p, 0, sizeof(js_socket_private_t));

	if ((p->sock = open_socket(domain, type, protocol)) == INVALID_SOCKET) {
		JS_ReportError(cx, "open_socket failed with error %d", SOCKET_ERRNO);
		free(protocol);
		free(p);
		return JS_FALSE;
	}
	free(protocol);
	p->type = type;
	p->network_byte_order = true;
	p->session = -1;
	p->unflushed = 0;
	p->tls_minver = 102;

	if (!JS_SetPrivate(cx, obj, p)) {
		JS_ReportError(cx, "JS_SetPrivate failed");
		closesocket(p->sock);
		free(p);
		return JS_FALSE;
	}

#ifdef BUILD_JSDOCS
	js_DescribeSyncObject(cx, obj, "Class used for TCP/IP socket communications", 310);
	js_DescribeSyncConstructor(cx, obj, "To create a new Socket object: "
	                           "<tt>load('sockdefs.js'); var s = new Socket(<i>type</i>, <i>protocol</i> ,<i>ipv6</i>=false)</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<br>"
	                           "To create a socket from an existing socket descriptor: "
	                           "<tt>var s = new Socket(true, <i>descriptor</i>)</tt><br>"
	                           "where <i>descriptor</i> is the numerical value of an existing valid socket descriptor"
	                           );
	js_CreateArrayOfStrings(cx, obj, "_property_desc_list", socket_prop_desc, JSPROP_READONLY);
#endif

	if (!js_DefineSocketOptionsArray(cx, obj, type)) {
		JS_ReportError(cx, "js_DefineSocketOptionsArray failed");
		closesocket(p->sock);
		free(p);
		return JS_FALSE;
	}

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

JSObject* js_CreateSocketClass(JSContext* cx, JSObject* parent)
{
	JSObject* sockobj;
	JSObject* sockproto;
	JSObject* csockobj;
	JSObject* lsockobj;
	jsval     val;
	JSObject* constructor;

	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 */
	                       , NULL, NULL);
	if (sockobj == NULL)
		return sockobj;
	if (JS_GetProperty(cx, parent, js_socket_class.name, &val) && !JSVAL_NULL_OR_VOID(val)) {
		JS_ValueToObject(cx, val, &constructor);
		JS_DefineProperty(cx, constructor, "AF_INET", INT_TO_JSVAL(AF_INET), NULL, NULL
		                  , JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY);
		JS_DefineProperty(cx, constructor, "AF_INET6", INT_TO_JSVAL(AF_INET6), NULL, NULL
		                  , JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY);
		JS_DefineProperty(cx, constructor, "IPPROTO_IP", INT_TO_JSVAL(IPPROTO_IP), NULL, NULL
		                  , JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY);
		JS_DefineProperty(cx, constructor, "PF_INET", INT_TO_JSVAL(PF_INET), NULL, NULL
		                  , JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY);
		JS_DefineProperty(cx, constructor, "PF_INET6", INT_TO_JSVAL(PF_INET6), NULL, NULL
		                  , JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY);
		JS_DefineProperty(cx, constructor, "PF_UNSPEC", INT_TO_JSVAL(PF_UNSPEC), NULL, NULL
		                  , JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY);
		JS_DefineProperty(cx, constructor, "SOCK_DGRAM", INT_TO_JSVAL(SOCK_STREAM), NULL, NULL
		                  , JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY);
		JS_DefineProperty(cx, constructor, "SOCK_STREAM", INT_TO_JSVAL(SOCK_STREAM), NULL, NULL
		                  , JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY);
	}
	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);
	(void)csockobj;
	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);
	(void)lsockobj;

	return sockobj;
}

JSObject* js_CreateSocketObject(JSContext* cx, JSObject* parent, const char *name, SOCKET sock, CRYPT_CONTEXT session)
{
	JSObject* obj;

	obj = js_CreateSocketObjectWithoutParent(cx, sock, session);
	if (obj == NULL)
		return NULL;
	JS_DefineProperty(cx, parent, name, OBJECT_TO_JSVAL(obj), NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY);

	return obj;
}

JSObject* js_CreateSocketObjectFromSet(JSContext* cx, JSObject* parent, const char *name, struct xpms_set *set)
{
	JSObject*            obj;
	js_socket_private_t* p;
	int                  type = SOCK_STREAM;
	socklen_t            len;

	obj = JS_DefineObject(cx, parent, name, &js_socket_class, NULL
	                      , JSPROP_ENUMERATE | JSPROP_READONLY);

	if (obj == NULL)
		return NULL;

	if (set->sock_count < 1)
		return NULL;

	len = sizeof(type);
	getsockopt(set->socks[0].sock, SOL_SOCKET, SO_TYPE, (char *)&type, &len);

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

	if ((p = (js_socket_private_t*)malloc(sizeof(js_socket_private_t))) == NULL)
		return NULL;
	memset(p, 0, sizeof(js_socket_private_t));

	p->set = set;
	p->sock = INVALID_SOCKET;
	p->external = true;
	p->network_byte_order = true;
	p->session = -1;
	p->unflushed = 0;
	p->tls_minver = 102;

	if (!JS_SetPrivate(cx, obj, p)) {
		dbprintf(true, p, "JS_SetPrivate failed");
		return NULL;
	}

	dbprintf(false, p, "object created");

	return obj;
}

#endif  /* JAVSCRIPT */
