/*
Copyright 2006 - 2018 Intel Corporation

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

	http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#include "duktape.h"

#include "ILibDuktape_net.h"
#include "ILibDuktape_Helpers.h"
#include "ILibDuktape_DuplexStream.h"
#include "ILibDuktapeModSearch.h"
#include "ILibDuktape_EventEmitter.h"
#include "ILibDuktape_ChildProcess.h"
#include "microstack/ILibAsyncSocket.h"
#include "microstack/ILibCrypto.h"
#include "microstack/ILibAsyncServerSocket.h"
#include "microstack/ILibRemoteLogging.h"
#include "microstack/ILibProcessPipe.h"

#ifdef _POSIX
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#endif

#ifdef WIN32
#include <accctrl.h>
#include <AclAPI.h>
#endif

typedef struct ILibDuktape_net_socket
{
	duk_context *ctx;
	ILibAsyncSocket_SocketModule socketModule;
	void *object;
	void *net;
	void *duplexStream;
	void *chain;
	void *OnSetTimeout;
	int unshiftBytes;
	ILibDuktape_EventEmitter *emitter;
#ifndef MICROSTACK_NOTLS
	SSL_CTX *ssl_ctx;
	SSL *ssl;
#endif
}ILibDuktape_net_socket;

typedef struct ILibDuktape_net_server
{
	duk_context *ctx;
	void *self;
	ILibAsyncServerSocket_ServerModule server;
	ILibDuktape_EventEmitter *emitter;
	int isTLS;
}ILibDuktape_net_server;
typedef struct ILibDuktape_net_server_session
{
	duk_context *ctx;
	void *self;
	ILibAsyncServerSocket_ConnectionToken connection;
	ILibDuktape_EventEmitter *emitter;
	ILibDuktape_DuplexStream *stream;

	int unshiftBytes;
}ILibDuktape_net_server_session;

int ILibDuktape_TLS_ctx2socket = -1;
int ILibDuktape_TLS_ctx2server = -1;

#ifdef WIN32
#define ILibDuktape_net_IPC_BUFFERSIZE	4096
typedef struct ILibDuktape_net_WindowsIPC
{
	ILibProcessPipe_Manager manager;
	duk_context *ctx;
	void *mServer, *mSocket;
	HANDLE mPipeHandle;
	ILibProcessPipe_Pipe mPipe;

	OVERLAPPED overlapped;
	ILibDuktape_DuplexStream *ds;

	int processingRead;
	char *buffer;
	int bufferLength;
	int bufferOffset;
	int bytesLeft;
	int unshiftedBytes;
}ILibDuktape_net_WindowsIPC;
#endif

#define ILibDuktape_SecureContext2CertBuffer	"\xFF_SecureContext2CertBuffer"
#define ILibDuktape_SecureContext2SSLCTXPTR		"\xFF_SecureContext2SSLCTXPTR"
#define ILibDuktape_GlobalTunnel_DataPtr		"\xFF_GlobalTunnel_DataPtr"
#define ILibDuktape_GlobalTunnel_Stash			"global-tunnel"
#define ILibDuktape_net_Server_buffer			"\xFF_FixedBuffer"
#define ILibDuktape_net_Server_Session_buffer	"\xFF_SessionFixedBuffer"
#define ILibDuktape_net_socket_ptr				"\xFF_SocketPtr"
#define ILibDuktape_net_WindowsIPC_Buffer		"\xFF_WindowsIPC"
#define ILibDuktape_net_WindowsIPC_PendingArray "\xFF_WindowsIPC_PendingArray"
#define ILibDuktape_SERVER2ContextTable			"\xFF_Server2ContextTable"
#define ILibDuktape_SERVER2OPTIONS				"\xFF_ServerToOptions"
#define ILibDuktape_SERVER2LISTENOPTIONS		"\xFF_ServerToListenOptions"
#define ILibDuktape_TLSSocket2SecureContext		"\xFF_TLSSocket2SecureContext"

extern void ILibAsyncServerSocket_RemoveFromChain(ILibAsyncServerSocket_ServerModule serverModule);

// Prototypes
void ILibDuktape_net_socket_PUSH(duk_context *ctx, ILibAsyncSocket_SocketModule module);
#ifndef MICROSTACK_NOTLS
duk_ret_t ILibDuktape_tls_server_addContext(duk_context *ctx);
#endif

#ifdef WIN32
ILibTransport_DoneState ILibDuktape_net_server_IPC_WriteSink(ILibDuktape_DuplexStream *stream, char *buffer, int bufferLen, void *user);
void ILibDuktape_net_server_IPC_EndSink(ILibDuktape_DuplexStream *stream, void *user);
void ILibDuktape_net_server_IPC_PauseSink(ILibDuktape_DuplexStream *sender, void *user);
void ILibDuktape_net_server_IPC_ResumeSink(ILibDuktape_DuplexStream *sender, void *user);
int ILibDuktape_net_server_IPC_unshiftSink(ILibDuktape_DuplexStream *sender, int unshiftBytes, void *user);
duk_ret_t ILibDuktape_net_server_IPC_ConnectSink_Finalizer(duk_context *ctx);
#endif


void ILibDuktape_net_socket_OnData(ILibAsyncSocket_SocketModule socketModule, char* buffer, int *p_beginPointer, int endPointer, ILibAsyncSocket_OnInterrupt* OnInterrupt, void **user, int *PAUSE)
{
	ILibDuktape_net_socket *ptrs = (ILibDuktape_net_socket*)((ILibChain_Link*)socketModule)->ExtraMemoryPtr;
	if (ILibDuktape_DuplexStream_WriteData((ILibDuktape_DuplexStream*)ptrs->duplexStream, buffer + *p_beginPointer, endPointer - *p_beginPointer) != 0) 
	{ 
		*PAUSE = 1; 
	}

	*p_beginPointer = endPointer - ptrs->unshiftBytes;
	ptrs->unshiftBytes = 0;
}
void ILibDuktape_net_socket_OnConnect(ILibAsyncSocket_SocketModule socketModule, int Connected, void *user)
{
	ILibDuktape_net_socket *ptrs = (ILibDuktape_net_socket*)((ILibChain_Link*)socketModule)->ExtraMemoryPtr;
	struct sockaddr_in6 local;
	if (ptrs->ctx == NULL) { return; }

	duk_push_heapptr(ptrs->ctx, ptrs->object);					// [sockat]
	duk_push_false(ptrs->ctx);									// [socket][connecting]
	duk_put_prop_string(ptrs->ctx, -2, "connecting");			// [socket]
	duk_pop(ptrs->ctx);											// ...

	if (Connected != 0)
	{
		duk_push_heapptr(ptrs->ctx, ptrs->object);																		// [sock]
		if (ILibAsyncSocket_IsDomainSocket(socketModule) == 0)
		{
			ILibAsyncSocket_GetLocalInterface(socketModule, (struct sockaddr*)&local);
			duk_push_string(ptrs->ctx, ILibInet_ntop2((struct sockaddr*)&local, ILibScratchPad, sizeof(ILibScratchPad)));	// [sock][localAddr]
			duk_put_prop_string(ptrs->ctx, -2, "localAddress");																// [sock]
			duk_push_int(ptrs->ctx, (int)ntohs(local.sin6_port));															// [sock][port]
			duk_put_prop_string(ptrs->ctx, -2, "localPort");																// [sock]

			ILibAsyncSocket_GetRemoteInterface(socketModule, (struct sockaddr*)&local);
			duk_push_string(ptrs->ctx, ILibInet_ntop2((struct sockaddr*)&local, ILibScratchPad, sizeof(ILibScratchPad)));	// [sock][remoteAddr]
			duk_put_prop_string(ptrs->ctx, -2, "remoteAddress");															// [sock]
			duk_push_string(ptrs->ctx, local.sin6_family == AF_INET6 ? "IPv6" : "IPv4");									// [sock][remoteFamily]
			duk_put_prop_string(ptrs->ctx, -2, "remoteFamily");																// [sock]
			duk_push_int(ptrs->ctx, (int)ntohs(local.sin6_port));															// [sock][remotePort]
			duk_put_prop_string(ptrs->ctx, -2, "remotePort");																// [sock]
		}
		else
		{
			duk_get_prop_string(ptrs->ctx, -1, "remoteHost");																// [sock][remoteHost]
			duk_put_prop_string(ptrs->ctx, -2, "path");																		// [sock]
		}
		duk_pop(ptrs->ctx);																								// ...

#ifndef MICROSTACK_NOTLS
		if (ptrs->ssl != NULL)
		{
			duk_push_heapptr(ptrs->ctx, ptrs->object);									// [socket]
			duk_get_prop_string(ptrs->ctx, -1, "emit");									// [socket][emit]
			duk_swap_top(ptrs->ctx, -2);												// [emit][this]
			duk_push_string(ptrs->ctx, "secureConnect");								// [emit][this][secureConnect]
			if (duk_pcall_method(ptrs->ctx, 1) != 0) { ILibDuktape_Process_UncaughtExceptionEx(ptrs->ctx, "tls.socket.OnSecureConnect(): "); }
			duk_pop(ptrs->ctx);															// ...
			return;
		}
#endif
		duk_push_heapptr(ptrs->ctx, ptrs->object);					// [this]
		duk_get_prop_string(ptrs->ctx, -1, "emit");					// [this][emit]
		duk_swap_top(ptrs->ctx, -2);								// [emit][this]
		duk_push_string(ptrs->ctx, "connect");						// [emit][this][connect]
		if (duk_pcall_method(ptrs->ctx, 1) != 0) { ILibDuktape_Process_UncaughtException(ptrs->ctx); }
		duk_pop(ptrs->ctx);											// ...
	}
	else 
	{
		duk_push_heapptr(ptrs->ctx, ptrs->object);					// [this]
		duk_get_prop_string(ptrs->ctx, -1, "emit");					// [this][emit]
		duk_swap_top(ptrs->ctx, -2);								// [emit][this]
		duk_push_string(ptrs->ctx, "error");						// [emit][this][error]
		duk_push_object(ptrs->ctx);									// [emit][this][error][errorObj]
#ifndef MICROSTACK_NOTLS
		if (ptrs->ssl != NULL && ILibAsyncSocket_TLS_WasHandshakeError(socketModule))
		{
			duk_push_string(ptrs->ctx, "TLS Handshake Error");		// [emit][this][error][errorObj][msg]
		}
		else
		{
			duk_push_string(ptrs->ctx, "Connection Failed");		// [emit][this][error][errorObj][msg]
		}
#else
		duk_push_string(ptrs->ctx, "Connection Failed");			// [emit][this][error][errorObj][msg]
#endif
		duk_put_prop_string(ptrs->ctx, -2, "message");				// [emit][this][error][errorObj]
		if (duk_pcall_method(ptrs->ctx, 2) != 0) { ILibDuktape_Process_UncaughtException(ptrs->ctx); }
		if (ptrs->ctx != NULL) { duk_pop(ptrs->ctx); }				// ...
	}
}
void ILibDuktape_net_socket_OnDisconnect(ILibAsyncSocket_SocketModule socketModule, void *user)
{
	ILibDuktape_net_socket *ptrs = (ILibDuktape_net_socket*)((ILibChain_Link*)socketModule)->ExtraMemoryPtr;
	if (ILibMemory_CanaryOK(ptrs->emitter))
	{
		duk_push_heapptr(ptrs->ctx, ptrs->object);			// [sock]
		duk_push_string(ptrs->ctx, "0.0.0.0");				// [sock][localAddr]
		duk_put_prop_string(ptrs->ctx, -2, "localAddress");	// [sock]
		duk_push_undefined(ptrs->ctx);						// [sock][remoteAddr]
		duk_put_prop_string(ptrs->ctx, -2, "remoteAddress");// [sock]
		duk_pop(ptrs->ctx);									// ...

		ILibDuktape_DuplexStream_Closed((ILibDuktape_DuplexStream*)ptrs->duplexStream);
	}
}
void ILibDuktape_net_socket_OnSendOK(ILibAsyncSocket_SocketModule socketModule, void *user)
{
	ILibDuktape_net_socket *ptrs = (ILibDuktape_net_socket*)((ILibChain_Link*)socketModule)->ExtraMemoryPtr;
	ILibDuktape_DuplexStream_Ready((ILibDuktape_DuplexStream*)ptrs->duplexStream);
}
ILibTransport_DoneState ILibDuktape_net_socket_WriteHandler(ILibDuktape_DuplexStream *stream, char *buffer, int bufferLen, void *user)
{
	ILibDuktape_net_socket *ptrs = (ILibDuktape_net_socket*)user;
	return((ILibTransport_DoneState)ILibAsyncSocket_Send(ptrs->socketModule, buffer, bufferLen, ILibAsyncSocket_MemoryOwnership_USER));
}
void ILibDuktape_net_socket_EndHandler(ILibDuktape_DuplexStream *stream, void *user)
{
	ILibDuktape_net_socket *ptrs = (ILibDuktape_net_socket*)user;
	ILibAsyncSocket_Disconnect(ptrs->socketModule);
}
void ILibDuktape_net_socket_PauseHandler(ILibDuktape_DuplexStream *sender, void *user)
{
	ILibDuktape_net_socket *ptrs = (ILibDuktape_net_socket*)user;
	ILibAsyncSocket_Pause(ptrs->socketModule);
}
void ILibDuktape_net_socket_ResumeHandler(ILibDuktape_DuplexStream *sender, void *user)
{
	ILibDuktape_net_socket *ptrs = (ILibDuktape_net_socket*)user;
	ILibAsyncSocket_Resume(ptrs->socketModule);
}
duk_ret_t ILibDuktape_net_socket_connect_errorDispatch(duk_context *ctx)
{
	duk_dup(ctx, 0);																		// [socket]
	duk_get_prop_string(ctx, -1, "emit");													// [socket][emit]
	duk_swap_top(ctx, -2);																	// [emit][this]
	duk_push_string(ctx, "error");															// [emit][this][error]
	duk_dup(ctx, 1);																		// [emit][this][error][err]
	duk_call_method(ctx, 2);
	duk_pop(ctx);																			// ...
	return(0);
}
#ifdef WIN32 
void ILibDuktape_net_socket_ipc_error(duk_context *ctx, void ** args, int argsLen)
{
	ILibDuktape_net_WindowsIPC *winIPC = ((ILibDuktape_net_WindowsIPC**)args)[0];
	ILibDuktape_EventEmitter_SetupEmit(winIPC->ctx, winIPC->mSocket, "error");		// [emit][this][error]
	duk_push_string(winIPC->ctx, "Error Connecting Named Pipe");					// [emit][this][error][err]
	duk_pcall_method(winIPC->ctx, 2); duk_pop(winIPC->ctx);							// ...
}
void ILibDuktape_net_Socket_ipc_dataHookCallbackCont(duk_context *ctx, void ** args, int argsLen)
{
	ILibDuktape_EventEmitter *emitter = (ILibDuktape_EventEmitter*)args[0];

	duk_push_heapptr(emitter->ctx, emitter->object);		// [stream]
	duk_get_prop_string(emitter->ctx, -1, "resume");		// [stream][resume]
	duk_dup(emitter->ctx, -2);								// [stream][resume][this]
	duk_pcall_method(emitter->ctx, 0);
	duk_pop(emitter->ctx);									// ...
}
void ILibDuktape_net_socket_ipc_dataHookCallback(ILibDuktape_EventEmitter *sender, char *eventName, void *hookedCallback)
{
	if (ILibDuktape_EventEmitter_HasListeners(sender, "data") == 0)
	{
		int top = duk_get_top(sender->ctx);
		duk_push_heapptr(sender->ctx, sender->object);		// [stream]
		duk_get_prop_string(sender->ctx, -1, "isPaused");	// [stream][isPaused]
		duk_dup(sender->ctx, -2);							// [stream][isPaused][this]
		if (duk_pcall_method(sender->ctx, 0) == 0)
		{														
			if (duk_get_boolean(sender->ctx, -1))			// [stream][bool]
			{
				ILibDuktape_Immediate(sender->ctx, (void**)&sender, 1, ILibDuktape_net_Socket_ipc_dataHookCallbackCont);
			}
		}
		duk_set_top(sender->ctx, top);
	}
}
#endif
duk_ret_t ILibDuktape_net_socket_connect(duk_context *ctx)
{
	int nargs = duk_get_top(ctx);
	int port = 0;
	char *host = "127.0.0.1";
	char *path = NULL;
	duk_size_t pathLen = 0;
	ILibDuktape_net_socket *ptrs;
	struct sockaddr_in6 dest;
	struct sockaddr_in6 proxy;
	memset(&proxy, 0, sizeof(struct sockaddr_in6));

	if (nargs == 0) { return(ILibDuktape_Error(ctx, "Too few arguments")); }
	duk_push_this(ctx);														// [socket]
	duk_get_prop_string(ctx, -1, ILibDuktape_net_socket_ptr);				// [socket][ptrs]
	ptrs = (ILibDuktape_net_socket*)duk_to_pointer(ctx, -1);
	duk_pop(ctx);															// [socket]
	
	if (duk_is_object(ctx, 0))
	{
		/* This is an OPTIONS object
		port: Port the client should connect to(Required).
		host : Host the client should connect to.Defaults to 'localhost'.
		localAddress : Local interface to bind to for network connections.
		localPort : Local port to bind to for network connections.
		family : Version of IP stack.Defaults to 4.
		hints : dns.lookup() hints.Defaults to 0.
		lookup : Custom lookup function.Defaults to dns.lookup.
		*/
		host = Duktape_GetStringPropertyValue(ctx, 0, "host", "127.0.0.1");
		port = Duktape_GetIntPropertyValue(ctx, 0, "port", 0);
		path = Duktape_GetStringPropertyValueEx(ctx, 0, "path", NULL, &pathLen);
		if (duk_has_prop_string(ctx, 0, "proxy"))
		{
			duk_get_prop_string(ctx, 0, "proxy");
			ILibResolveEx(Duktape_GetStringPropertyValue(ctx, -1, "host", NULL), (unsigned short)Duktape_GetIntPropertyValue(ctx, -1, "port", 0), &proxy);
			duk_pop(ctx);
		}
		if (nargs >= 2 && duk_is_function(ctx, 1))
		{
			ILibDuktape_EventEmitter_AddOn(ptrs->emitter, "connect", duk_require_heapptr(ctx, 1));
		}
	}
	if (duk_is_string(ctx, 0) || (pathLen > 0 && port == 0))
	{
		// This is a PATH string (Domain Socket on Linux/MacOS, Named Pipe on Windows)
#ifdef WIN32
		duk_push_this(ctx);
		duk_push_array(ctx); duk_put_prop_string(ctx, -2, ILibDuktape_net_WindowsIPC_PendingArray);
		ILibDuktape_WriteID(ctx, "net.socket.ipc");

		ILibDuktape_net_WindowsIPC *winIPC = (ILibDuktape_net_WindowsIPC*)Duktape_PushBuffer(ctx, sizeof(ILibDuktape_net_WindowsIPC));
		duk_put_prop_string(ctx, -2, ILibDuktape_net_WindowsIPC_Buffer);
		winIPC->overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
		winIPC->ctx = ctx;
		winIPC->mSocket = duk_get_heapptr(ctx, -1);

		duk_eval_string(ctx, "require('child_process');");
		winIPC->manager = (ILibProcessPipe_Manager)Duktape_GetPointerProperty(ctx, -1, ILibDuktape_ChildProcess_Manager);
		duk_pop(ctx);

		if ((winIPC->mPipeHandle = CreateFileA(path, GENERIC_READ | FILE_WRITE_DATA, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0)) == INVALID_HANDLE_VALUE)
		{
			// We'll emit the 'error' event on the next event loop, becuase if 'createConnection' was called, there is no way the event listener is set yet
			ILibDuktape_Immediate(ctx, (void**)&winIPC, 1, ILibDuktape_net_socket_ipc_error);
		}
		else
		{
			// SUCCESS
			winIPC->ds = ILibDuktape_DuplexStream_InitEx(winIPC->ctx, ILibDuktape_net_server_IPC_WriteSink, ILibDuktape_net_server_IPC_EndSink, ILibDuktape_net_server_IPC_PauseSink, ILibDuktape_net_server_IPC_ResumeSink, ILibDuktape_net_server_IPC_unshiftSink, winIPC);
			winIPC->mPipe = ILibProcessPipe_Pipe_CreateFromExisting(winIPC->manager, winIPC->mPipeHandle, ILibProcessPipe_Pipe_ReaderHandleType_Overlapped);
			winIPC->ds->readableStream->paused = 1;
			ILibDuktape_EventEmitter_AddHook(ILibDuktape_EventEmitter_GetEmitter(winIPC->ctx, -1), "data", ILibDuktape_net_socket_ipc_dataHookCallback);

			ILibDuktape_EventEmitter_SetupEmit(winIPC->ctx, winIPC->mSocket, "connect");		//[emit][this][connect]
			ILibDuktape_EventEmitter_PrependOnce(winIPC->ctx, -2, "~", ILibDuktape_net_server_IPC_ConnectSink_Finalizer);

			if (duk_pcall_method(winIPC->ctx, 1) != 0)
			{
				ILibDuktape_Process_UncaughtExceptionEx(winIPC->ctx, "Error in net.socket.connect.onConnect(): ");
			}
			duk_pop(winIPC->ctx);
		}
	
		return(1);
#else
		
		if (pathLen > 0) 
		{ 
			host = path; 
		}
		else
		{
			host = (char*)duk_require_string(ctx, 0);
		}
		duk_push_heapptr(ptrs->ctx, ptrs->object);				// [socket]
		duk_push_string(ctx, host);								// [socket][host]
		ILibDuktape_CreateReadonlyProperty(ctx, "remoteHost");	// [socket]
		duk_pop(ctx);											// ...

		struct sockaddr_un serveraddr;
		memset(&serveraddr, 0, sizeof(serveraddr));
		serveraddr.sun_family = AF_UNIX;
		strcpy(serveraddr.sun_path, host);
		ILibAsyncSocket_ConnectTo(ptrs->socketModule, NULL, (struct sockaddr*)&serveraddr, NULL, ptrs);

		duk_push_heapptr(ptrs->ctx, ptrs->object);					// [sockat]
		duk_push_true(ptrs->ctx);									// [socket][connecting]
		duk_put_prop_string(ptrs->ctx, -2, "connecting");			// [socket]
		duk_pop(ptrs->ctx);											// ...
		return(0);
#endif
	}


	if (duk_is_number(ctx, 0))
	{
		// This is a PORT number
		port = duk_require_int(ctx, 0);
		host = nargs > 1 ? (char*)duk_require_string(ctx, 1) : "127.0.0.1";
		if (nargs > 2 && duk_is_function(ctx, 2))
		{
			ILibDuktape_EventEmitter_AddOn(ptrs->emitter, "connect", duk_require_heapptr(ctx, 2));
		}
	}

	duk_push_heapptr(ptrs->ctx, ptrs->object);				// [socket]
	duk_push_string(ctx, host);								// [socket][host]
	ILibDuktape_CreateReadonlyProperty(ctx, "remoteHost");	// [socket]
	duk_pop(ctx);											// ...

	ILibResolveEx(host, (unsigned short)port, &dest);
	if (dest.sin6_family == AF_UNSPEC || (duk_is_object(ctx, 0) && duk_has_prop_string(ctx, 0, "proxy") && proxy.sin6_family == AF_UNSPEC))
	{
		// Can't resolve... Delay event emit, until next event loop, because if app called net.createConnection(), they don't have the socket yet
		duk_push_heapptr(ctx, ptrs->object);													// [socket]																
		duk_push_global_object(ctx);															// [socket][g]
		duk_get_prop_string(ctx, -1, "setImmediate");											// [socket][g][immediate]
		duk_swap_top(ctx, -2);																	// [socket][immediate][this]
		duk_push_c_function(ctx, ILibDuktape_net_socket_connect_errorDispatch, DUK_VARARGS);	// [socket][immediate][this][callback]
		duk_dup(ctx, -4);																		// [socket][immediate][this][callback][socket]

		duk_push_error_object(ptrs->ctx, DUK_ERR_ERROR, "Cannot Resolve Hostname: %s", host);	// [socket][immediate][this][callback][socket][err]
		if (duk_pcall_method(ptrs->ctx, 3) != 0) { ILibDuktape_Process_UncaughtExceptionEx(ptrs->ctx, "socket.connect(): "); }
		duk_put_prop_string(ptrs->ctx, -2, "\xFF_Immediate");									// [socket]
		duk_pop(ptrs->ctx);
	}
	else
	{
		if(duk_is_object(ctx, 0) && duk_has_prop_string(ctx, 0, "proxy"))
		{
			duk_get_prop_string(ctx, 0, "proxy");
			ILibAsyncSocket_ConnectToProxy(ptrs->socketModule, NULL, (struct sockaddr*)&dest, (struct sockaddr*)&proxy, Duktape_GetStringPropertyValue(ctx, -1, "username", NULL), Duktape_GetStringPropertyValue(ctx, -1, "password", NULL), NULL, ptrs);
			duk_pop(ctx);
		}
		else
		{
			ILibAsyncSocket_ConnectTo(ptrs->socketModule, NULL, (struct sockaddr*)&dest, NULL, ptrs);
		}

		duk_push_heapptr(ptrs->ctx, ptrs->object);					// [sockat]
		duk_push_true(ptrs->ctx);									// [socket][connecting]
		duk_put_prop_string(ptrs->ctx, -2, "connecting");			// [socket]
		duk_pop(ptrs->ctx);											// ...
	}
	return 0;
}

duk_ret_t ILibduktape_net_socket_bytesWritten(duk_context *ctx)
{
	ILibDuktape_net_socket *ptrs;

	duk_push_this(ctx);											// [obj]
	duk_get_prop_string(ctx, -1, ILibDuktape_net_socket_ptr);	// [obj][ptrs]
	ptrs = (ILibDuktape_net_socket*)duk_to_pointer(ctx, -1);

	duk_push_int(ctx, (int)ILibAsyncSocket_GetTotalBytesSent(ptrs->socketModule));
	return 1;
}
duk_ret_t ILibDuktape_net_socket_address(duk_context *ctx)
{
	ILibDuktape_net_socket *ptrs;
	struct sockaddr_in6 local;

	duk_push_this(ctx);											// [sock]
	duk_get_prop_string(ctx, -1, ILibDuktape_net_socket_ptr);	// [socks][ptrs]
	ptrs = (ILibDuktape_net_socket*)duk_to_pointer(ctx, -1);

	memset(&local, 0, sizeof(struct sockaddr_in6));
	ILibAsyncSocket_GetLocalInterface(ptrs->socketModule, (struct sockaddr*)&local);

	duk_push_object(ctx);														// [retVal]
	duk_push_int(ctx, (int)ntohs(local.sin6_port));								// [retVal][port]
	duk_put_prop_string(ctx, -2, "port");										// [retVal]

	duk_push_string(ctx, local.sin6_family == AF_INET6 ? "IPv6" : "IPv4");		// [retVal][family]
	duk_put_prop_string(ctx, -2, "family");										// [retVal]

	duk_push_string(ctx, ILibInet_ntop2((struct sockaddr*)&local, ILibScratchPad, sizeof(ILibScratchPad)));
	duk_put_prop_string(ctx, -2, "address");
	

	return 1;
}

void ILibDuktape_net_socket_timeoutSink(ILibAsyncSocket_SocketModule socketModule, void *user)
{
	ILibDuktape_net_socket *ptrs = (ILibDuktape_net_socket*)((ILibChain_Link*)socketModule)->ExtraMemoryPtr;

	duk_push_heapptr(ptrs->ctx, ptrs->object);						// [this]
	duk_get_prop_string(ptrs->ctx, -1, "emit");						// [this][emit]
	duk_swap_top(ptrs->ctx, -2);									// [emit][this]
	duk_push_string(ptrs->ctx, "timeout");							// [emit][this][timeout]
	if (duk_pcall_method(ptrs->ctx, 1) != 0) { ILibDuktape_Process_UncaughtException(ptrs->ctx); }
	duk_pop(ptrs->ctx);												// ...
}
duk_ret_t ILibDuktape_net_socket_setTimeout(duk_context *ctx)
{
	int nargs = duk_get_top(ctx);
	ILibDuktape_net_socket *ptrs;
	int timeout = duk_require_int(ctx, 0);
	duk_push_this(ctx);											// [sock]
	duk_get_prop_string(ctx, -1, ILibDuktape_net_socket_ptr);	// [socks][ptrs]
	ptrs = (ILibDuktape_net_socket*)duk_to_pointer(ctx, -1);
	duk_pop(ctx);												// [socks]

	if (nargs > 1 && duk_is_function(ctx, 1))
	{
		ILibDuktape_EventEmitter_AddOnce(ptrs->emitter, "timeout", duk_require_heapptr(ctx, 1));
	}
	if (timeout == 0)
	{
		// Disable
		ILibDuktape_EventEmitter_RemoveAllListeners(ptrs->emitter, "timeout");
	}
	ILibAsyncSocket_SetTimeoutEx(ptrs->socketModule, timeout, timeout != 0 ? ILibDuktape_net_socket_timeoutSink : NULL);
	return 0;
}
duk_ret_t ILibDuktape_net_socket_finalizer(duk_context *ctx)
{
	void *chain = Duktape_GetChain(ctx);
	ILibDuktape_net_socket* ptrs;
	duk_get_prop_string(ctx, 0, ILibDuktape_net_socket_ptr);
	ptrs = (ILibDuktape_net_socket*)duk_get_pointer(ctx, -1);

	if (ptrs->socketModule != NULL)
	{
		if (ILibAsyncSocket_IsConnected(ptrs->socketModule) != 0) { ILibAsyncSocket_Disconnect(ptrs->socketModule); }
		ILibChain_SafeRemove(chain, ptrs->socketModule);
	}
	ptrs->ctx = NULL;
	return 0;
}
int ILibDuktape_net_socket_unshift(ILibDuktape_DuplexStream *sender, int unshiftBytes, void *user)
{
	ILibDuktape_net_socket *ptrs = (ILibDuktape_net_socket*)user;
	ptrs->unshiftBytes = unshiftBytes;
	return(unshiftBytes);
}
void ILibDuktape_net_socket_PUSH(duk_context *ctx, ILibAsyncSocket_SocketModule module)
{
	ILibDuktape_net_socket *ptrs = (ILibDuktape_net_socket*)((ILibChain_Link*)module)->ExtraMemoryPtr;
	if (ptrs != NULL && ptrs->object != NULL)
	{
		duk_push_heapptr(ctx, ptrs->object);
		return;
	}

	duk_push_object(ctx);										// [obj]
	ILibDuktape_WriteID(ctx, "net.socket");
	ptrs->ctx = ctx;
	ptrs->chain = ((ILibChain_Link*)module)->ParentChain;
	ptrs->object = duk_get_heapptr(ctx, -1);
	ptrs->socketModule = module;

	duk_push_pointer(ctx, ptrs);								// [obj][ptrs]
	duk_put_prop_string(ctx, -2, ILibDuktape_net_socket_ptr);	// [obj]
	duk_push_false(ctx);										// [obj][connecting]
	duk_put_prop_string(ctx, -2, "connecting");					// [obj]
	duk_push_string(ctx, "0.0.0.0");	
	duk_put_prop_string(ctx, -2, "localAddress");
	duk_push_int(ctx, 0);
	duk_put_prop_string(ctx, -2, "localPort");
	duk_push_undefined(ctx);
	duk_put_prop_string(ctx, -2, "remoteAddress");

	ptrs->emitter = ILibDuktape_EventEmitter_Create(ctx);
	ptrs->duplexStream = ILibDuktape_DuplexStream_InitEx(ctx, ILibDuktape_net_socket_WriteHandler, ILibDuktape_net_socket_EndHandler, ILibDuktape_net_socket_PauseHandler, ILibDuktape_net_socket_ResumeHandler, ILibDuktape_net_socket_unshift, ptrs);

	ILibDuktape_EventEmitter_CreateEventEx(ptrs->emitter, "close");
	ILibDuktape_EventEmitter_CreateEventEx(ptrs->emitter, "connect");
	ILibDuktape_EventEmitter_CreateEventEx(ptrs->emitter, "error");
	ILibDuktape_EventEmitter_CreateEventEx(ptrs->emitter, "timeout");

	ILibDuktape_CreateProperty_InstanceMethod(ctx, "connect", ILibDuktape_net_socket_connect, DUK_VARARGS);

	ILibDuktape_CreateEventWithGetter(ctx, "bytesWritten", ILibduktape_net_socket_bytesWritten);


	ILibDuktape_CreateInstanceMethod(ctx, "address", ILibDuktape_net_socket_address, 0);
	ILibDuktape_CreateInstanceMethod(ctx, "setTimeout", ILibDuktape_net_socket_setTimeout, DUK_VARARGS);
	ILibDuktape_CreateFinalizer(ctx, ILibDuktape_net_socket_finalizer);
}

duk_ret_t ILibDuktape_net_socket_constructor(duk_context *ctx)
{
	ILibDuktape_net_socket *ptrs;
	ILibAsyncSocket_SocketModule sm;
	void *chain;
	void *net;

	if (!duk_is_constructor_call(ctx)) { return(ILibDuktape_Error(ctx, "Invalid call")); }
	
	duk_push_current_function(ctx);				// [func]
	duk_get_prop_string(ctx, -1, "chain");		// [func][chain]
	chain = duk_to_pointer(ctx, -1);
	duk_get_prop_string(ctx, -2, "net");		// [func][net]
	net = duk_get_heapptr(ctx, -1);

	sm = ILibCreateAsyncSocketModuleWithMemory(chain, 4096, ILibDuktape_net_socket_OnData, ILibDuktape_net_socket_OnConnect, ILibDuktape_net_socket_OnDisconnect, ILibDuktape_net_socket_OnSendOK, sizeof(ILibDuktape_net_socket));
	ptrs = (ILibDuktape_net_socket*)((ILibChain_Link*)sm)->ExtraMemoryPtr;
	ptrs->net = net;
	ILibDuktape_net_socket_PUSH(ctx, sm);

	return 1;
}
duk_ret_t ILibDuktape_net_createConnection(duk_context *ctx)
{
	void *chain;
	void *net;
	ILibAsyncSocket_SocketModule sm;
	ILibDuktape_net_socket *ptrs;
	int nargs = duk_get_top(ctx), i;

	duk_push_this(ctx);						// [net]
	duk_get_prop_string(ctx, -1, "chain");	// [net][chain]
	chain = duk_to_pointer(ctx, -1);
	net = duk_get_heapptr(ctx, -2);
	duk_pop(ctx);							// [net]

	sm = ILibCreateAsyncSocketModuleWithMemory(chain, 4096, ILibDuktape_net_socket_OnData, ILibDuktape_net_socket_OnConnect, ILibDuktape_net_socket_OnDisconnect, ILibDuktape_net_socket_OnSendOK, sizeof(ILibDuktape_net_socket));
	ptrs = (ILibDuktape_net_socket*)((ILibChain_Link*)sm)->ExtraMemoryPtr;
	ptrs->net = net;

	ILibDuktape_net_socket_PUSH(ctx, sm);							// [net][socket]
	duk_get_prop_string(ctx, -1, "connect");						// [net][socket][connect]
	duk_dup(ctx, -2);												// [net][socket][connect][this]
	for (i = 0; i < nargs; ++i)
	{
		duk_dup(ctx, i);											// [net][socket][connect][this][...args...]
	}
	duk_call_method(ctx, nargs); duk_pop(ctx);						// [net][socket]
	if (duk_is_object(ctx, 0))
	{
		duk_dup(ctx, 0);											// [net][socket][options]
		duk_put_prop_string(ctx, -2, ILibDuktape_SOCKET2OPTIONS);	// [net][socket]
	}	
	return 1;
}

ILibTransport_DoneState ILibDuktape_net_server_WriteSink(ILibDuktape_DuplexStream *stream, char *buffer, int bufferLen, void *user)
{
	ILibDuktape_net_server_session *session = (ILibDuktape_net_server_session*)user;
	if (!ILibMemory_CanaryOK(session)) { return(ILibTransport_DoneState_ERROR); }

	return((ILibTransport_DoneState)ILibAsyncServerSocket_Send(NULL, session->connection, buffer, bufferLen, ILibAsyncSocket_MemoryOwnership_USER));
}
void ILibDuktape_net_server_EndSink(ILibDuktape_DuplexStream *stream, void *user)
{
	ILibDuktape_net_server_session *session = (ILibDuktape_net_server_session*)user;
	if (!ILibMemory_CanaryOK(session)) { return; }

	if (session->connection != NULL) { ILibAsyncServerSocket_Disconnect(NULL, session->connection); }
}
void ILibDuktape_net_server_PauseSink(ILibDuktape_DuplexStream *sender, void *user)
{
	ILibDuktape_net_server_session *session = (ILibDuktape_net_server_session*)user;
	if (!ILibMemory_CanaryOK(session)) { return; }

	ILibAsyncSocket_Pause(session->connection);
}
void ILibDuktape_net_server_ResumeSink(ILibDuktape_DuplexStream *sender, void *user)
{
	ILibDuktape_net_server_session *session = (ILibDuktape_net_server_session*)user;
	if (!ILibMemory_CanaryOK(session)) { return; }

	ILibAsyncSocket_Resume(session->connection);
}
duk_ret_t ILibDuktape_net_server_socket_Finalizer(duk_context *ctx)
{
	ILibDuktape_net_server_session *session;
	duk_get_prop_string(ctx, 0, ILibDuktape_net_Server_Session_buffer);
	session = (ILibDuktape_net_server_session*)Duktape_GetBuffer(ctx, -1, NULL);
	if (session != NULL && session->connection != NULL) 
	{
		void *data = ILibAsyncSocket_GetUser(session->connection);
		if (data != NULL) { free(data); ILibAsyncSocket_SetUser(session->connection, NULL); }
		ILibAsyncServerSocket_Disconnect(NULL, session->connection); 
	}

	return 0;
}
int ILibDuktape_net_server_unshiftSink(ILibDuktape_DuplexStream *sender, int unshiftBytes, void *user)
{
	ILibDuktape_net_server_session *session = (ILibDuktape_net_server_session*)user;
	if (!ILibMemory_CanaryOK(session)) { return(unshiftBytes); }

	session->unshiftBytes = unshiftBytes;
	return(unshiftBytes);
}
void ILibDuktape_net_server_OnConnect(ILibAsyncServerSocket_ServerModule AsyncServerSocketModule, ILibAsyncServerSocket_ConnectionToken ConnectionToken, void **user)
{
	ILibDuktape_net_server *ptr = (ILibDuktape_net_server*)((void**)ILibMemory_GetExtraMemory(AsyncServerSocketModule, ILibMemory_ASYNCSERVERSOCKET_CONTAINERSIZE))[0];
	ILibDuktape_net_server_session *session;
#ifndef MICROSTACK_NOTLS
	int isTLS = ILibAsyncSocket_IsUsingTls(ConnectionToken);
#else
	int isTLS = 0;
#endif
	if (!ILibMemory_CanaryOK(ptr)) { return; }

	duk_push_heapptr(ptr->ctx, ptr->self);																					// [server]

	duk_get_prop_string(ptr->ctx, -1, "emit");																				// [server][emit]
	duk_swap_top(ptr->ctx, -2);																								// [emit][this]
	duk_push_string(ptr->ctx, isTLS ? "secureConnection" : "connection");													// [emit][this][connection]

	duk_push_object(ptr->ctx);																								// [emit][this][connection][socket]
	ILibDuktape_WriteID(ptr->ctx, isTLS ? "tls.serverSocketConnection" : "net.serverSocketConnection");
	ILibDuktape_CreateFinalizer(ptr->ctx, ILibDuktape_net_server_socket_Finalizer);
	session = Duktape_PushBuffer(ptr->ctx, sizeof(ILibDuktape_net_server_session));											// [emit][this][connection][socket][buffer]
	duk_put_prop_string(ptr->ctx, -2, ILibDuktape_net_Server_Session_buffer);												// [emit][this][connection][socket]

	struct sockaddr_in6 local;
	ILibAsyncSocket_GetLocalInterface(ConnectionToken, (struct sockaddr*)&local);
	duk_push_string(ptr->ctx, ILibInet_ntop2((struct sockaddr*)&local, ILibScratchPad, sizeof(ILibScratchPad)));			// [emit][this][connection][sock][localAddr]
	duk_put_prop_string(ptr->ctx, -2, "localAddress");																		// [emit][this][connection][sock]
	duk_push_int(ptr->ctx, (int)ntohs(local.sin6_port));																	// [emit][this][connection][sock][port]
	duk_put_prop_string(ptr->ctx, -2, "localPort");																			// [emit][this][connection][sock]
	ILibAsyncSocket_GetRemoteInterface(ConnectionToken, (struct sockaddr*)&local);
	duk_push_string(ptr->ctx, ILibInet_ntop2((struct sockaddr*)&local, ILibScratchPad, sizeof(ILibScratchPad)));			// [emit][this][connection][sock][remoteAddr]
	duk_put_prop_string(ptr->ctx, -2, "remoteAddress");																		// [emit][this][connection][sock]
	duk_push_string(ptr->ctx, local.sin6_family == AF_INET6 ? "IPv6" : "IPv4");												// [emit][this][connection][sock][remoteFamily]
	duk_put_prop_string(ptr->ctx, -2, "remoteFamily");																		// [emit][this][connection][sock]
	duk_push_int(ptr->ctx, (int)ntohs(local.sin6_port));																	// [emit][this][connection][sock][remotePort]
	duk_put_prop_string(ptr->ctx, -2, "remotePort");																		// [emit][this][connection][sock]


	*user = session;
	session->ctx = ptr->ctx;
	session->connection = ConnectionToken;
	session->self = duk_get_heapptr(ptr->ctx, -1);
	session->emitter = ILibDuktape_EventEmitter_Create(ptr->ctx);
		

	ILibDuktape_EventEmitter_CreateEventEx(session->emitter, "timeout");

	session->stream = ILibDuktape_DuplexStream_InitEx(ptr->ctx, ILibDuktape_net_server_WriteSink, ILibDuktape_net_server_EndSink,
		ILibDuktape_net_server_PauseSink, ILibDuktape_net_server_ResumeSink, ILibDuktape_net_server_unshiftSink, session);

	if (duk_pcall_method(ptr->ctx, 2) != 0) { ILibDuktape_Process_UncaughtExceptionEx(ptr->ctx, (isTLS ? "tls.server.OnSecureConnection(): Exception" : "net.server.OnConnect(): Exception")); }
	duk_pop(ptr->ctx);																	// ...
}
void ILibDuktape_net_server_OnDisconnect(ILibAsyncServerSocket_ServerModule AsyncServerSocketModule, ILibAsyncServerSocket_ConnectionToken ConnectionToken, void *user)
{
	ILibDuktape_net_server_session *session = (ILibDuktape_net_server_session*)user;
	if (!ILibMemory_CanaryOK(session)) { return; }

	if (session->connection != NULL)
	{
		ILibDuktape_DuplexStream_Closed(session->stream);
		session->connection = NULL;
	}
}
void ILibDuktape_net_server_OnReceive(ILibAsyncServerSocket_ServerModule AsyncServerSocketModule, ILibAsyncServerSocket_ConnectionToken ConnectionToken, char* buffer, int *p_beginPointer, int endPointer, ILibAsyncServerSocket_OnInterrupt *OnInterrupt, void **user, int *PAUSE)
{
	ILibDuktape_net_server_session *session = (ILibDuktape_net_server_session*)*user;
	if (!ILibMemory_CanaryOK(session)) { *p_beginPointer = endPointer;  return; }

	session->unshiftBytes = 0;
	ILibDuktape_DuplexStream_WriteData(session->stream, buffer + *p_beginPointer, endPointer);
	*p_beginPointer = endPointer - session->unshiftBytes;
}
void ILibDuktape_net_server_OnInterrupt(ILibAsyncServerSocket_ServerModule AsyncServerSocketModule, ILibAsyncServerSocket_ConnectionToken ConnectionToken, void *user)
{
}
void ILibDuktape_net_server_OnSendOK(ILibAsyncServerSocket_ServerModule AsyncServerSocketModule, ILibAsyncServerSocket_ConnectionToken ConnectionToken, void *user)
{
	ILibDuktape_net_server_session *session = (ILibDuktape_net_server_session*)user;
	if (!ILibMemory_CanaryOK(session)) { return; }

	ILibDuktape_DuplexStream_Ready(session->stream);
}

#ifdef WIN32
extern void ILibProcessPipe_FreePipe(ILibProcessPipe_Pipe pipeObject);

int ILibDuktape_net_server_IPC_unshiftSink(ILibDuktape_DuplexStream *sender, int unshiftBytes, void *user)
{
	ILibDuktape_net_WindowsIPC *winIPC = (ILibDuktape_net_WindowsIPC*)user;
	if (!ILibMemory_CanaryOK(user)) { return(0); }
	winIPC->unshiftedBytes = unshiftBytes;
	return(unshiftBytes);
}
void ILibDuktape_net_server_IPC_readsink(ILibProcessPipe_Pipe sender, void *user, DWORD dwErrorCode, char *buffer, int bufferLen)
{
	ILibDuktape_net_WindowsIPC *winIPC = (ILibDuktape_net_WindowsIPC*)user;
	if (!ILibMemory_CanaryOK(user)) { return; }

	if (dwErrorCode == 0)
	{
		winIPC->bytesLeft += bufferLen;
		ILibDuktape_net_server_IPC_ResumeSink(winIPC->ds, winIPC);
	}
	else
	{
		ILibDuktape_DuplexStream_Closed(winIPC->ds);
		ILibProcessPipe_FreePipe(winIPC->mPipe);
		winIPC->mPipe = NULL;

		if (winIPC->mServer != NULL)
		{
			// Server IPC, so we can create a new Instance, and listen for a connection
			duk_context *ctx = winIPC->ctx;									// We need to dereference this, because winIPC will go out of scope when we call listen
			CloseHandle(winIPC->overlapped.hEvent);
			if (winIPC->buffer != NULL) { free(winIPC->buffer); winIPC->buffer = NULL; }

			duk_push_heapptr(ctx, winIPC->mServer);							// [server]
			duk_get_prop_string(ctx, -1, "listen");							// [server][listen]
			duk_swap_top(ctx, -2);											// [listen][this]
			duk_get_prop_string(ctx, -1, ILibDuktape_SERVER2LISTENOPTIONS);	// [listen][this][options]
			duk_pcall_method(ctx, 1); duk_pop(ctx);							// ...
		}
	}
}
void ILibDuktape_net_server_IPC_PauseSink(ILibDuktape_DuplexStream *sender, void *user)
{
	// No-OP, becuase all we need to so is set Paused flag, which is already the  case when we get here
}
void ILibDuktape_net_server_IPC_ResumeSink(ILibDuktape_DuplexStream *sender, void *user)
{
	ILibDuktape_net_WindowsIPC *winIPC = (ILibDuktape_net_WindowsIPC*)user;
	if (winIPC->processingRead != 0) { return; }
	winIPC->processingRead = 1;

	if (winIPC->buffer == NULL)
	{
		winIPC->buffer = ILibMemory_Allocate(ILibDuktape_net_IPC_BUFFERSIZE, 0, NULL, NULL);
		winIPC->bufferLength = ILibDuktape_net_IPC_BUFFERSIZE;
		winIPC->bufferOffset = 0;
		winIPC->bytesLeft = 0;
	}

	if (winIPC->bytesLeft <= 0)
	{
		winIPC->bytesLeft = 0;
		winIPC->bufferOffset = 0;
		if (ILibProcessPipe_Pipe_ReadEx(winIPC->mPipe, winIPC->buffer, winIPC->bufferLength, winIPC, ILibDuktape_net_server_IPC_readsink) != 0)
		{
			ILibDuktape_net_server_IPC_readsink(winIPC->mPipe, winIPC, 1, NULL, 0);
		}
	}
	else
	{
		// Check to see if we can drain any of the buffer first
		while (winIPC->ds->readableStream->paused == 0)
		{
			winIPC->unshiftedBytes = 0;
			ILibDuktape_DuplexStream_WriteData(winIPC->ds, winIPC->buffer + winIPC->bufferOffset, winIPC->bytesLeft);
			if (winIPC->unshiftedBytes > 0)
			{
				winIPC->bufferOffset += (winIPC->bytesLeft - winIPC->unshiftedBytes);
				if (winIPC->bytesLeft == winIPC->unshiftedBytes)
				{
					winIPC->unshiftedBytes = 0;
				}
				winIPC->bytesLeft = winIPC->unshiftedBytes;
			}
			else
			{
				winIPC->bufferOffset = winIPC->bytesLeft = 0;
			}

			if (winIPC->ds->readableStream->paused == 0 && (winIPC->bytesLeft == 0 || (winIPC->bytesLeft > 0 && winIPC->unshiftedBytes == 0)))
			{
				if (winIPC->bufferLength - winIPC->bufferOffset - winIPC->bytesLeft == 0)
				{
					// We need to grow the buffer
					ILibMemory_ReallocateRaw(&(winIPC->buffer), winIPC->bufferLength + ILibDuktape_net_IPC_BUFFERSIZE);
					winIPC->bufferLength += ILibDuktape_net_IPC_BUFFERSIZE;
				}
				if (ILibProcessPipe_Pipe_ReadEx(winIPC->mPipe, winIPC->buffer + winIPC->bufferOffset + winIPC->bytesLeft, winIPC->bufferLength - winIPC->bufferOffset - winIPC->bytesLeft, winIPC, ILibDuktape_net_server_IPC_readsink) != 0)
				{
					ILibDuktape_net_server_IPC_readsink(winIPC->mPipe, winIPC, 1, NULL, 0);
				}
				break;
			}
		}
	}
	winIPC->processingRead = 0;
}
void ILibDuktape_net_server_IPC_WriteCompletionEvent(ILibProcessPipe_Pipe sender, void *user, DWORD errorCode, int bytesWritten)
{
	if (!ILibMemory_CanaryOK(user) || errorCode != 0) { return; }
	ILibDuktape_net_WindowsIPC *winIPC = (ILibDuktape_net_WindowsIPC*)user;
	duk_idx_t top = duk_get_top(winIPC->ctx);

	duk_push_heapptr(winIPC->ctx, winIPC->mSocket);									// [obj]
	duk_get_prop_string(winIPC->ctx, -1, ILibDuktape_net_WindowsIPC_PendingArray);	// [obj][array]
	duk_get_prop_string(winIPC->ctx, -1, "shift");									// [obj][array][shift]
	duk_dup(winIPC->ctx, -2);														// [obj][array][shift][this]
	if (duk_pcall_method(winIPC->ctx, 0) != 0)										// [obj][array][buffer]
	{
		ILibDuktape_Process_UncaughtExceptionEx(winIPC->ctx, "Internal Error: net.socket.ipc.writeCompletionEvent");
		duk_set_top(winIPC->ctx, top);												// ...
		return;
	}
	duk_pop(winIPC->ctx);															// [obj][array]
	if (duk_get_length(winIPC->ctx, -1) > 0)
	{
		// Still pending Writes
		duk_get_prop_index(winIPC->ctx, -1, 0);										// [obj][array][buffer]
		duk_size_t bufLen;
		char *buf = (char*)Duktape_GetBuffer(winIPC->ctx, -1, &bufLen);
		duk_set_top(winIPC->ctx, top);												// ...
		ILibProcessPipe_Pipe_WriteEx(winIPC->mPipe, buf, (int)bufLen, winIPC, ILibDuktape_net_server_IPC_WriteCompletionEvent);
	}
	else
	{
		// No more pending writes, so we can emit drain
		ILibDuktape_DuplexStream_Ready(winIPC->ds);
	}
}
ILibTransport_DoneState ILibDuktape_net_server_IPC_WriteSink(ILibDuktape_DuplexStream *stream, char *buffer, int bufferLen, void *user)
{
	if (!ILibMemory_CanaryOK(user)) { return(ILibTransport_DoneState_ERROR); }
	ILibDuktape_net_WindowsIPC *winIPC = (ILibDuktape_net_WindowsIPC*)user;
	
	duk_push_heapptr(winIPC->ctx, winIPC->mSocket);											// [obj]
	duk_get_prop_string(winIPC->ctx, -1, ILibDuktape_net_WindowsIPC_PendingArray);			// [obj][array]
	
	
	char *q = duk_push_fixed_buffer(winIPC->ctx, bufferLen);								// [obj][array][buffer]
	duk_size_t len = duk_get_length(winIPC->ctx, -2);
	duk_put_prop_index(winIPC->ctx, -2, (duk_uarridx_t)len);								// [obj][array]
	memcpy_s(q, bufferLen, buffer, bufferLen);
	duk_pop_2(winIPC->ctx);																	// ...

	if (len == 0)
	{
		// No Pending Writes
		ILibProcessPipe_Pipe_WriteEx(winIPC->mPipe, q, bufferLen, winIPC, ILibDuktape_net_server_IPC_WriteCompletionEvent);
	}

	return(ILibTransport_DoneState_INCOMPLETE);
}
void ILibDuktape_net_server_IPC_EndSink(ILibDuktape_DuplexStream *stream, void *user)
{
	if (!ILibMemory_CanaryOK(user)) { return; }
	ILibDuktape_net_WindowsIPC *winIPC = (ILibDuktape_net_WindowsIPC*)user;

	if (ILibProcessPipe_Pipe_CancelEx(winIPC->mPipe) == 0)
	{
		ILibProcessPipe_FreePipe(winIPC->mPipe);
		winIPC->mPipe = NULL;
		if (winIPC->mServer != NULL)
		{
			// Server IPC, so we can create a new Instance, and listen for a connection
			duk_context *ctx = winIPC->ctx;									// We need to dereference this, because winIPC will go out of scope when we call listen
			CloseHandle(winIPC->overlapped.hEvent);

			duk_push_heapptr(ctx, winIPC->mServer);							// [server]
			duk_get_prop_string(ctx, -1, "listen");							// [server][listen]
			duk_swap_top(ctx, -2);											// [listen][this]
			duk_get_prop_string(ctx, -1, ILibDuktape_SERVER2LISTENOPTIONS);	// [listen][this][options]
			duk_pcall_method(ctx, 1); duk_pop(ctx);							// ...
		}
	}
}
duk_ret_t ILibDuktape_net_server_IPC_ConnectSink_Finalizer(duk_context *ctx)
{
	ILibDuktape_net_WindowsIPC *winIPC = (ILibDuktape_net_WindowsIPC*)Duktape_GetBufferProperty(ctx, 0, ILibDuktape_net_WindowsIPC_Buffer);
	if (winIPC->mPipe != NULL && winIPC->mPipeHandle != NULL)
	{
		// It's ok to do this, becuase the CancelEx happens on the same thread, and the completion routine will use an APC Queue, so the Canary will fail before it tries to deref
		ILibProcessPipe_Pipe_CancelEx(winIPC->mPipe);
		ILibProcessPipe_FreePipe(winIPC->mPipe);
		winIPC->mPipe = NULL;
	}
	if (winIPC->buffer != NULL) { free(winIPC->buffer); winIPC->buffer = NULL; }

	return(0);
}
BOOL ILibDuktape_net_server_IPC_ConnectSink(HANDLE event, ILibWaitHandle_ErrorStatus status, void* user)
{
	if (ILibMemory_CanaryOK(user))
	{
		ILibDuktape_net_WindowsIPC *winIPC = (ILibDuktape_net_WindowsIPC*)user;
		ILibDuktape_EventEmitter_SetupEmit(winIPC->ctx, winIPC->mServer, "connection");	// [emit][this][connection]
		duk_push_object(winIPC->ctx);													// [emit][this][connection][socket]
		ILibDuktape_WriteID(winIPC->ctx, "net.socket.ipc");

		duk_push_heapptr(winIPC->ctx, winIPC->mServer);									// [emit][this][connection][socket][server]
		duk_get_prop_string(winIPC->ctx, -1, ILibDuktape_net_WindowsIPC_Buffer);		// [emit][this][connection][socket][server][buffer]
		duk_remove(winIPC->ctx, -2);													// [emit][this][connection][socket][buffer]
		duk_put_prop_string(winIPC->ctx, -2, ILibDuktape_net_WindowsIPC_Buffer);		// [emit][this][connection][socket]

		duk_push_array(winIPC->ctx); duk_put_prop_string(winIPC->ctx, -2, ILibDuktape_net_WindowsIPC_PendingArray);
		winIPC->mSocket = duk_get_heapptr(winIPC->ctx, -1);
		winIPC->ds = ILibDuktape_DuplexStream_InitEx(winIPC->ctx, ILibDuktape_net_server_IPC_WriteSink, ILibDuktape_net_server_IPC_EndSink, ILibDuktape_net_server_IPC_PauseSink, ILibDuktape_net_server_IPC_ResumeSink, ILibDuktape_net_server_IPC_unshiftSink, winIPC);
		winIPC->mPipe = ILibProcessPipe_Pipe_CreateFromExisting(winIPC->manager, winIPC->mPipeHandle, ILibProcessPipe_Pipe_ReaderHandleType_Overlapped);
		winIPC->ds->readableStream->paused = 1;
		ILibDuktape_EventEmitter_AddHook(ILibDuktape_EventEmitter_GetEmitter(winIPC->ctx, -1), "data", ILibDuktape_net_socket_ipc_dataHookCallback);
		ILibDuktape_EventEmitter_PrependOnce(winIPC->ctx, -1, "~", ILibDuktape_net_server_IPC_ConnectSink_Finalizer);

		if (duk_pcall_method(winIPC->ctx, 2) != 0)
		{
			ILibDuktape_Process_UncaughtExceptionEx(winIPC->ctx, "Error emitting net.socket.ipc.connection");
		}
		duk_pop(winIPC->ctx);
	}
	return(FALSE);
}
#endif

duk_ret_t ILibDuktape_net_server_listen(duk_context *ctx)
{
	int nargs = duk_get_top(ctx);
	ILibDuktape_net_server *server = NULL;
	int i;

	unsigned short port = 0;
	int backlog = 0;
	struct sockaddr_in6 local;
#ifdef _POSIX
	struct sockaddr_un ipcaddr;
	memset(&ipcaddr, 0, sizeof(struct sockaddr_un));
#endif
	int maxConnections = 10;
	int initalBufferSize = 4096;
	char *host, *ipc;
	duk_size_t ipcLen;
	memset(&local, 0, sizeof(struct sockaddr_in6));

	duk_push_this(ctx);
	duk_get_prop_string(ctx, -1, ILibDuktape_net_Server_buffer);
	server = (ILibDuktape_net_server*)Duktape_GetBuffer(ctx, -1, NULL);

	if (nargs == 0 || !duk_is_object(ctx, 0))
	{
		duk_push_this(ctx);															// [server]
		duk_get_prop_string(ctx, -1, "listen");										// [server][listen]
		duk_swap_top(ctx, -2);														// [listen][this]
		duk_push_object(ctx);														// [listen][this][Options]

		// let's call listen again, using an Options object
		if (nargs > 0 && (duk_is_number(ctx, 0) || duk_is_string(ctx, 0)))
		{
			duk_dup(ctx, 0); duk_put_prop_string(ctx, -2, duk_is_number(ctx, 0) ? "port" : "path");					// [listen][this][Options]
			for (i = 1; i < nargs; ++i)
			{
				if (duk_is_number(ctx, i)) { duk_dup(ctx, i); duk_put_prop_string(ctx, -2, "backlog"); }
				if (duk_is_string(ctx, i)) { duk_dup(ctx, i); duk_put_prop_string(ctx, -2, "host"); }
				if (duk_is_function(ctx, i)) { duk_dup(ctx, i); break; }			// [listen][this][Options][callback]
			}
			duk_call_method(ctx, i < nargs ? 2 : 1);
			return(1);
		}
		else
		{
			duk_call_method(ctx, 1);
			return(1);
		}
	}

	// If we are here, we were called with an Options Object
	duk_push_this(ctx);													// [server]
	duk_dup(ctx, 0);													// [server][Options]
	duk_put_prop_string(ctx, -2, ILibDuktape_SERVER2LISTENOPTIONS);		// [server]

	port = (unsigned short)Duktape_GetIntPropertyValue(ctx, 0, "port", 0);
	backlog = Duktape_GetIntPropertyValue(ctx, 0, "backlog", 64);
	host = Duktape_GetStringPropertyValue(ctx, 0, "host", NULL);
	ipc = Duktape_GetStringPropertyValueEx(ctx, 0, "path", NULL, &ipcLen);
	if (nargs > 1 && duk_is_function(ctx, 1))
	{
		// Callback
		ILibDuktape_EventEmitter_AddOn(server->emitter, "listening", duk_require_heapptr(ctx, 1));
	}
	if (host != NULL)
	{
		ILibResolveEx(host, port, &local);
		if (local.sin6_family == AF_UNSPEC)
		{
			return(ILibDuktape_Error(ctx, "Socket.listen(): Could not resolve host: '%s'", host));
		}
	}
	
	if (ipc != NULL && port == 0)
	{
#if defined(_POSIX)
		if (ipcLen > sizeof(ipcaddr.sun_path)) { return(ILibDuktape_Error(ctx, "Path too long")); }
		ipcaddr.sun_family = AF_UNIX;
		strcpy_s((char*)(ipcaddr.sun_path), sizeof(ipcaddr.sun_path), ipc);
		int ipcmod = Duktape_GetBooleanProperty(ctx, 0, "writableAll", 0) == 0 ? 0 : 0777;
		
		server->server = ILibCreateAsyncServerSocketModuleWithMemoryExMOD(Duktape_GetChain(ctx), maxConnections, initalBufferSize, (struct sockaddr*)&ipcaddr,
			ILibDuktape_net_server_OnConnect, ILibDuktape_net_server_OnDisconnect, ILibDuktape_net_server_OnReceive,
			ILibDuktape_net_server_OnInterrupt, ILibDuktape_net_server_OnSendOK, ipcmod, sizeof(void*), sizeof(void*));
#elif defined(WIN32)
		// IPC on Windows Implemented as Named Pipe

		SECURITY_ATTRIBUTES IPC_SA = { 0 };
		SECURITY_ATTRIBUTES *pIPC_SA = &IPC_SA;
		PACL IPC_ACL;
		SECURITY_DESCRIPTOR IPC_SD;
		EXPLICIT_ACCESS IPC_EA = { 0 };
				
		duk_push_this(ctx);
		ILibDuktape_net_WindowsIPC *winIPC = (ILibDuktape_net_WindowsIPC*)Duktape_PushBuffer(ctx, sizeof(ILibDuktape_net_WindowsIPC));
		duk_put_prop_string(ctx, -2, ILibDuktape_net_WindowsIPC_Buffer);
		winIPC->overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
		winIPC->ctx = ctx;
		winIPC->mServer = duk_get_heapptr(ctx, -1);


		duk_eval_string(ctx, "require('child_process');");
		winIPC->manager = (ILibProcessPipe_Manager)Duktape_GetPointerProperty(ctx, -1, ILibDuktape_ChildProcess_Manager);
		duk_pop(ctx);

		if (Duktape_GetBooleanProperty(ctx, 0, "writableAll", 0) != 0)
		{
			// World Writable, so we need to set the Security Descriptor to reflect that
			IPC_EA.grfAccessMode = SET_ACCESS;
			IPC_EA.grfInheritance = NO_INHERITANCE;
			IPC_EA.grfAccessPermissions = FILE_GENERIC_READ | FILE_WRITE_DATA;
			IPC_EA.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
			IPC_EA.Trustee.TrusteeType = TRUSTEE_IS_GROUP;
			IPC_EA.Trustee.ptstrName = "EVERYONE";

			SetEntriesInAcl(1, &IPC_EA, NULL, &IPC_ACL);
			InitializeSecurityDescriptor(&IPC_SD, SECURITY_DESCRIPTOR_REVISION);
			SetSecurityDescriptorDacl(&IPC_SD, TRUE, IPC_ACL, FALSE);

			memset(&IPC_SA, 0, sizeof(SECURITY_ATTRIBUTES));
			IPC_SA.nLength = sizeof(SECURITY_ATTRIBUTES);
			IPC_SA.bInheritHandle = FALSE;
			IPC_SA.lpSecurityDescriptor = &IPC_SD;
		}
		else
		{
			// Default security is Read/Write for LocalSystem and owner, and Read for everybody else
			pIPC_SA = NULL;
		}

		winIPC->mPipeHandle = CreateNamedPipeA((LPCSTR)ipc, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
			PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_REJECT_REMOTE_CLIENTS,
			1, ILibDuktape_net_IPC_BUFFERSIZE, ILibDuktape_net_IPC_BUFFERSIZE, 0, pIPC_SA);
		if (winIPC->mPipeHandle == INVALID_HANDLE_VALUE)
		{
			CloseHandle(winIPC->overlapped.hEvent);
			duk_del_prop_string(ctx, -1, ILibDuktape_net_WindowsIPC_Buffer);
			return(ILibDuktape_Error(ctx, "Error Creating Named Pipe: %s", ipc));
		}

		ConnectNamedPipe(winIPC->mPipeHandle, &winIPC->overlapped);
		ILibProcessPipe_WaitHandle_Add2(winIPC->manager, winIPC->overlapped.hEvent, winIPC, ILibDuktape_net_server_IPC_ConnectSink);

		if (pIPC_SA != NULL) { LocalFree(IPC_ACL); }
		return(1);
#endif
	}
	else
	{
		local.sin6_family = AF_INET;
		local.sin6_port = htons(port);

		server->server = ILibCreateAsyncServerSocketModuleWithMemoryEx(Duktape_GetChain(ctx), maxConnections, initalBufferSize, (struct sockaddr*)&local,
			ILibDuktape_net_server_OnConnect, ILibDuktape_net_server_OnDisconnect, ILibDuktape_net_server_OnReceive,
			ILibDuktape_net_server_OnInterrupt, ILibDuktape_net_server_OnSendOK, sizeof(void*), sizeof(void*));
	}


	if (server->server == NULL)
	{
		return(ILibDuktape_Error(ctx, "server.listen(): Failed to bind"));
	}

	((void**)ILibMemory_GetExtraMemory(server->server, ILibMemory_ASYNCSERVERSOCKET_CONTAINERSIZE))[0] = server;
	ILibAsyncServerSocket_SetTag(server->server, server);
#ifndef MICROSTACK_NOTLS
	{
		if (server->isTLS)
		{
			duk_push_this(ctx);												// [server]
			if (duk_has_prop_string(ctx, -1, "addContext"))
			{
				duk_get_prop_string(ctx, -1, "addContext");					// [server][addContext]
				duk_swap_top(ctx, -2);										// [addContext][this]
				duk_push_string(ctx, "*");									// [addContext][this][*]
				duk_eval_string(ctx, "require('tls');");					// [addContext][this][*][tls]
				duk_get_prop_string(ctx, -1, "createSecureContext");		// [addContext][this][*][tls][createSecureContext]
				duk_swap_top(ctx, -2);										// [addContext][this][*][createSecureContext][this]
				duk_get_prop_string(ctx, -4, ILibDuktape_SERVER2OPTIONS);	// [addContext][this][*][createSecureContext][this][options]
				duk_call_method(ctx, 1);									// [addContext][this][*][secureContext]
				duk_call_method(ctx, 2); duk_pop(ctx);						// ...
			}
			else
			{
				duk_pop(ctx);												// ...
			}
		}
	}
#endif


		duk_push_heapptr(server->ctx, server->self);			// [this]
		duk_get_prop_string(server->ctx, -1, "emit");			// [this][emit]
		duk_swap_top(server->ctx, -2);							// [emit][this]
		duk_push_string(server->ctx, "listening");				// [emit][this][listenting]
		if (duk_pcall_method(server->ctx, 1) != 0) { ILibDuktape_Process_UncaughtExceptionEx(server->ctx, "net.server.listen(): Error "); }
		duk_pop(server->ctx);									// ...
	
#ifndef WIN32
	ignore_result(backlog);
#endif

	duk_push_this(ctx);
	return 1;
}
duk_ret_t ILibDuktape_net_server_Finalizer(duk_context *ctx)
{
	void *chain = Duktape_GetChain(ctx);
	ILibDuktape_net_server *server;
	duk_get_prop_string(ctx, 0, ILibDuktape_net_Server_buffer);
	server = (ILibDuktape_net_server*)Duktape_GetBuffer(ctx, -1, NULL);

	if (server != NULL && server->server != NULL && ILibIsChainBeingDestroyed(chain) == 0)
	{
		ILibAsyncServerSocket_RemoveFromChain(server->server);
	}

#ifdef WIN32
	ILibDuktape_net_WindowsIPC *ipc = Duktape_GetBufferProperty(ctx, 0, ILibDuktape_net_WindowsIPC_Buffer);
	if (ipc != NULL && ipc->mPipeHandle != NULL && ipc->overlapped.hEvent != NULL)
	{
		ILibProcessPipe_WaitHandle_Remove(ipc->manager, ipc->overlapped.hEvent);
		CloseHandle(ipc->mPipeHandle);
		CloseHandle(ipc->overlapped.hEvent);
		ipc->overlapped.hEvent = NULL;
	}
#endif

	return 0;
}
duk_ret_t ILibDuktape_net_server_address(duk_context *ctx)
{
	duk_push_this(ctx);															// [server]
	duk_get_prop_string(ctx, -1, ILibDuktape_net_Server_buffer);				// [server][buffer]
	ILibDuktape_net_server *server = (ILibDuktape_net_server*)Duktape_GetBuffer(ctx, -1, NULL);
	struct sockaddr_in6 local;
	memset(&local, 0, sizeof(struct sockaddr_in6));

	ILibAsyncServerSocket_GetLocal(server->server, (struct sockaddr*)&local, sizeof(struct sockaddr_in6));
	if (local.sin6_family == AF_UNSPEC) { return(ILibDuktape_Error(ctx, "net.server.address(): call to getsockname() failed")); }

	duk_push_object(ctx);
	duk_push_string(ctx, local.sin6_family == AF_INET6 ? "IPv6" : "IPv4");
	duk_put_prop_string(ctx, -2, "family");

	duk_push_int(ctx, (int)ntohs(local.sin6_port));
	duk_put_prop_string(ctx, -2, "port");

	duk_push_string(ctx, ILibRemoteLogging_ConvertAddress((struct sockaddr*)&local));
	duk_put_prop_string(ctx, -2, "address");

	return(1);
}

duk_ret_t ILibDuktape_net_server_close(duk_context *ctx)
{
	int nargs = duk_get_top(ctx);

	duk_push_this(ctx);
	if (nargs > 0)
	{
		duk_get_prop_string(ctx, -1, "once");	// [server][once]
		duk_dup(ctx, -2);						// [server][once][this]
		duk_push_string(ctx, "close");			// [server][once][this][close]
		duk_dup(ctx, 0);						// [server][once][this][close][callback]
		duk_call_method(ctx, 2); duk_pop(ctx);	// [server]
	}

	ILibDuktape_net_server *server = (ILibDuktape_net_server*)Duktape_GetBufferProperty(ctx, -1, ILibDuktape_net_Server_buffer);
	if (server != NULL && server->server != NULL)
	{
		if (!ILibIsChainBeingDestroyed(Duktape_GetChain(ctx)))
		{
			ILibAsyncServerSocket_RemoveFromChain(server->server);
			server->server = NULL;

			ILibDuktape_EventEmitter_SetupEmit(ctx, server->self, "close");		// [emit][this][close]
			duk_call_method(ctx, 1);
		}
	}
	return(0);
}

duk_ret_t ILibDuktape_net_server_listening(duk_context *ctx)
{
	duk_push_this(ctx);		// [server]
	ILibDuktape_net_server *server = (ILibDuktape_net_server*)Duktape_GetBufferProperty(ctx, -1, ILibDuktape_net_Server_buffer);

	if (server->server != NULL || duk_has_prop_string(ctx, -1, ILibDuktape_net_WindowsIPC_Buffer))
	{
		duk_push_true(ctx);
	}
	else
	{
		duk_push_false(ctx);
	}

	return(1);
}
duk_ret_t ILibDuktape_net_createServer(duk_context *ctx)
{
	int nargs = duk_get_top(ctx);
	int i;
	ILibDuktape_net_server *server;

	duk_push_current_function(ctx);
	int isTLS = Duktape_GetIntPropertyValue(ctx, -1, "tls", 0);
	duk_pop(ctx);

	duk_push_object(ctx);														// [server]
	ILibDuktape_WriteID(ctx, isTLS ? "tls.Server" : "net.Server");
	if (nargs > 0 && duk_is_object(ctx, 0))
	{
		duk_dup(ctx, 0);														// [server][Options]
		duk_put_prop_string(ctx, -2, ILibDuktape_SERVER2OPTIONS);				// [server]
	}
	server = Duktape_PushBuffer(ctx, sizeof(ILibDuktape_net_server));			// [server][fbuffer]
	duk_put_prop_string(ctx, -2, ILibDuktape_net_Server_buffer);				// [server]
	
	server->isTLS = isTLS;
	server->self = duk_get_heapptr(ctx, -1);
	server->ctx = ctx;
	server->emitter = ILibDuktape_EventEmitter_Create(ctx);
	ILibDuktape_EventEmitter_CreateEventEx(server->emitter, "close");
	ILibDuktape_EventEmitter_CreateEventEx(server->emitter, "connection");
	ILibDuktape_CreateProperty_InstanceMethod(ctx, "close", ILibDuktape_net_server_close, DUK_VARARGS);
#ifndef MICROSTACK_NOTLS
	if (isTLS)
	{
		ILibDuktape_EventEmitter_CreateEventEx(server->emitter, "secureConnection");
		ILibDuktape_EventEmitter_CreateEventEx(server->emitter, "tlsClientError");
		ILibDuktape_CreateInstanceMethod(ctx, "addContext", ILibDuktape_tls_server_addContext, 2);
		duk_push_object(ctx); duk_put_prop_string(ctx, -2, ILibDuktape_SERVER2ContextTable);
		if (ILibDuktape_TLS_ctx2server < 0) { ILibDuktape_TLS_ctx2server = SSL_get_ex_new_index(0, "ILibDuktape_TLS_Server index", NULL, NULL, NULL); }
	}
#endif
	ILibDuktape_EventEmitter_CreateEventEx(server->emitter, "error");
	ILibDuktape_EventEmitter_CreateEventEx(server->emitter, "listening");
	ILibDuktape_CreateEventWithGetter(ctx, "listening", ILibDuktape_net_server_listening);

	ILibDuktape_CreateInstanceMethod(ctx, "listen", ILibDuktape_net_server_listen, DUK_VARARGS);
	ILibDuktape_CreateInstanceMethod(ctx, "address", ILibDuktape_net_server_address, 0);
	ILibDuktape_CreateFinalizer(ctx, ILibDuktape_net_server_Finalizer);

	for (i = 0; i < 2 && i < nargs; ++i)
	{
		if (duk_is_function(ctx, i))
		{
			// Callback
			ILibDuktape_EventEmitter_AddOn(server->emitter, isTLS ? "secureConnection" : "connection", duk_require_heapptr(ctx, i));
		}
		if (duk_is_object(ctx, i))
		{
			// Options
			if (isTLS && !duk_has_prop_string(ctx, i, "secureProtocol"))
			{
				duk_dup(ctx, i);									// [options]
				duk_push_string(ctx, "SSLv23_server_method");		// [options][secureProtocol]
				duk_put_prop_string(ctx, -2, "secureProtocol");		// [options]
				duk_pop(ctx);										// ...
			}
		}
	}

	return 1;
}
duk_ret_t ILibDuktape_net_addr2int(duk_context *ctx)
{
	struct sockaddr_in6 addr6;
	ILibResolveEx((char*)duk_require_string(ctx, 0), 0, &addr6);
	if (addr6.sin6_family == AF_INET)
	{
		duk_push_int(ctx, ((struct sockaddr_in*)&addr6)->sin_addr.s_addr);
		return(1);
	}
	else
	{
		return(ILibDuktape_Error(ctx, "Error converting address"));
	}
}
void ILibDuktape_net_PUSH_net(duk_context *ctx, void *chain)
{
	duk_push_object(ctx);														// [net]
	ILibDuktape_WriteID(ctx, "net");
	duk_push_pointer(ctx, chain);												// [net][chain]
	duk_put_prop_string(ctx, -2, "chain");										// [net]
	duk_push_c_function(ctx, ILibDuktape_net_socket_constructor, DUK_VARARGS);	// [net][constructor]
	duk_push_pointer(ctx, chain);												// [net][constructor][chain]
	duk_put_prop_string(ctx, -2, "chain");										// [net][constructor]
	duk_dup(ctx, -2);															// [net][constructor][net]
	duk_put_prop_string(ctx, -2, "net");										// [net][constructor]
	duk_put_prop_string(ctx, -2, "socket");										// [net]
	ILibDuktape_CreateInstanceMethodWithIntProperty(ctx, "tls", 0, "createServer", ILibDuktape_net_createServer, DUK_VARARGS);
	ILibDuktape_CreateInstanceMethod(ctx, "createConnection", ILibDuktape_net_createConnection, DUK_VARARGS);
	ILibDuktape_CreateInstanceMethod(ctx, "connect", ILibDuktape_net_createConnection, DUK_VARARGS);
	ILibDuktape_CreateInstanceMethod(ctx, "addr2int", ILibDuktape_net_addr2int, 1);
}
duk_ret_t ILibDuktape_globalTunnel_end(duk_context *ctx)
{
	duk_push_heap_stash(ctx);
	duk_del_prop_string(ctx, -1, ILibDuktape_GlobalTunnel_Stash);
	return 0;
}
duk_ret_t ILibDuktape_globalTunnel_initialize(duk_context *ctx)
{
	ILibDuktape_globalTunnel_data *data;

	duk_push_this(ctx);
	duk_get_prop_string(ctx, -1, ILibDuktape_GlobalTunnel_DataPtr);
	data = (ILibDuktape_globalTunnel_data*)Duktape_GetBuffer(ctx, -1, NULL);

	if (duk_has_prop_string(ctx, 0, "host") && duk_has_prop_string(ctx, 0, "port"))
	{
		char *host = Duktape_GetStringPropertyValue(ctx, 0, "host", "127.0.0.1");
		int port = Duktape_GetIntPropertyValue(ctx, 0, "port", 0);
		ILibResolveEx(host, (unsigned short)port, &(data->proxyServer));
		if (data->proxyServer.sin6_family == AF_UNSPEC)
		{
			return(ILibDuktape_Error(ctx, "globalTunnel.initialize(): Error, could not resolve: %s", host));
		}
	}
	else
	{
		return(ILibDuktape_Error(ctx, "globalTunnel.initialize(): Error, invalid parameter"));
	}

	return 0;
}
duk_ret_t ILibDuktape_globalTunnel_finalizer(duk_context *ctx)
{
	ILibDuktape_globalTunnel_data *data;
	duk_get_prop_string(ctx, 0, ILibDuktape_GlobalTunnel_DataPtr);
	data = (ILibDuktape_globalTunnel_data*)Duktape_GetBuffer(ctx, -1, NULL);
	ILibHashtable_Destroy(data->exceptionsTable);
	return 0;
}
duk_ret_t ILibDuktape_globalTunnel_isProxying(duk_context *ctx)
{
	ILibDuktape_globalTunnel_data *data;

	duk_push_this(ctx);
	duk_get_prop_string(ctx, -1, ILibDuktape_GlobalTunnel_DataPtr);
	data = (ILibDuktape_globalTunnel_data*)Duktape_GetBuffer(ctx, -1, NULL);
	if (data->proxyServer.sin6_family == AF_UNSPEC)
	{
		duk_push_false(ctx);
	}
	else
	{
		duk_push_true(ctx);
	}
	return(1);
}
duk_ret_t ILibDuktape_globalTunnel_proxyConfig(duk_context *ctx)
{
	ILibDuktape_globalTunnel_data *data;

	duk_push_this(ctx);
	duk_get_prop_string(ctx, -1, ILibDuktape_GlobalTunnel_DataPtr);
	data = (ILibDuktape_globalTunnel_data*)Duktape_GetBuffer(ctx, -1, NULL);
	if (data->proxyServer.sin6_family == AF_UNSPEC)
	{
		duk_push_null(ctx);
	}
	else
	{
		duk_push_object(ctx);
		duk_push_string(ctx, ILibRemoteLogging_ConvertAddress((struct sockaddr*)&(data->proxyServer)));
		duk_put_prop_string(ctx, -2, "host");
		duk_push_int(ctx, (int)ntohs(data->proxyServer.sin6_port));
		duk_put_prop_string(ctx, -2, "port");																		
	}
	return(1);
}
ILibDuktape_globalTunnel_data* ILibDuktape_GetNewGlobalTunnelEx(duk_context *ctx, int native)
{
	ILibDuktape_globalTunnel_data *retVal;

	if (native != 0) { duk_push_heap_stash(ctx); }						// [stash]

	duk_push_object(ctx);												// [stash][tunnel]
	duk_dup(ctx, -1);													// [stash][tunnel][dup]
	duk_put_prop_string(ctx, -3, ILibDuktape_GlobalTunnel_Stash);		// [stash][tunnel]
	duk_swap_top(ctx, -2);												// [tunnel][stash]
	duk_pop(ctx);														// [tunnel]

	duk_push_fixed_buffer(ctx, sizeof(ILibDuktape_globalTunnel_data));	// [tunnel][buffer]
	retVal = (ILibDuktape_globalTunnel_data*)Duktape_GetBuffer(ctx, -1, NULL);
	memset(retVal, 0, sizeof(ILibDuktape_globalTunnel_data));
	duk_put_prop_string(ctx, -2, ILibDuktape_GlobalTunnel_DataPtr);		// [tunnel]

	retVal->exceptionsTable = ILibHashtable_Create();
	ILibDuktape_CreateInstanceMethod(ctx, "initialize", ILibDuktape_globalTunnel_initialize, DUK_VARARGS);
	ILibDuktape_CreateInstanceMethod(ctx, "end", ILibDuktape_globalTunnel_end, 0);
	ILibDuktape_CreateEventWithGetter(ctx, "proxyConfig", ILibDuktape_globalTunnel_proxyConfig);
	ILibDuktape_CreateEventWithGetter(ctx, "isProxying", ILibDuktape_globalTunnel_isProxying);
	ILibDuktape_CreateFinalizer(ctx, ILibDuktape_globalTunnel_finalizer);

	if (native != 0) { duk_pop(ctx); }									// ...
	return retVal;
}

void ILibDuktape_globalTunnel_PUSH(duk_context *ctx, void *chain)
{
	duk_push_heap_stash(ctx);											// [stash]

	if (duk_has_prop_string(ctx, -1, ILibDuktape_GlobalTunnel_Stash))
	{
		duk_get_prop_string(ctx, -1, ILibDuktape_GlobalTunnel_Stash);	// [stash][tunnel]
		duk_swap_top(ctx, -2);											// [tunnel][stash]
		duk_pop(ctx);													// [tunnel]
	}
	else
	{
		ILibDuktape_GetNewGlobalTunnelEx(ctx, 0);						// [tunnel]
	}
	return;
}
ILibDuktape_globalTunnel_data* ILibDuktape_GetGlobalTunnel(duk_context *ctx)
{
	ILibDuktape_globalTunnel_data *retVal = NULL;

	duk_push_heap_stash(ctx);											// [stash]
	if (duk_has_prop_string(ctx, -1, ILibDuktape_GlobalTunnel_Stash))
	{
		duk_get_prop_string(ctx, -1, ILibDuktape_GlobalTunnel_Stash);	// [stash][tunnel]
		duk_get_prop_string(ctx, -1, ILibDuktape_GlobalTunnel_DataPtr);	// [stash][tunnel][buffer]
		retVal = (ILibDuktape_globalTunnel_data*)Duktape_GetBuffer(ctx, -1, NULL);
		if (retVal->proxyServer.sin6_family == AF_UNSPEC) { retVal = NULL; }
		duk_pop_2(ctx);													// [stash]
	}
	duk_pop(ctx);														// ...
	return retVal;
}


#ifndef MICROSTACK_NOTLS
SSL_CTX* ILibDuktape_TLS_SecureContext_GetCTX(duk_context *ctx, void *secureContext)
{
	SSL_CTX *retVal = NULL;

	duk_push_heapptr(ctx, secureContext);																// [context]
	if (duk_has_prop_string(ctx, -1, ILibDuktape_SecureContext2SSLCTXPTR))
	{
		retVal = (SSL_CTX*)Duktape_GetPointerProperty(ctx, -1, ILibDuktape_SecureContext2SSLCTXPTR);
	}
	duk_pop(ctx);																						// ...
	return(retVal);
}
void ILibDuktape_TLS_X509_PUSH(duk_context *ctx, X509* cert)
{
	char hash[UTIL_SHA384_HASHSIZE];
	char fingerprint[150];

	util_keyhash2(cert, hash);
	util_tohex2(hash, UTIL_SHA384_HASHSIZE, fingerprint);

	duk_push_object(ctx);							// [cert]
	duk_push_string(ctx, fingerprint);				// [cert][fingerprint]
	duk_put_prop_string(ctx, -2, "fingerprint");	// [cert]
}
int ILibDuktape_TLS_verify(int preverify_ok, X509_STORE_CTX *storectx)
{
	STACK_OF(X509) *certChain = X509_STORE_CTX_get_chain(storectx);
	SSL *ssl = (SSL*)X509_STORE_CTX_get_ex_data(storectx, SSL_get_ex_data_X509_STORE_CTX_idx());
	ILibDuktape_net_socket *data = (ILibDuktape_net_socket*)SSL_get_ex_data(ssl, ILibDuktape_TLS_ctx2socket);

	int i;
	int retVal = 0;

	duk_push_heapptr(data->ctx, data->object);													// [Socket]
	duk_get_prop_string(data->ctx, -1, ILibDuktape_SOCKET2OPTIONS);								// [Socket][Options]
	if (Duktape_GetBooleanProperty(data->ctx, -1, "rejectUnauthorized", 1)) { duk_pop_2(data->ctx); return(preverify_ok); }
	void *OnVerify = Duktape_GetHeapptrProperty(data->ctx, -1, "checkServerIdentity");

	if (OnVerify == NULL) { duk_pop_2(data->ctx); return(1); }

	duk_push_heapptr(data->ctx, OnVerify);													// [func]
	duk_push_heapptr(data->ctx, data->object);												// [func][this]
	duk_push_array(data->ctx);																// [func][this][certs]
	for (i = 0; i < sk_X509_num(certChain); ++i)
	{
		ILibDuktape_TLS_X509_PUSH(data->ctx, sk_X509_value(certChain, i));					// [func][this][certs][cert]
		duk_put_prop_index(data->ctx, -2, i);												// [func][this][certs]
	}
	retVal = duk_pcall_method(data->ctx, 1) == 0 ? 1 : 0;									// [undefined]
	duk_pop(data->ctx);																		// ...
	return retVal;
}
int ILibDuktape_TLS_server_verify(int preverify_ok, X509_STORE_CTX *storectx)
{
	STACK_OF(X509) *certChain = X509_STORE_CTX_get_chain(storectx);
	SSL *ssl = (SSL*)X509_STORE_CTX_get_ex_data(storectx, SSL_get_ex_data_X509_STORE_CTX_idx());
	ILibDuktape_net_server *data = (ILibDuktape_net_server*)SSL_get_ex_data(ssl, ILibDuktape_TLS_ctx2server);

	int i;
	int retVal = 0;
	if (!ILibMemory_CanaryOK(data)) { return(0); }


	duk_push_heapptr(data->ctx, data->self);													// [Server]
	duk_get_prop_string(data->ctx, -1, ILibDuktape_SERVER2OPTIONS);								// [Server][Options]
	if (Duktape_GetBooleanProperty(data->ctx, -1, "rejectUnauthorized", 1)) { duk_pop_2(data->ctx); return(preverify_ok); }
	void *OnVerify = Duktape_GetHeapptrProperty(data->ctx, -1, "checkClientIdentity");

	if (OnVerify == NULL) { return(1); }

	duk_push_heapptr(data->ctx, OnVerify);													// [func]
	duk_push_heapptr(data->ctx, data->self);												// [func][this]
	duk_push_array(data->ctx);																// [func][this][certs]
	for (i = 0; i < sk_X509_num(certChain); ++i)
	{
		ILibDuktape_TLS_X509_PUSH(data->ctx, sk_X509_value(certChain, i));					// [func][this][certs][cert]
		duk_put_prop_index(data->ctx, -2, i);												// [func][this][certs]
	}
	retVal = duk_pcall_method(data->ctx, 1) == 0 ? 1 : 0;									// [undefined]
	duk_pop(data->ctx);																		// ...
	return retVal;
}
void ILibDuktape_tls_server_OnSSL(ILibAsyncServerSocket_ServerModule AsyncServerSocketModule, void *ConnectionToken, SSL* ctx, void **user)
{
	ILibDuktape_net_server *server = (ILibDuktape_net_server*)ILibAsyncServerSocket_GetTag(AsyncServerSocketModule);
	if (!ILibMemory_CanaryOK(server)) { return; }

	if (ctx != NULL && ILibDuktape_TLS_ctx2server)
	{
		SSL_set_ex_data(ctx, ILibDuktape_TLS_ctx2server, server);
	}
}
static int ILibDuktape_tls_server_sniCallback(SSL *s, int *ad, void *arg)
{
	const char *servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
	ILibDuktape_net_server *data = (ILibDuktape_net_server*)SSL_get_ex_data(s, ILibDuktape_TLS_ctx2server);
	if (!ILibMemory_CanaryOK(data)) { return(SSL_TLSEXT_ERR_OK); }

	duk_push_heapptr(data->ctx, data->self);								// [server]
	duk_get_prop_string(data->ctx, -1, ILibDuktape_SERVER2ContextTable);	// [server][table]
	if (duk_has_prop_string(data->ctx, -1, servername))
	{
		duk_get_prop_string(data->ctx, -1, servername);						// [server][table][secureContext]
		SSL_CTX *newCTX = ILibDuktape_TLS_SecureContext_GetCTX(data->ctx, duk_get_heapptr(data->ctx, -1));
		if (newCTX != NULL)
		{
			SSL_set_SSL_CTX(s, newCTX);
		}
		duk_pop(data->ctx);													// [server][table]
	}
	duk_pop_2(data->ctx);													// ...
	return(SSL_TLSEXT_ERR_OK);
}
duk_ret_t ILibDuktape_tls_server_addContext(duk_context *ctx)
{
	duk_size_t hostLen;
	char *host = (char*)duk_get_lstring(ctx, 0, &hostLen);
	void *context = duk_require_heapptr(ctx, 1);

	duk_push_this(ctx);												// [server]
	duk_get_prop_string(ctx, -1, ILibDuktape_SERVER2ContextTable);	// [server][table]
	duk_dup(ctx, 0);												// [server][table][host]
	duk_dup(ctx, 1);												// [server][table][host][context]
	duk_put_prop(ctx, -3);											// [server][table]

	if (hostLen == 1 && strncasecmp(host, "*", 1) == 0)
	{
		// Default CTX
		SSL_CTX *ssl_ctx = ILibDuktape_TLS_SecureContext_GetCTX(ctx, context);
		duk_get_prop_string(ctx, -2, ILibDuktape_SERVER2OPTIONS);	// [server][table][options]
		if (Duktape_GetBooleanProperty(ctx, -1, "requestCert", 0) || Duktape_GetHeapptrProperty(ctx, -1, "checkClientIdentity")!=NULL)
		{
			SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, ILibDuktape_TLS_server_verify);
		}
		duk_get_prop_string(ctx, -3, ILibDuktape_net_Server_buffer);// [server][table][options][buffer]
		ILibDuktape_net_server *server = (ILibDuktape_net_server*)Duktape_GetBuffer(ctx, -1, NULL);
		if (server->server != NULL)
		{
#ifdef MICROSTACK_TLS_DETECT
			ILibAsyncServerSocket_SetSSL_CTX(server->server, ssl_ctx, 1);
#else
			ILibAsyncServerSocket_SetSSL_CTX(server->server, ssl_ctx);
#endif
			ILibAsyncServerSocket_SSL_SetSink(server->server, ILibDuktape_tls_server_OnSSL);
		}
		SSL_CTX_set_tlsext_servername_callback(ssl_ctx, ILibDuktape_tls_server_sniCallback);
	}

	return(0);
}
void ILibDuktape_TLS_connect_resolveError(duk_context *ctx, void ** args, int argsLen)
{
	ILibDuktape_net_socket *data = (ILibDuktape_net_socket*)args[0];

	ILibDuktape_EventEmitter_SetupEmit(ctx, data->emitter->object, "error");	// [emit][this][error]
	duk_push_heapptr(ctx, args[1]);												// [emit][this][error][err]
	if (duk_pcall_method(ctx, 2) != 0) { ILibDuktape_Process_UncaughtExceptionEx(ctx, "tls.socket.OnError(): "); }
	duk_pop(ctx);
}
duk_ret_t ILibDuktape_TLS_connect(duk_context *ctx)
{
	int nargs = duk_get_top(ctx), i;
	if (nargs > 0 && duk_is_number(ctx, 0))
	{
		// tls.connect(port[, host][, options][, callback])
		// let's convert to the other overload
		duk_push_this(ctx);							// [TLS]
		duk_get_prop_string(ctx, -1, "connect");	// [TLS][connect]
		duk_swap_top(ctx, -2);						// [connect][this]
		for (i = 1; i < nargs; ++i)
		{
			if (duk_is_object(ctx, i))
			{
				duk_dup(ctx, i);					// [connect][this][Options]
				break;
			}
		}
		if (i == nargs) { duk_push_object(ctx); }	// [connect][this][Options]
		duk_dup(ctx, 0);							// [connect][this][Options][port]
		duk_put_prop_string(ctx, -2, "port");
		if (nargs > 1 && duk_is_string(ctx, 1))
		{
			duk_dup(ctx, 1);						// [connect][this][Options][host]
		}
		else
		{
			duk_push_string(ctx, "127.0.0.1");		// [connect][this][Options][host]
		}
		duk_put_prop_string(ctx, -2, "host");		// [connect][this][Options]
		for (i = 1; i < nargs; ++i)
		{
			if (duk_is_function(ctx, i))
			{
				duk_dup(ctx, i);					// [connect][this][Options][callback]
				break;
			}
		}
		duk_call_method(ctx, i == nargs ? 1 : 2);	// [socket]
		return(1);
	}

	// tls.connect(options[, callback])
	ILibAsyncSocket_SocketModule module = ILibCreateAsyncSocketModuleWithMemory(Duktape_GetChain(ctx), 4096, ILibDuktape_net_socket_OnData, ILibDuktape_net_socket_OnConnect, ILibDuktape_net_socket_OnDisconnect, ILibDuktape_net_socket_OnSendOK, sizeof(ILibDuktape_net_socket));
	ILibDuktape_net_socket *data = (ILibDuktape_net_socket*)((ILibChain_Link*)module)->ExtraMemoryPtr;

	if (ILibDuktape_TLS_ctx2socket < 0)
	{
		ILibDuktape_TLS_ctx2socket = SSL_get_ex_new_index(0, "ILibDuktape_TLS index", NULL, NULL, NULL);
	}

	ILibDuktape_net_socket_PUSH(ctx, module);													// [socket]
	ILibDuktape_WriteID(ctx, "tls.socket");
	duk_dup(ctx, 0);																			// [socket][options]
	if (duk_has_prop_string(ctx, -1, "secureContext"))
	{
		duk_get_prop_string(ctx, -1, "secureContext");											// [socket][options][secureContext]
	}
	else
	{
		duk_push_this(ctx);																		// [socket][options][tls]
		duk_get_prop_string(ctx, -1, "createSecureContext");									// [socket][options][tls][createSecureContext]
		duk_swap_top(ctx, -2);																	// [socket][options][createSecureContext][this]
		duk_dup(ctx, -3);																		// [socket][options][createSecureContext][this][options]
		duk_call_method(ctx, 1);																// [socket][options][secureContext]
	}
	if ((data->ssl_ctx = (SSL_CTX*)Duktape_GetPointerProperty(ctx, -1, ILibDuktape_SecureContext2SSLCTXPTR)) == NULL)
	{
		return(ILibDuktape_Error(ctx, "Invalid SecureContext Object"));
	}
	SSL_CTX_set_verify(data->ssl_ctx, SSL_VERIFY_PEER, ILibDuktape_TLS_verify); /* Ask for authentication */

	duk_remove(ctx, -2);																		// [socket][secureContext]
	duk_put_prop_string(ctx, -2, ILibDuktape_TLSSocket2SecureContext);

	duk_dup(ctx, 0);																			// [socket][options]
	duk_put_prop_string(ctx, -2, ILibDuktape_SOCKET2OPTIONS);									// [socket]
	ILibDuktape_EventEmitter_CreateEventEx(data->emitter, "secureConnect");
	if (nargs > 0 && duk_is_function(ctx, 1))
	{
		ILibDuktape_EventEmitter_AddOnce(data->emitter, "secureConnect", duk_require_heapptr(ctx, 1));
	}

	duk_size_t hostLen;
	char *host = Duktape_GetStringPropertyValueEx(ctx, 0, "host", "127.0.0.1", &hostLen);
	char *sniname = Duktape_GetStringPropertyValue(ctx, 0, "servername", host);
	int port = Duktape_GetIntPropertyValue(ctx, 0, "port", 0);
	struct sockaddr_in6 dest;
	struct sockaddr_in6 proxy;
	memset(&dest, 0, sizeof(struct sockaddr_in6));
	memset(&proxy, 0, sizeof(struct sockaddr_in6));

	if (duk_has_prop_string(ctx, 0, "proxy"))
	{
		duk_get_prop_string(ctx, 0, "proxy");
		ILibResolveEx(Duktape_GetStringPropertyValue(ctx, -1, "host", NULL), (unsigned short)Duktape_GetIntPropertyValue(ctx, -1, "port", 0), &proxy);
		duk_pop(ctx);
	}

	if (hostLen > 0 && hostLen < 1024 && host[0] == '[')
	{
		char hostCopy[1024];
		int pct = ILibString_LastIndexOf(host, (int)hostLen, "%", 1);

		memcpy_s(hostCopy, sizeof(hostCopy), host, hostLen);

		hostCopy[(int)hostLen - 1] = 0;
		if (pct > 0)
		{
			hostCopy[pct] = 0;
			pct = atoi(hostCopy + pct + 1);
		}
		else
		{
			pct = -1;
		}

		memset(&dest, 0, sizeof(struct sockaddr_in6));
		dest.sin6_family = AF_INET6;
		ILibInet_pton(AF_INET6, hostCopy + 1, &(dest.sin6_addr));
		if (pct >= 0)
		{
			dest.sin6_scope_id = pct;
		}
		dest.sin6_port = (unsigned short)htons(port);
	}
	else
	{
		ILibResolveEx(host, (unsigned short)port, &dest);
	}
	if (dest.sin6_family == AF_UNSPEC || (duk_has_prop_string(ctx, 0, "proxy") && proxy.sin6_family == AF_UNSPEC))
	{
		// Can't resolve... Delay event emit, until next event loop, because if app called net.createConnection(), they don't have the socket yet
		duk_push_error_object(ctx, DUK_ERR_ERROR, "tls.socket.connect(): Cannot resolve host '%s'", host);
		void *imm = ILibDuktape_Immediate(ctx, (void*[]) { data, duk_get_heapptr(ctx, -1) }, 2, ILibDuktape_TLS_connect_resolveError);
		duk_push_heapptr(ctx, imm);					// [socket][err][imm]
		duk_swap_top(ctx, -2);						// [socket][imm][err]
		duk_put_prop_string(ctx, -2, "\xFF_tmp");	// [socket][imm]
		duk_pop(ctx);								// [socket]
	}
	else
	{
		if (duk_has_prop_string(ctx, 0, "proxy"))
		{
			duk_get_prop_string(ctx, 0, "proxy");
			ILibAsyncSocket_ConnectToProxy(data->socketModule, NULL, (struct sockaddr*)&dest, (struct sockaddr*)&proxy, Duktape_GetStringPropertyValue(ctx, -1, "username", NULL), Duktape_GetStringPropertyValue(ctx, -1, "password", NULL), NULL, data);
			duk_pop(ctx);
		}
		else
		{
			ILibAsyncSocket_ConnectTo(data->socketModule, NULL, (struct sockaddr*)&dest, NULL, data);
		}
		data->ssl = ILibAsyncSocket_SetSSLContextEx(data->socketModule, data->ssl_ctx, ILibAsyncSocket_TLS_Mode_Client, sniname);
		SSL_set_ex_data(data->ssl, ILibDuktape_TLS_ctx2socket, data);
	}

	return(1);
}
duk_ret_t ILibDuktape_TLS_secureContext_Finalizer(duk_context *ctx)
{
	SSL_CTX_free(ILibDuktape_TLS_SecureContext_GetCTX(ctx, duk_require_heapptr(ctx, 0)));

	duk_get_prop_string(ctx, 0, ILibDuktape_SecureContext2CertBuffer);
	struct util_cert *cert = (struct util_cert*)Duktape_GetBuffer(ctx, -1, NULL);
	util_freecert(cert);
	return(0);
}
duk_ret_t ILibDuktape_TLS_createSecureContext(duk_context *ctx)
{
	duk_push_object(ctx);																				// [secureContext]
	ILibDuktape_WriteID(ctx, "tls.secureContext");	
	struct util_cert *cert = (struct util_cert*)duk_push_fixed_buffer(ctx, sizeof(struct util_cert));	// [secureContext][cert]			
	duk_put_prop_string(ctx, -2, ILibDuktape_SecureContext2CertBuffer);									// [secureContext]
	memset(cert, 0, sizeof(struct util_cert));
	ILibDuktape_CreateFinalizer(ctx, ILibDuktape_TLS_secureContext_Finalizer);

	duk_size_t secureProtocolLen;
	char *secureProtocol = (char*)Duktape_GetStringPropertyValueEx(ctx, 0, "secureProtocol", "SSLv23_method", &secureProtocolLen);
	SSL_CTX *ssl_ctx = NULL;

	if (secureProtocolLen == 13 && strncmp(secureProtocol, "SSLv23_method", 13) == 0)
	{
		ssl_ctx = SSL_CTX_new(TLS_method());
		SSL_CTX_set_options(ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1);
	}
	else if (secureProtocolLen == 20 && strncmp(secureProtocol, "SSLv23_client_method", 20) == 0)
	{
		ssl_ctx = SSL_CTX_new(TLS_method());
		SSL_CTX_set_options(ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1);
	}
	else if (secureProtocolLen == 20 && strncmp(secureProtocol, "SSLv23_server_method", 20) == 0)
	{
		ssl_ctx = SSL_CTX_new(TLS_method());
		SSL_CTX_set_options(ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1);
	}
	else if (secureProtocolLen == 12 && strncmp(secureProtocol, "TLSv1_method", 12) == 0)
	{
		ssl_ctx = SSL_CTX_new(TLS_method());
		SSL_CTX_set_options(ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2);
	}
	else if (secureProtocolLen == 19 && strncmp(secureProtocol, "TLSv1_client_method", 19) == 0)
	{
		ssl_ctx = SSL_CTX_new(TLS_method());
		SSL_CTX_set_options(ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2);
	}
	else if (secureProtocolLen == 19 && strncmp(secureProtocol, "TLSv1_server_method", 19) == 0)
	{
		ssl_ctx = SSL_CTX_new(TLS_method());
		SSL_CTX_set_options(ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2);
	}
	else if (secureProtocolLen == 14 && strncmp(secureProtocol, "TLSv1_1_method", 14) == 0)
	{
		ssl_ctx = SSL_CTX_new(TLS_method());
		SSL_CTX_set_options(ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_2);
	}
	else if (secureProtocolLen == 21 && strncmp(secureProtocol, "TLSv1_1_client_method", 21) == 0)
	{
		ssl_ctx = SSL_CTX_new(TLS_method());
		SSL_CTX_set_options(ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_2);
	}
	else if (secureProtocolLen == 21 && strncmp(secureProtocol, "TLSv1_1_server_method", 21) == 0)
	{
		ssl_ctx = SSL_CTX_new(TLS_method());
		SSL_CTX_set_options(ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_2);
	}
	else if (secureProtocolLen == 14 && strncmp(secureProtocol, "TLSv1_2_method", 14) == 0)
	{
		ssl_ctx = SSL_CTX_new(TLS_method());
		SSL_CTX_set_options(ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
	}
	else if (secureProtocolLen == 21 && strncmp(secureProtocol, "TLSv1_2_client_method", 21) == 0)
	{
		ssl_ctx = SSL_CTX_new(TLS_method());
		SSL_CTX_set_options(ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
	}
	else if (secureProtocolLen == 21 && strncmp(secureProtocol, "TLSv1_2_server_method", 21) == 0)
	{
		ssl_ctx = SSL_CTX_new(TLS_method());
		SSL_CTX_set_options(ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
	}
	else if (secureProtocolLen == 11 && strncmp(secureProtocol, "DTLS_method", 11) == 0)
	{
		ssl_ctx = SSL_CTX_new(DTLS_method());
	}
	else
	{
		return(ILibDuktape_Error(ctx, "tls.createSecureContext(): secureProtocol[%s] not supported at this time", secureProtocol));
	}
	duk_push_pointer(ctx, ssl_ctx); duk_put_prop_string(ctx, -2, ILibDuktape_SecureContext2SSLCTXPTR);

	if (duk_has_prop_string(ctx, 0, "pfx") && duk_has_prop_string(ctx, 0, "passphrase"))
	{
		duk_get_prop_string(ctx, 0, "pfx");													// [secureContext][pfx]
		duk_size_t pfxLen;
		char *pfx = (char*)Duktape_GetBuffer(ctx, -1, &pfxLen);
		if (util_from_p12(pfx, (int)pfxLen, Duktape_GetStringPropertyValue(ctx, 0, "passphrase", ""), cert) == 0)
		{
			// Failed to load certificate
			return(ILibDuktape_Error(ctx, "tls.createSecureContext(): Invalid passphrase"));
		}
		duk_pop(ctx);
		SSL_CTX_use_certificate(ssl_ctx, cert->x509);
		SSL_CTX_use_PrivateKey(ssl_ctx, cert->pkey);
	}

	return(1);
}
duk_ret_t ILibDuktape_TLS_generateCertificate(duk_context *ctx)
{
	char *passphrase = (char*)duk_require_string(ctx, 0);
	int len;
	struct util_cert cert;
	char *data;

	len = util_mkCert(NULL, &(cert), 3072, 10000, "localhost", CERTIFICATE_TLS_CLIENT, NULL);
	len = util_to_p12(cert, passphrase, &data);

	duk_push_fixed_buffer(ctx, len);
	memcpy_s((void*)Duktape_GetBuffer(ctx, -1, NULL), len, data, len);
	duk_push_buffer_object(ctx, -1, 0, len, DUK_BUFOBJ_NODEJS_BUFFER);
	ILibDuktape_WriteID(ctx, "tls.pfxCertificate");
	util_free(data);
	util_freecert(&cert);
	return 1;
}
duk_ret_t ILibDuktape_TLS_loadpkcs7b(duk_context *ctx)
{
	duk_size_t len;
	char *buffer = (char*)Duktape_GetBuffer(ctx, 0, &len);
	int val = util_from_pkcs7b_string(buffer, (int)len, NULL, 0);
	char *out;

	if (val > 0)
	{
		duk_push_fixed_buffer(ctx, val);
		out = Duktape_GetBuffer(ctx, -1, NULL);
		duk_push_buffer_object(ctx, -1, 0, val, DUK_BUFOBJ_NODEJS_BUFFER);
		util_from_pkcs7b_string(buffer, (int)len, out, val);
		return(1);
	}
	else
	{
		return(ILibDuktape_Error(ctx, "Error reading pkcs7b data"));
	}
}

duk_ret_t ILibDuktape_TLS_loadCertificate_finalizer(duk_context *ctx)
{
	struct util_cert *cert = (struct util_cert*)Duktape_GetBufferProperty(ctx, 0, ILibDuktape_TLS_util_cert);
	util_freecert(cert);
	return(0);
}
duk_ret_t ILibDuktape_TLS_loadCertificate_getKeyHash(duk_context *ctx)
{
	duk_push_this(ctx);
	struct util_cert *cert = (struct util_cert*)Duktape_GetBufferProperty(ctx, -1, ILibDuktape_TLS_util_cert);
	char *hash = duk_push_fixed_buffer(ctx, UTIL_SHA384_HASHSIZE);
	duk_push_buffer_object(ctx, -1, 0, UTIL_SHA384_HASHSIZE, DUK_BUFOBJ_NODEJS_BUFFER);
	util_keyhash(cert[0], hash);
	return(1);
}
duk_ret_t ILibDuktape_TLS_toDER(duk_context *ctx)
{
	duk_push_this(ctx);
	struct util_cert *cert = (struct util_cert*)Duktape_GetBufferProperty(ctx, -1, ILibDuktape_TLS_util_cert);
	int outLen = i2d_X509(cert->x509, NULL); 
	if (outLen <= 0)
	{
		return(ILibDuktape_Error(ctx, "Certificate Error"));
	}

	char *out = duk_push_fixed_buffer(ctx, outLen);
	duk_push_buffer_object(ctx, -1, 0, outLen, DUK_BUFOBJ_NODEJS_BUFFER);
	i2d_X509(cert->x509, (unsigned char**)&out);
	return(1);
}
duk_ret_t ILibDuktape_TLS_loadCertificate(duk_context *ctx)
{
	duk_size_t pfxLen, derLen;
	char *pfx = Duktape_GetBufferPropertyEx(ctx, 0, "pfx", &pfxLen);
	char *der = Duktape_GetBufferPropertyEx(ctx, 0, "der", &derLen);
	if (der == NULL) { der = Duktape_GetBufferPropertyEx(ctx, 0, "cer", &derLen); }

	if (pfx != NULL || der != NULL)
	{
		duk_push_object(ctx);
		ILibDuktape_WriteID(ctx, "tls.certificate");
		struct util_cert *cert = (struct util_cert*)Duktape_PushBuffer(ctx, sizeof(struct util_cert));
		duk_put_prop_string(ctx, -2, ILibDuktape_TLS_util_cert);
		if (pfx != NULL)
		{
			if (util_from_p12(pfx, (int)pfxLen, Duktape_GetStringPropertyValue(ctx, 0, "passphrase", NULL), cert) == 0)
			{
				// Failed to load certificate
				return(ILibDuktape_Error(ctx, "tls.loadCertificate(): Invalid passphrase"));
			}
		}
		else if (der != NULL)
		{
			if (util_from_cer(der, (int)derLen, cert) == 0)
			{
				// Failed to load certificate
				return(ILibDuktape_Error(ctx, "tls.loadCertificate(): Failed to parse Certificate (%s)", ERR_reason_error_string(ERR_get_error())));
			}
		}
		ILibDuktape_CreateFinalizer(ctx, ILibDuktape_TLS_loadCertificate_finalizer);
		ILibDuktape_CreateInstanceMethod(ctx, "getKeyHash", ILibDuktape_TLS_loadCertificate_getKeyHash, 0);
		ILibDuktape_CreateInstanceMethod(ctx, "toDER", ILibDuktape_TLS_toDER, 0);
		return(1);
	}
	else
	{
		return(ILibDuktape_Error(ctx, "tls.loadCertificate(): No certificate format specified"));
	}
}
void ILibDuktape_tls_PUSH(duk_context *ctx, void *chain)
{
	duk_push_object(ctx);				// [TLS]
	ILibDuktape_CreateInstanceMethodWithIntProperty(ctx, "tls", 1, "createServer", ILibDuktape_net_createServer, DUK_VARARGS);
	ILibDuktape_CreateInstanceMethod(ctx, "connect", ILibDuktape_TLS_connect, DUK_VARARGS);
	ILibDuktape_CreateInstanceMethod(ctx, "createSecureContext", ILibDuktape_TLS_createSecureContext, 1);
	ILibDuktape_CreateInstanceMethod(ctx, "generateCertificate", ILibDuktape_TLS_generateCertificate, 1);
	ILibDuktape_CreateInstanceMethod(ctx, "loadCertificate", ILibDuktape_TLS_loadCertificate, 1);
	ILibDuktape_CreateInstanceMethod(ctx, "loadpkcs7b", ILibDuktape_TLS_loadpkcs7b, 1);

	char generateRandomInteger[] = "exports.generateRandomInteger = function generateRandomInteger(low, high)\
									{\
										return(require('bignum').randomRange(require('bignum')(low), require('bignum')(high)).toString());\
									};";
	ILibDuktape_ModSearch_AddHandler_AlsoIncludeJS(ctx, generateRandomInteger, sizeof(generateRandomInteger) - 1);
}
#endif

void ILibDuktape_net_init(duk_context * ctx, void * chain)
{
	ILibDuktape_ModSearch_AddHandler(ctx, "net", ILibDuktape_net_PUSH_net);
	ILibDuktape_ModSearch_AddHandler(ctx, "global-tunnel", ILibDuktape_globalTunnel_PUSH);
#ifndef MICROSTACK_NOTLS
	ILibDuktape_ModSearch_AddHandler(ctx, "tls", ILibDuktape_tls_PUSH);
#endif
}


#ifdef __DOXY__
/*!
\implements DuplexStream
\brief TCP socket abstraction
*/
class Socket
{
public:
	/*!
	\brief If true, 'connect' was called and hasn't finished yet. Will be set to 'false' before 'connect' event is emitted.
	*/
	bool connecting;
	/*!
	\brief The string representation of the local IP address the remote client is connecting on.
	*/
	String localAddress;
	/*!
	\brief The numeric representation of the local port
	*/
	integer localPort;
	/*!
	\brief The string representation of the remote IP address.
	*/
	String remoteAddress;
	/*!
	\brief The amount of bytes sent.
	*/
	integer bytesWritten;

	/*!
	\brief Event emitted if the socket times out from inactivity. This is only to notify that the socket has been idle. The user must manually close the connection.
	*/
	void timeout;
	/*!
	\brief The 'close' event is emitted when the stream and any of its underlying resources have been closed.
	*
	The event indicates that no more events will be emitted, and no further computation will occur.
	*/
	void close;
	/*!
	\brief Event emitted when a socket connection is successfully established
	*/
	void connect;
	/*!
	\brief The 'error' event is emitted if an error occurred while writing or piping data.
	\param arg Error argument describing the error that occured
	*/
	void error;

	/*!
	\brief Initiate a connection on a given socket
	*
	\par Possible signatures:\n
	Socket connect(options[, connectListener]);\n
	Socket connect(port[, host][, connectListener]);\n
	\param options <Object> with the following fields:\n
	<b>port</b> <number> Required. Port the socket should connect to.\n
	<b>host</b> \<String\> Host the socket should connect to. Defaults to 'localhost'.\n
	<b>localAddress</b> \<String\> Local address the socket should connect from.\n
	<b>localPort</b> <number> Local port the socket should connect from.\n
	<b>family</b> <number>: Version of IP stack, can be either 4 or 6. Defaults to 4.\n
	\param connectListener <func> that will be added as one time listener for 'connect' event.
	*/
	Socket connect();
	/*!
	\brief Returns the bound address, the address family name, and port of the socket as reported by the OS (ie: {port: 12345, family: 'IPv4', address: '127.0.0.1'})
	*/
	Object address();
	/*!
	\brief Sets the socket to timeout after timeout milliseconds of inactivity on the socket. By default Socket do not have a timeout.
	*
	When an idle timeout is triggered the socket will receive a 'timeout' event but the connection <b>will not</b> be severed.
	\param milliseconds <integer> Number of milliseconds to set the idle timeout to
	\param timeout <func> Optional callback will be set as one time listener for to the 'timeout' event.
	*/
	void setTimeout(milliseconds[, timeout]);
};
#endif
