/*
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.
*/

#ifndef MICROSTACK_NOTLS

#if defined(WINSOCK2)
#include <winsock2.h>
#include <ws2tcpip.h>
#endif

#include "ILibParsers.h"
#include "ILibAsyncSocket.h"
#include "ILibWebRTC.h"
#include "ILibWrapperWebRTC.h"
#include "ILibRemoteLogging.h"

#if defined(WIN32) && !defined(_WIN32_WCE)
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#endif

#define ILibWrapper_WebRTC_ConnectionFactory_ConnectionBucketSize 16	// *MUST* be a power of 2
#define ILibWrapper_WebRTC_Connection_DataChannelsBucketSize 16			// *MUST* be a power of 2
#define ILibWrapper_WebRTC_MaxStunServerPathLength 255

#define INET_SOCKADDR_LENGTH(x) ((x==AF_INET6?sizeof(struct sockaddr_in6):sizeof(struct sockaddr_in)))
#define INET_SOCKADDR_PORT(x) (x->sa_family==AF_INET6?(unsigned short)(((struct sockaddr_in6*)x)->sin6_port):(unsigned short)(((struct sockaddr_in*)x)->sin_port))

#define ILibBlockToSDP_CREDENTIALS_ONLY 0x00
#define ILibBlockToSDP_CREDENTIALS_ONLY_WITH_ALLOCATE (void*)0x01

char g_SessionRandom[32];
unsigned int g_SessionRandomId;
unsigned int g_nextiv = 0;
int ILibWrapper_WebRTC_ConnectionFactoryIndex = -1;

typedef struct
{
	ILibChain_Link ChainLink;

	ILibWrapper_WebRTC_ConnectionFactory_STUNHandler mStunHandler;
	ILibWrapper_WebRTC_ConnectionFactory mStunModule; 
	ILibSparseArray Connections;
	int NextConnectionID;
	struct util_cert cert;
	SSL_CTX* ctx;
	char tlsServerCertThumbprint[UTIL_SHA256_HASHSIZE];

	ILibWebRTC_TURN_ConnectFlags mTurnSetting;
	struct sockaddr_in6 mTurnServer;
}ILibWrapper_WebRTC_ConnectionFactoryStruct;

typedef struct ILibWrapper_WebRTC_ConnectionStruct
{
	void *dtlsSession;
	void *User1, *User2, *User3;
	ILibWrapper_WebRTC_ConnectionFactoryStruct *mFactory;
	char** stunServerList;
	char* stunServerFlags;
	int stunServerListLength;
	int stunIndex;
	int isConnected;

	int id;
	char localUsername[9];
	char localPassword[33];

	char *remoteUsername;
	char* remotePassword;
	char *remoteOfferBlock;

	char* offerBlock;
	int offerBlockLen;
	int isOfferInitiator;
	int isDtlsClient;

	ILibSparseArray DataChannels;

	ILibWrapper_WebRTC_OnConnectionCandidate OnCandidates;
	ILibWrapper_WebRTC_Connection_OnConnect OnConnected;
	ILibWrapper_WebRTC_Connection_OnDataChannel OnDataChannel;
	ILibWrapper_WebRTC_Connection_OnSendOK OnSendOK;

	ILibWrapper_WebRTC_Connection_Debug_OnEvent OnDebugEvent;
}ILibWrapper_WebRTC_ConnectionStruct;

// Prototypes:

int ILibWrapper_WebRTC_PerformStun(ILibWrapper_WebRTC_ConnectionStruct *connection);
char* ILibWrapper_WebRTC_Connection_GetLocalUsername(ILibWrapper_WebRTC_Connection connection)
{
	return(((ILibWrapper_WebRTC_ConnectionStruct*)connection)->localUsername);
}
void* ILibWrapper_WebRTC_Connection_GetStunModule(ILibWrapper_WebRTC_Connection connection)
{
	return(((ILibWrapper_WebRTC_ConnectionStruct*)connection)->mFactory->mStunModule);
}
void* ILibWrapper_WebRTC_DtlsSessionToSSL(void *dtls)
{
	return(ILibWebRTC_Dtls2SSL(dtls));
}
ILibTransport_DoneState ILibWrapper_ILibTransport_SendSink(void *transport, char* buffer, int bufferLength, ILibTransport_MemoryOwnership ownership, ILibTransport_DoneState done)
{
	ILibTransport_DoneState retVal = ILibWrapper_WebRTC_DataChannel_Send((ILibWrapper_WebRTC_DataChannel*)transport, buffer, bufferLength);

	UNREFERENCED_PARAMETER(done);

	if (ownership == ILibTransport_MemoryOwnership_CHAIN) free(buffer);
	return retVal;
}
unsigned int ILibWrapper_ILibTransport_PendingBytesPtr(void *transport)
{
	return(ILibSCTP_GetPendingBytesToSend(((ILibWrapper_WebRTC_ConnectionStruct*)((ILibWrapper_WebRTC_DataChannel*)transport)->parent)->dtlsSession));
}
void ILibWrapper_InitializeDataChannel_Transport(ILibWrapper_WebRTC_DataChannel *dataChannel)
{ 
	dataChannel->Header.transport.ChainLink.ParentChain = ((ILibWrapper_WebRTC_ConnectionStruct*)dataChannel->parent)->mFactory->ChainLink.ParentChain;
	dataChannel->Header.transport.IdentifierFlags = ILibTransports_WebRTC_DataChannel;
	dataChannel->Header.transport.ClosePtr = (ILibTransport_ClosePtr)ILibWrapper_WebRTC_DataChannel_Close;
	dataChannel->Header.transport.SendPtr = (ILibTransport_SendPtr)ILibWrapper_ILibTransport_SendSink;
	dataChannel->Header.transport.PendingBytesPtr = (ILibTransport_PendingBytesToSendPtr)ILibWrapper_ILibTransport_PendingBytesPtr;
}

short ILibWrapper_ReadShort(char* buffer, int offset)
{
	return(ntohs(((short*)(buffer+offset))[0]));
}
int ILibWrapper_ReadInt(char* buffer, int offset)
{
	return(ntohl(((int*)(buffer+offset))[0]));
}
char* ILibWrapper_ReadString(char* buffer, int offset, int length)
{
	char* retVal;
	if((retVal = (char*)malloc(length+1)) == NULL) {ILIBCRITICALEXIT(254);}
	memcpy_s(retVal,length + 1,  buffer+offset, length);
	retVal[length] = 0;
	return retVal;
}

unsigned short ILibWrapper_GetNextStreamId(ILibWrapper_WebRTC_ConnectionStruct *connection)
{
	unsigned short id = 0;
	int done = 0;

	ILibSparseArray_Lock(connection->DataChannels);

	while(done==0)
	{
		done = 1;
		while(++id % 2 == (connection->isDtlsClient==0 ? 0 : 1));
		if(ILibSparseArray_Get(connection->DataChannels, (int)id)!=NULL) {done=0;}
	}
	ILibSparseArray_UnLock(connection->DataChannels);

	return(id);
}

char* ILibWrapper_SdpToBlock(char* sdp, int sdpLen, int *isActive, char **username, char **password)
{
	struct parser_result *pr = NULL;
	struct parser_result_field *f;

	int ptr;
	int blockLen;
	void* candidates = NULL;
	char* lines;
	char* dtlshash = NULL;
	int dtlsHashLen = 0;
	int candidatecount = 0;
	int BlockFlags = 0;
	int passwordLen = 0, usernameLen = 0;
	char *retVal = NULL;
	*isActive = 0;
	*username = NULL;
	*password = NULL;
	

	ILibCreateStack(&candidates);

	lines = ILibString_Replace(sdp, sdpLen, "\n", 1, "\r", 1);
	pr = ILibParseString(lines, 0, sdpLen, "\r", 1);
	f = pr->FirstResult;
	while(f!=NULL)
	{
		if(f->datalength == 0) { f = f->NextResult; continue; }

		f->data[f->datalength] = 0;
		if(strcmp(f->data, "a=setup:passive")==0)
        {			
			BlockFlags |= ILibWebRTC_SDP_Flags_DTLS_SERVER;
        }
		else if(strcmp(f->data, "a=setup:active")==0 || strcmp(f->data, "a=setup:actpass")==0)
		{
			*isActive = 1;
		}

		if (f->datalength > 12 && strncmp(f->data, "a=ice-ufrag:", 12) == 0) { *username = f->data + 12; usernameLen = (int)strnlen_s(*username, f->datalength - 12); }
		if (f->datalength > 10 && strncmp(f->data, "a=ice-pwd:", 10) == 0) { *password = f->data + 10; passwordLen = (int)strnlen_s(*password, f->datalength - 10); }

		if(f->datalength > 22 && strncmp(f->data, "a=fingerprint:sha-256 ", 22)==0)
		{
			char* tmp = ILibString_Replace(f->data + 22, f->datalength - 22, ":", 1, "", 0);
			dtlsHashLen = util_hexToBuf(tmp, (int)strnlen_s(tmp, f->datalength - 22), tmp);
			dtlshash = tmp;
		}

		if(f->datalength > 12 && strncmp(f->data, "a=candidate:", 12)==0)
        {
			struct parser_result* pr2 = ILibParseString(f->data, 0, f->datalength, " ", 1);

			if(pr2->FirstResult->NextResult->datalength == 1 && pr2->FirstResult->NextResult->data[0] == '1' && pr2->FirstResult->NextResult->NextResult->datalength == 3 && strncasecmp(pr2->FirstResult->NextResult->NextResult->data, "UDP", 3)==0)
			//if(pr2->FirstResult->NextResult->NextResult->datalength == 3 && strncasecmp(pr2->FirstResult->NextResult->NextResult->data, "UDP", 3)==0)
			{
				char* candidateData;
				struct parser_result *pr3;
				char *tmp = pr2->FirstResult->NextResult->NextResult->NextResult->NextResult->NextResult->data;
				unsigned short port;

				tmp[pr2->FirstResult->NextResult->NextResult->NextResult->NextResult->NextResult->datalength] = 0;
				port = (unsigned short)atoi(tmp);
				
				pr3 = ILibParseString(pr2->FirstResult->NextResult->NextResult->NextResult->NextResult->data, 0, pr2->FirstResult->NextResult->NextResult->NextResult->NextResult->datalength, ".", 1);					
				if (pr3->NumResults == 4)
				{
					candidateData = pr3->FirstResult->data;
					pr3->FirstResult->data[pr3->FirstResult->datalength] = 0;
					candidateData[0] = (char)atoi(pr3->FirstResult->data);
					pr3->FirstResult->NextResult->data[pr3->FirstResult->NextResult->datalength] = 0;
					candidateData[1] = (char)atoi(pr3->FirstResult->NextResult->data);
					pr3->FirstResult->NextResult->NextResult->data[pr3->FirstResult->NextResult->NextResult->datalength] = 0;
					candidateData[2] = (char)atoi(pr3->FirstResult->NextResult->NextResult->data);
					pr3->FirstResult->NextResult->NextResult->NextResult->data[pr3->FirstResult->NextResult->NextResult->NextResult->datalength] = 0;
					candidateData[3] = (char)atoi(pr3->FirstResult->NextResult->NextResult->NextResult->data);

					((unsigned short*)candidateData)[2] = htons(port);
					candidateData[6] = 0;

					ILibPushStack(&candidates, candidateData);
					++candidatecount;
				}
				ILibDestructParserResults(pr3);
			}

			ILibDestructParserResults(pr2);
        }
		f = f->NextResult;
    }

	if (*username == NULL || *password == NULL || dtlshash == NULL)
    {
		if (pr != NULL) { ILibDestructParserResults(pr); }
		if (dtlshash != NULL) { free(dtlshash); }
		while (ILibPeekStack(&candidates) != NULL)
		{
			ILibPopStack(&candidates);
		}
		return NULL;
    }

	
	blockLen = 6 + usernameLen+1 + passwordLen+1  + dtlsHashLen + 1 + (candidatecount*6)+1;

	retVal = (char*)ILibMemory_SmartAllocateEx(blockLen, sizeof(int));
	ptr = 0;

	((unsigned short*)(retVal+ptr))[0] = htons(1);
	ptr += 2;

	((unsigned int*)(retVal + ptr))[0] = htonl(BlockFlags);
	ptr += 4;

	(retVal)[ptr] = (char)usernameLen;
	ptr += 1;
	memcpy_s((retVal + ptr), blockLen - ptr, *username, usernameLen);
	ptr += usernameLen;

	(retVal)[ptr] = (char)passwordLen;
	ptr += 1;
	memcpy_s((retVal + ptr), blockLen - ptr, *password, passwordLen);
	ptr += passwordLen;

	(retVal)[ptr] = (char)dtlsHashLen;
	ptr += 1;
	memcpy_s((retVal + ptr), blockLen - ptr, dtlshash, dtlsHashLen);
	ptr += dtlsHashLen;

	(retVal)[ptr] = (char)candidatecount;
	ptr += 1;
	while(ILibPeekStack(&candidates)!=NULL)
	{
		memcpy_s((retVal + ptr), blockLen - ptr, ILibPopStack(&candidates), 6);
		ptr += 6;
	}

	ILibDestructParserResults(pr);
	free(lines);
	if(dtlshash!=NULL) {free(dtlshash);}
	return(retVal);
}

int ILibWrapper_BlockToSDPEx(char* block, int blockLen, char** username, char** password, char **sdp, char* serverReflexiveCandidateAddress, unsigned short serverReflexiveCandidatePort)
{
	char sdpTemplate1[] = "v=0\r\no=MeshAgent %u 0 IN IP4 0.0.0.0\r\ns=SIP Call\r\nt=0 0\r\na=ice-ufrag:%s\r\na=ice-pwd:%s\r\na=fingerprint:sha-256 %s\r\nm=application 1 DTLS/SCTP 5000\r\nc=IN IP4 0.0.0.0\r\na=sctpmap:5000 webrtc-datachannel 16\r\na=setup:%s\r\n";
	char sdpTemplateRelay[] = "a=candidate:%d %d UDP %d %s %d typ relay raddr %u.%u.%u.%u %d\r\n";
	char sdpTemplateLocalCand[] = "a=candidate:%d %d UDP %d %u.%u.%u.%u %u typ host\r\n";
	char sdpTemplateSrflxCand[] = "a=candidate:%d %d UDP %d %s %u typ srflx raddr %u.%u.%u.%u rport %u\r\n";

	int sdpLen;
	int c,x,i,ptr;
	char junk[4];
	struct sockaddr_in6 *relayEndPoint = NULL;
	char relayAddressString[128];
	unsigned short relayAddressPort = 0;
	
	// Decode the block

	//This value is commented out to prevent a compiler warning for unused variables, but I left it here to show how to fetch it
	//
    //short blockversion = ILibWrapper_ReadShort(block, 0);
	//

    int blockflags = ILibWrapper_ReadInt(block, 2);	
	int isActive = (blockflags & ILibWebRTC_SDP_Flags_DTLS_SERVER) == ILibWebRTC_SDP_Flags_DTLS_SERVER ? 0:1;

	int usernamelen = (int)block[6];
	int passwordlen = (int)block[7 + usernamelen];
	int dtlshashlen = (int)block[8 + usernamelen + passwordlen];
	int candidatecount = (int)block[9 + usernamelen + passwordlen + dtlshashlen];
	char dtlshash[128];
	int rlen = 10 + usernamelen + passwordlen + dtlshashlen + (candidatecount * 6);
	char* candidates = block + 10 + usernamelen + passwordlen + dtlshashlen;

	if (sdp == ILibBlockToSDP_CREDENTIALS_ONLY)
	{
		// Special case, used only by Mesh Agent. Target is a fixed length array
		memcpy_s(username, usernamelen, block + 7, usernamelen);
		memcpy_s(password, passwordlen, block + 8 + usernamelen, passwordlen);
		return 0;
	}
	else if (sdp == ILibBlockToSDP_CREDENTIALS_ONLY_WITH_ALLOCATE)
	{
		*username = block + 7;
		*password = block + 8 + usernamelen;
		(*username)[usernamelen] = 0;
		(*password)[passwordlen] = 0;
		return 0;
	}

    *username = ILibWrapper_ReadString(block, 7, usernamelen);            
    *password = ILibWrapper_ReadString(block, 8 + usernamelen, passwordlen);

	util_tohex2(block + 9 + usernamelen + passwordlen, dtlshashlen, dtlshash);

	if (rlen < blockLen)
    {
		relayEndPoint = (struct sockaddr_in6*)(block+rlen+1);
		ILibInet_ntop2((struct sockaddr*)relayEndPoint, relayAddressString, 128);
		switch(((struct sockaddr_in*)relayEndPoint)->sin_family)
		{
			case AF_INET:
				relayAddressPort = ntohs(((struct sockaddr_in*)relayEndPoint)->sin_port);
				break;
			case AF_INET6:
				relayAddressPort = ntohs(relayEndPoint->sin6_port);
				break;
			default:
				relayEndPoint = NULL;
		}
    }


	// Build the sdp
	sdpLen = 2 + (int)sizeof(sdpTemplate1) + usernamelen + passwordlen + 7 + (dtlshashlen*3) + 7 + (2 *   (candidatecount * (12 + 21 + (int)sizeof(sdpTemplateLocalCand)))  );
	if(serverReflexiveCandidateAddress!=NULL)
	{
		sdpLen += (2* (12 + 21 + (int)sizeof(sdpTemplateSrflxCand)));
	}
	if(relayEndPoint!=NULL)
	{
		sdpLen += (2* (12 + 21 + (int)sizeof(sdpTemplateRelay)));
	}

	if((*sdp = (char*)malloc(sdpLen))==NULL){ILIBCRITICALEXIT(254);}

	util_random(4, junk);

	x = sprintf_s(*sdp, sdpLen, sdpTemplate1, ((unsigned int*)junk)[0]%1000000, *username, *password, dtlshash, isActive==0?"passive":"actpass");

	for(c = 1; c <= 2; ++c)
	{
		for (i = 0; i < candidatecount; i++)
		{
			ptr = i*6;
			x += sprintf_s(*sdp+x, sdpLen-x, sdpTemplateLocalCand, i, c, 2128609535-i, (unsigned char)candidates[ptr], (unsigned char)candidates[ptr+1], (unsigned char)candidates[ptr+2], (unsigned char)candidates[ptr+3], ntohs(((unsigned short*)(candidates+ptr+4))[0]));
		}
		if(serverReflexiveCandidateAddress!=NULL)
		{
			x += sprintf_s(*sdp+x, sdpLen-x, sdpTemplateSrflxCand, i, c, 2128609535-i, serverReflexiveCandidateAddress, serverReflexiveCandidatePort, (unsigned char)candidates[0], (unsigned char)candidates[1], (unsigned char)candidates[2], (unsigned char)candidates[3], ntohs(((unsigned short*)(candidates+4))[0]));
			++i;
		}
		if(relayEndPoint!=NULL)
		{
			x += sprintf_s(*sdp+x, sdpLen-x, sdpTemplateRelay, i, c, 2128609535-i, relayAddressString, relayAddressPort,  (unsigned char)candidates[0], (unsigned char)candidates[1], (unsigned char)candidates[2], (unsigned char)candidates[3]);
		}
	}
	return(x);
}

void ILibWrapper_BlockToUserNamePWD(char* block, int blockLen, char **username, int *usernameLen, char** password, int *passwordLen)
{
	UNREFERENCED_PARAMETER(blockLen);
	*usernameLen = (int)block[6];
	*passwordLen = (int)block[7 + *usernameLen];

    *username = ILibWrapper_ReadString(block, 7, *usernameLen);            
    *password = ILibWrapper_ReadString(block, 8 + *usernameLen, *passwordLen);
}
int ILibWrapper_BlockToSDP(char* block, int blockLen, int isActive, char** username, char** password, char **sdp)
{
	UNREFERENCED_PARAMETER(isActive);
	return(ILibWrapper_BlockToSDPEx(block, blockLen, username, password, sdp, NULL, 0));
}
void ILibWrapper_WebRTC_InitializeCrypto(ILibWrapper_WebRTC_ConnectionFactoryStruct *factory, struct util_cert *tls)
{
	int l = 32;

	if (factory->ctx == NULL)
	{
		// Init SSL
		util_openssl_init();

		// Init DTLS
#ifdef _MINCORE
		factory->ctx = SSL_CTX_new(DTLSv1_method());
#else
		factory->ctx = SSL_CTX_new(DTLS_method());
		//SSL_CTX_set_ecdh_auto(factory->ctx, 1); // Turn on elliptic curve for dTLS, this is required starting with Firefox 39. (DEPRECATED for OpenSSL/1.1.x)
#endif
		if (ILibWrapper_WebRTC_ConnectionFactoryIndex < 0)
		{
			ILibWrapper_WebRTC_ConnectionFactoryIndex = SSL_CTX_get_ex_new_index(0, "ILibWrapper_WebRTC_ConnectionFactoryStruct index", NULL, NULL, NULL);
		}
		SSL_CTX_set_ex_data(factory->ctx, ILibWrapper_WebRTC_ConnectionFactoryIndex, factory);
	}

	if (tls != ILibWebWrapperWebRTC_ConnectionFactory_INIT_CRYPTO_LATER && factory->cert.x509 == NULL)
	{
		// Init Certs
		if (tls == NULL)
		{
			util_mkCert(NULL, &(factory->cert), 2048, 10000, "localhost", CERTIFICATE_ROOT, NULL);
		}
		else
		{
			memcpy_s(&(factory->cert), sizeof(struct util_cert), tls, sizeof(struct util_cert));
			factory->cert.flags |= ILibCrypto_Cert_Ownership_Other;
		}

		SSL_CTX_use_certificate(factory->ctx, factory->cert.x509);
		SSL_CTX_use_PrivateKey(factory->ctx, factory->cert.pkey);
		X509_digest(factory->cert.x509, EVP_get_digestbyname("sha256"), (unsigned char*)factory->tlsServerCertThumbprint, (unsigned int*)&l);

		if (factory->mStunModule != NULL) { ILibStunClient_SetOptions(factory->mStunModule, factory->ctx, factory->tlsServerCertThumbprint); }
	}
}
void ILibWrapper_WebRTC_UnInitializeCrypto(ILibWrapper_WebRTC_ConnectionFactoryStruct *factory)
{
	// UnInit DTLS
	SSL_CTX_free(factory->ctx);
	factory->ctx = NULL;

	// UnInit Cert
	if (factory->cert.x509 != NULL) { util_freecert(&(factory->cert)); }
	memset(&(factory->cert), 0, sizeof(struct util_cert));
	
	// UnInit SSL
	util_openssl_uninit();
}

void ILibWrapper_WebRTC_ConnectionFactory_OnDestroyEx(ILibSparseArray sender, int index, void *value, void *user)
{
	UNREFERENCED_PARAMETER(user);
	UNREFERENCED_PARAMETER(index);
	UNREFERENCED_PARAMETER(sender);

	ILibWrapper_WebRTC_Connection_DestroyConnection((ILibWrapper_WebRTC_Connection)value);
}
void ILibWrapper_WebRTC_ConnectionFactory_OnDestroy(void *obj)
{
	ILibWrapper_WebRTC_ConnectionFactoryStruct *cfs = (ILibWrapper_WebRTC_ConnectionFactoryStruct*)obj;

	ILibSparseArray_DestroyEx(cfs->Connections, ILibWrapper_WebRTC_ConnectionFactory_OnDestroyEx, cfs);
	ILibWrapper_WebRTC_UnInitializeCrypto((ILibWrapper_WebRTC_ConnectionFactoryStruct*)obj);
}

void ILibWrapper_WebRTC_Connection_DestroyConnectionEx(ILibSparseArray sender, int index, void *value, void *user)
{
	ILibWrapper_WebRTC_DataChannel *dc = (ILibWrapper_WebRTC_DataChannel*)value;
	UNREFERENCED_PARAMETER(user);
	UNREFERENCED_PARAMETER(index);
	UNREFERENCED_PARAMETER(sender);

	if(dc->OnClosed!=NULL) {dc->OnClosed(dc);}
	free(dc->channelName);
	free(dc);
}
void ILibWrapper_WebRTC_Connection_DestroyConnection(ILibWrapper_WebRTC_Connection connection)
{
	ILibWrapper_WebRTC_ConnectionStruct *obj = (ILibWrapper_WebRTC_ConnectionStruct*)connection;

	if(obj==NULL || !ILibMemory_CanaryOK(obj)) {return;}

	
	if(ILibIsChainBeingDestroyed(obj->mFactory->ChainLink.ParentChain)==0)
	{
		ILibSparseArray_Remove(obj->mFactory->Connections, obj->id);

		// Chain is not being destroyed, so we can disconnect DTLS		
		if(obj->dtlsSession!=NULL)
		{
			ILibSCTP_Close(obj->dtlsSession);
		}
		else
		{
			if(obj->offerBlock!=NULL && obj->offerBlockLen>0)
			{
				// Clear the ICE State for the local Offer
				ILibStun_ClearIceState(obj->mFactory->mStunModule, ILibStun_CharToSlot(obj->offerBlock[7]));
			}
			if(obj->remoteOfferBlock!=NULL && ILibMemory_CanaryOK(obj->remoteOfferBlock) && obj->isOfferInitiator)
			{
				// Clear the ICE State for the remote Offer
				ILibStun_ClearIceState(obj->mFactory->mStunModule, ILibStun_CharToSlot(obj->remoteOfferBlock[7]));
			}
		}
	}

	ILibWebRTC_SetUserObject(obj->mFactory->mStunModule, obj->localUsername, NULL);

	if(obj->stunServerList!=NULL && obj->stunServerListLength>0)
	{
		int i;
		for(i=0;i<obj->stunServerListLength;++i)
		{
			free(obj->stunServerList[i]);
		}
		free(obj->stunServerList);
		obj->stunServerList = NULL;
	}

	if(obj->offerBlock!=NULL)
	{
		free(obj->offerBlock);
		obj->offerBlock = NULL;
	}

	if(obj->remoteOfferBlock!=NULL)
	{
		ILibMemory_Free(obj->remoteOfferBlock);
		obj->remoteOfferBlock = NULL;
	}

	ILibSparseArray_DestroyEx(obj->DataChannels, &ILibWrapper_WebRTC_Connection_DestroyConnectionEx, obj);
	if(obj->stunServerFlags!=NULL)
	{
		free(obj->stunServerFlags);
	}

	ILibMemory_Free(obj);
}


void ILibWrapper_WebRTC_Connection_Disconnect(ILibWrapper_WebRTC_Connection connection)
{
	ILibWrapper_WebRTC_ConnectionStruct *obj = (ILibWrapper_WebRTC_ConnectionStruct*)connection;

	if(obj->dtlsSession!=NULL)
	{
		// If DTLS was connected, this will call OnDisconnect later
		ILibSCTP_Close(obj->dtlsSession);
	}
	else
	{
		// No DTLS session, so just clean up the connection object

		if(obj->offerBlock!=NULL && obj->offerBlockLen>0)
		{
			// Clear the ICE State for the local Offer
			ILibStun_ClearIceState(obj->mFactory->mStunModule, ILibStun_CharToSlot(obj->offerBlock[7]));
		}
		if(obj->remoteOfferBlock!=NULL && ILibMemory_CanaryOK(obj->remoteOfferBlock))
		{
			// Clear the ICE State for the remote Offer
			ILibStun_ClearIceState(obj->mFactory->mStunModule, ILibStun_CharToSlot(obj->remoteOfferBlock[7]));
		}

		if(obj->OnConnected!=NULL) {obj->OnConnected(connection, 0);}
		ILibWrapper_WebRTC_Connection_DestroyConnection(connection);
	}
}

void ILibWrapper_WebRTC_OnStunResult(void* StunModule, ILibStun_Results Result, struct sockaddr_in* PublicIP, void *user)
{
	ILibWrapper_WebRTC_ConnectionStruct *connection;
	ILibWrapper_WebRTC_ConnectionFactoryStruct *factory = (ILibWrapper_WebRTC_ConnectionFactoryStruct*)SSL_CTX_get_ex_data(ILibStunClient_GetSSLCTX(StunModule), ILibWrapper_WebRTC_ConnectionFactoryIndex);

	// This is a StunResult for an operation called on the Factory object, not the Connection object
	if (factory == user) 
	{
		if (factory->mStunHandler != NULL) { factory->mStunHandler(factory, Result, PublicIP); }
		return;
	}
	connection = (ILibWrapper_WebRTC_ConnectionStruct*)user;
	connection->stunServerFlags[connection->stunIndex] = 1;

	switch(Result)
	{
		case ILibStun_Results_Unknown:
			// The STUN Server we used is no good, so try with a different server
			connection->stunServerFlags[connection->stunIndex] = 2;
			if(ILibWrapper_WebRTC_PerformStun(connection)!=0 && connection->OnCandidates!=NULL)
			{
				// No more STUN servers to try, so give up
				connection->OnCandidates(connection, NULL);
			}
			break;
		case ILibStun_Results_Public_Interface:
			if(connection->OnCandidates!=NULL)
			{
				connection->OnCandidates(connection, (struct sockaddr_in6*)PublicIP);
			}
			break;
		case ILibStun_Results_No_NAT:
			// Don't need to do anything if there was no NAT, as the local candidates will be good enough
			if(connection->OnCandidates!=NULL)
			{
				connection->OnCandidates(connection, NULL);
			}
			break;
		default:
			break;
	}
}

int ILibWrapper_WebRTC_Connection_IsConnected(ILibWrapper_WebRTC_Connection connection)
{
	ILibWrapper_WebRTC_ConnectionStruct *obj = (ILibWrapper_WebRTC_ConnectionStruct*) connection;

	return(obj!=NULL?obj->isConnected:0);
}
void ILibWrapper_WebRTC_Connection_CloseAllDataChannels(ILibWrapper_WebRTC_Connection connection)
{
	ILibWebRTC_CloseDataChannel_ALL(((ILibWrapper_WebRTC_ConnectionStruct*)connection)->dtlsSession);
}
void ILibWrapper_WebRTC_OnConnectSink(void *StunModule, void* module, int connected)
{
	ILibWrapper_WebRTC_ConnectionStruct *obj = (ILibWrapper_WebRTC_ConnectionStruct*) ILibWebRTC_GetUserObjectFromDtlsSession(module);
	if(obj==NULL) {return;}

	UNREFERENCED_PARAMETER(StunModule);

	obj->dtlsSession = module;
	obj->isConnected = connected;

	obj->isDtlsClient = ILibWebRTC_IsDtlsInitiator(module);

	if(obj->OnConnected!=NULL)
	{
		obj->OnConnected(obj, connected);
	}

	if(connected==0)
	{
		if(ILibIsChainBeingDestroyed(obj->mFactory->ChainLink.ParentChain)==0)
		{
			// Connection was closed, so let's clean up, but only if the chain is still running. It's possible that the previous callback caused the layer above
			// us to initiate a chain shutdown. In that case, skip this method call, becuase otherwise we may end up calling it twice, which will cause a crash.
			ILibWrapper_WebRTC_Connection_DestroyConnection(obj);
		}
	}
}
void ILibWrapper_WebRTC_OnDataSink(void* StunModule, void* module, unsigned short streamId, int pid, char* buffer, int bufferLen, void** user)
{
	ILibWrapper_WebRTC_DataChannel *dc = NULL;
	ILibWrapper_WebRTC_ConnectionStruct *connection = (ILibWrapper_WebRTC_ConnectionStruct*)ILibWebRTC_GetUserObjectFromDtlsSession(module);
	if (connection == NULL) { return; }

	UNREFERENCED_PARAMETER(user);
	UNREFERENCED_PARAMETER(StunModule);

	ILibRemoteLogging_printf(ILibChainGetLogger(((ILibChain_Link*)StunModule)->ParentChain), ILibRemoteLogging_Modules_WebRTC_SCTP, ILibRemoteLogging_Flags_VerbosityLevel_3, "DataChannel[%u] OnData: PID(%d) Bytes(%d)", streamId, pid, bufferLen);

	dc = (ILibWrapper_WebRTC_DataChannel*)ILibSparseArray_Get(connection->DataChannels, (int)streamId);

	if(dc!=NULL)
	{
		if(dc->Header.DataChannelCallbacks.OnRawData!=NULL)	{dc->Header.DataChannelCallbacks.OnRawData(dc, buffer, bufferLen, pid);}
		if(dc->Header.DataChannelCallbacks.OnStringData!=NULL && pid == 51) {dc->Header.DataChannelCallbacks.OnStringData(dc, buffer, bufferLen);}
		if(dc->Header.DataChannelCallbacks.OnBinaryData!=NULL && pid == 53) {dc->Header.DataChannelCallbacks.OnBinaryData(dc, buffer, bufferLen);}
	}
}
void ILibWrapper_WebRTC_OnSendOK_EnumerateSink(ILibSparseArray sender, int index, void *value, void *user)
{
	ILibWrapper_WebRTC_DataChannel *dc = (ILibWrapper_WebRTC_DataChannel*)value;

	if (dc != NULL && dc->Header.transport.SendOkPtr != NULL) { dc->Header.transport.SendOkPtr(dc); }
}
void ILibWrapper_WebRTC_OnSendOKSink(void* StunModule, void* module, void* user)
{
	ILibWrapper_WebRTC_ConnectionStruct *obj = (ILibWrapper_WebRTC_ConnectionStruct*) ILibWebRTC_GetUserObjectFromDtlsSession(module);
	UNREFERENCED_PARAMETER(user);
	UNREFERENCED_PARAMETER(StunModule);

	if(obj != NULL && obj->OnSendOK != NULL)
	{
		obj->OnSendOK(obj);
	}
	if (obj != NULL) { ILibSparseArray_Enumerate(obj->DataChannels, ILibWrapper_WebRTC_OnSendOK_EnumerateSink, obj); }
}
void ILibWrapper_WebRTC_OnDataChannelClosed(void *StunModule, void* WebRTCModule, unsigned short StreamId)
{
	ILibWrapper_WebRTC_ConnectionStruct *obj = (ILibWrapper_WebRTC_ConnectionStruct*) ILibWebRTC_GetUserObjectFromDtlsSession(WebRTCModule);
	ILibWrapper_WebRTC_DataChannel* dataChannel = NULL;
	if (obj == NULL) { return; }
	UNREFERENCED_PARAMETER(StunModule);

	ILibSparseArray_Lock(obj->DataChannels);
	dataChannel = (ILibWrapper_WebRTC_DataChannel*)ILibSparseArray_Remove(obj->DataChannels, (int)StreamId);
	ILibSparseArray_UnLock(obj->DataChannels);
	ILibRemoteLogging_printf(ILibChainGetLogger(obj->mFactory->ChainLink.ParentChain), ILibRemoteLogging_Modules_WebRTC_SCTP, ILibRemoteLogging_Flags_VerbosityLevel_1, "[ILibWrapperWebRTC] Data Channel Signaled to Close: [%s]:%u", dataChannel != NULL ? dataChannel->channelName : "UNKNOWN", StreamId);

	if(dataChannel!=NULL) {ILibWrapper_WebRTC_Connection_DestroyConnectionEx(obj->DataChannels, (int)StreamId, dataChannel, obj);}
}
int ILibWrapper_WebRTC_OnDataChannel(void *StunModule, void* WebRTCModule, unsigned short StreamId, char* ChannelName, int ChannelNameLength)
{
	ILibWrapper_WebRTC_ConnectionStruct *obj = (ILibWrapper_WebRTC_ConnectionStruct*) ILibWebRTC_GetUserObjectFromDtlsSession(WebRTCModule);
	ILibWrapper_WebRTC_DataChannel* dataChannel = NULL;
	if (obj == NULL) { return(0); }
	UNREFERENCED_PARAMETER(StunModule);

	ILibSparseArray_Lock(obj->DataChannels);
	if((dataChannel = (ILibWrapper_WebRTC_DataChannel*)ILibSparseArray_Get(obj->DataChannels, (int)StreamId))==NULL)
	{
		dataChannel = (ILibWrapper_WebRTC_DataChannel*)ILibChain_Link_Allocate(sizeof(ILibWrapper_WebRTC_DataChannel), (int)ILibMemory_ExtraSize(obj));
		dataChannel->Header.transport.ChainLink.MetaData = "ILibWrapper_WebRTC_DataChannel";
		dataChannel->parent = obj;
		dataChannel->streamId = StreamId;
		if((dataChannel->channelName = (char*)malloc(ChannelNameLength+1))==NULL){ILIBCRITICALEXIT(254);}
		ChannelName[ChannelNameLength] = 0;
		dataChannel->channelName[ChannelNameLength] = 0;
		memcpy_s(dataChannel->channelName, ChannelNameLength + 1, ChannelName, ChannelNameLength);
		ILibSparseArray_Add(obj->DataChannels, (int)StreamId, dataChannel);
		ILibWrapper_InitializeDataChannel_Transport(dataChannel);
	}
	ILibSparseArray_UnLock(obj->DataChannels);

	if(obj->OnDataChannel!=NULL)
	{			
		obj->OnDataChannel(obj, dataChannel);
	}

	return 0; // We always return 0, becuase we want the default ACK behavior that is handled underneath
}
void ILibWrapper_WebRTC_OnDataChannelAck(void *StunModule, void* WebRTCModule, unsigned short StreamId)
{
	ILibWrapper_WebRTC_ConnectionStruct *connection = (ILibWrapper_WebRTC_ConnectionStruct*)ILibWebRTC_GetUserObjectFromDtlsSession(WebRTCModule);
	ILibWrapper_WebRTC_DataChannel *dataChannel;

	UNREFERENCED_PARAMETER(StunModule);

	if(connection!=NULL)
	{
		ILibSparseArray_Lock(connection->DataChannels);
		dataChannel = (ILibWrapper_WebRTC_DataChannel*)ILibSparseArray_Get(connection->DataChannels, (int)StreamId);
		ILibSparseArray_UnLock(connection->DataChannels);

		if(dataChannel!=NULL && dataChannel->OnAck!=NULL)  {dataChannel->OnAck(dataChannel);}
	}
}
void ILibWrapper_WebRTC_OnOfferUpdated(void* stunModule, char* iceOffer, int iceOfferLen)
{
	UNREFERENCED_PARAMETER(stunModule);
	UNREFERENCED_PARAMETER(iceOffer);
	UNREFERENCED_PARAMETER(iceOfferLen);
}

int ILibWrapper_WebRTC_ConnectionFactory_Bucketizer(int index)
{
	return(index & (ILibWrapper_WebRTC_ConnectionFactory_ConnectionBucketSize-1));
}
SSL_CTX* ILibWrapper_WebRTC_ConnectionFactory_GetCTX(ILibWrapper_WebRTC_ConnectionFactory factory)
{
	return(((ILibWrapper_WebRTC_ConnectionFactoryStruct*)factory)->ctx);
}
void ILibWrapper_WebRTC_ConnectionFactory_Set_STUNHandler(ILibWrapper_WebRTC_ConnectionFactory factory, ILibWrapper_WebRTC_ConnectionFactory_STUNHandler handler)
{
	((ILibWrapper_WebRTC_ConnectionFactoryStruct*)factory)->mStunHandler = handler;
}

void ILibWrapper_WebRTC_ConnectionFactory_InitCrypto(ILibWrapper_WebRTC_ConnectionFactory factory, struct util_cert *tlsCert)
{
	ILibWrapper_WebRTC_InitializeCrypto((ILibWrapper_WebRTC_ConnectionFactoryStruct*)factory, tlsCert);
}
void ILibWrapper_WebRTC_ConnectionFactory_RemoveFromChainSink(void *chain, void *user)
{
	ILibWrapper_WebRTC_ConnectionFactoryStruct *data = (ILibWrapper_WebRTC_ConnectionFactoryStruct*)user;
	ILibLinkedList links = ILibChain_GetLinks(chain);
	void *node = ILibLinkedList_GetNode_Search(links, NULL, data);
	ILibChain_Link *obj;
	void *turnClient = NULL;
	int finished = 0;

	// Factory Connection object was first object added, TurnClient object was last, so delete everything in between
	turnClient = ILibStun_GetTurnClient(data->mStunModule);

	while (node != NULL && finished == 0)
	{
		obj = (ILibChain_Link*)ILibLinkedList_GetDataFromNode(node);

		if (obj == turnClient) { finished = 1; }
		if (obj->DestroyHandler != NULL) { obj->DestroyHandler(obj); }
		ILibChain_FreeLink(obj);
		node = ILibLinkedList_Remove(node);
	}
}
void ILibWrapper_WebRTC_ConnectionFactory_RemoveFromChain(ILibWrapper_WebRTC_ConnectionFactory factory)
{
	// We are going to force a context switch, so we can make sure that we aren't doing this from a callback of one of the objects we're going to remove
	ILibWrapper_WebRTC_ConnectionFactoryStruct *data = (ILibWrapper_WebRTC_ConnectionFactoryStruct*)factory;
	ILibChain_RunOnMicrostackThreadEx(data->ChainLink.ParentChain, ILibWrapper_WebRTC_ConnectionFactory_RemoveFromChainSink, data);
}
ILibWrapper_WebRTC_ConnectionFactory ILibWrapper_WebRTC_ConnectionFactory_CreateConnectionFactory2(void* chain, unsigned short localPort, struct util_cert *tlsCert)
{
	ILibWrapper_WebRTC_ConnectionFactoryStruct *retVal = (ILibWrapper_WebRTC_ConnectionFactoryStruct*)malloc(sizeof(ILibWrapper_WebRTC_ConnectionFactoryStruct));
	if (retVal == NULL) { ILIBCRITICALEXIT(254); }

	memset(retVal, 0, sizeof(ILibWrapper_WebRTC_ConnectionFactoryStruct));
	retVal->ChainLink.MetaData = "ILibWrapper_WebRTC_ConnectionFactory";
	retVal->ChainLink.DestroyHandler = &ILibWrapper_WebRTC_ConnectionFactory_OnDestroy;
	ILibAddToChain(chain, retVal);

	retVal->mStunModule = ILibStunClient_Start(chain, localPort, &ILibWrapper_WebRTC_OnStunResult);
	if (retVal->mStunModule == NULL) 
	{ 
		ILibLinkedList_Remove_ByData(ILibChain_GetLinks(chain), retVal);
		free(retVal); 
		return(NULL); 
	}

	ILibWrapper_WebRTC_InitializeCrypto(retVal, tlsCert);

	if (tlsCert != ILibWebWrapperWebRTC_ConnectionFactory_INIT_CRYPTO_LATER)
	{
		ILibStunClient_SetOptions(retVal->mStunModule, retVal->ctx, retVal->tlsServerCertThumbprint);
	}
	ILibSCTP_SetCallbacks(retVal->mStunModule, &ILibWrapper_WebRTC_OnConnectSink, &ILibWrapper_WebRTC_OnDataSink, &ILibWrapper_WebRTC_OnSendOKSink);
	ILibWebRTC_SetCallbacks(retVal->mStunModule, &ILibWrapper_WebRTC_OnDataChannel, &ILibWrapper_WebRTC_OnDataChannelClosed, &ILibWrapper_WebRTC_OnDataChannelAck, &ILibWrapper_WebRTC_OnOfferUpdated);

	retVal->Connections = ILibSparseArray_Create(ILibWrapper_WebRTC_ConnectionFactory_ConnectionBucketSize, &ILibWrapper_WebRTC_ConnectionFactory_Bucketizer);
	retVal->ChainLink.ParentChain = chain;

	return retVal;
}

void ILibWrapper_WebRTC_Connection_SetOnSendOK(ILibWrapper_WebRTC_Connection connection, ILibWrapper_WebRTC_Connection_OnSendOK SendOKSink)
{
	((ILibWrapper_WebRTC_ConnectionStruct*)connection)->OnSendOK = SendOKSink;
}

ILibWrapper_WebRTC_ConnectionFactory ILibWrapper_WebRTC_ConnectionFactory_CreateConnectionFactory(void* chain, unsigned short localPort)
{
	return(ILibWrapper_WebRTC_ConnectionFactory_CreateConnectionFactory2(chain, localPort, NULL));
}

int ILibWrapper_WebRTC_Connection_DataChannelBucketizer(int index)
{
	return(index & (ILibWrapper_WebRTC_Connection_DataChannelsBucketSize - 1));
}

int ILibWrapper_WebRTC_Connection_GetID(ILibWrapper_WebRTC_Connection connection)
{
	return(((ILibWrapper_WebRTC_ConnectionStruct*)connection)->id);
}
void ILibWrapper_WebRTC_ConnectionFactory_SendRaw(ILibWrapper_WebRTC_ConnectionFactory factory, struct sockaddr* target, char* data, int datalen, enum ILibAsyncSocket_MemoryOwnership UserFree)
{
	ILibStunClient_SendData(((ILibWrapper_WebRTC_ConnectionFactoryStruct*)factory)->mStunModule, target, data, datalen, UserFree);
}
ILibWrapper_WebRTC_Connection ILibWrapper_WebRTC_ConnectionFactory_CreateConnectionEx2(ILibWrapper_WebRTC_ConnectionFactory factory, char *iceOfferBlock, int iceOfferBlockLen, ILibWrapper_WebRTC_Connection_OnConnect OnConnectHandler, ILibWrapper_WebRTC_Connection_OnDataChannel OnDataChannelHandler, ILibWrapper_WebRTC_Connection_OnSendOK OnConnectionSendOK, int extraMemorySize)
{
	ILibWrapper_WebRTC_ConnectionStruct *retVal = NULL;
	char tmp[1];

	int iceSlot = ILibStun_GetExistingIceOfferIndex(((ILibWrapper_WebRTC_ConnectionFactoryStruct*)factory)->mStunModule, iceOfferBlock, iceOfferBlockLen);
	if (iceSlot < 0)
	{
		// There is no outstanding offer from the remote peer
		retVal = ILibWrapper_WebRTC_ConnectionFactory_CreateConnection2(factory, OnConnectHandler, OnDataChannelHandler, OnConnectionSendOK, extraMemorySize);
		retVal->remoteOfferBlock = (char*)ILibMemory_SmartAllocateEx(iceOfferBlockLen, sizeof(int));
		((int*)ILibMemory_Extra(retVal->remoteOfferBlock))[0] = 0;

		memcpy_s(retVal->remoteOfferBlock, iceOfferBlockLen, iceOfferBlock, iceOfferBlockLen);
		retVal->offerBlockLen = ILibStun_SetIceOffer(((ILibWrapper_WebRTC_ConnectionFactoryStruct*)factory)->mStunModule, iceOfferBlock, iceOfferBlockLen, &(retVal->offerBlock));
		if (retVal->offerBlock != NULL)
		{
			ILibWrapper_BlockToSDP(retVal->remoteOfferBlock, iceOfferBlockLen, 0, (char**)&(retVal->remoteUsername), (char**)&(retVal->remotePassword), ILibBlockToSDP_CREDENTIALS_ONLY_WITH_ALLOCATE);
			ILibWrapper_BlockToSDP(retVal->offerBlock, retVal->offerBlockLen, 0, (char**)&(retVal->localUsername), (char**)&(retVal->localPassword), ILibBlockToSDP_CREDENTIALS_ONLY);
			ILibWebRTC_SetUserObject(((ILibWrapper_WebRTC_ConnectionFactoryStruct*)factory)->mStunModule, retVal->localUsername, retVal);
		}
	}
	else
	{
		// Outstanding offer from remote peer was found
		tmp[0] = (char)ILibStun_SlotToChar(iceSlot);
		retVal = ILibWebRTC_GetUserObject(((ILibWrapper_WebRTC_ConnectionFactoryStruct*)factory)->mStunModule, tmp);
		if (retVal != NULL)
		{
			if (retVal->remoteOfferBlock != NULL) { ILibMemory_Free(retVal->remoteOfferBlock); }
			if (retVal->offerBlock != NULL) { free(retVal->offerBlock); }

			retVal->remoteOfferBlock = (char*)ILibMemory_SmartAllocateEx(iceOfferBlockLen, sizeof(int));
			((int*)ILibMemory_Extra(retVal->remoteOfferBlock))[0] = 1;
			memcpy_s(retVal->remoteOfferBlock, iceOfferBlockLen, iceOfferBlock, iceOfferBlockLen);
			retVal->offerBlockLen = ILibStun_SetIceOffer(((ILibWrapper_WebRTC_ConnectionFactoryStruct*)factory)->mStunModule, iceOfferBlock, iceOfferBlockLen, &(retVal->offerBlock));
		}
	}

	return retVal;
}
ILibWrapper_WebRTC_Connection ILibWrapper_WebRTC_ConnectionFactory_CreateConnectionEx(ILibWrapper_WebRTC_ConnectionFactory factory, char *iceOfferBlock, int iceOfferBlockLen, ILibWrapper_WebRTC_Connection_OnConnect OnConnectHandler, ILibWrapper_WebRTC_Connection_OnDataChannel OnDataChannelHandler, ILibWrapper_WebRTC_Connection_OnSendOK OnConnectionSendOK)
{
	return(ILibWrapper_WebRTC_ConnectionFactory_CreateConnectionEx2(factory, iceOfferBlock, iceOfferBlockLen, OnConnectHandler, OnDataChannelHandler, OnConnectionSendOK, 0));
}

ILibWrapper_WebRTC_Connection ILibWrapper_WebRTC_ConnectionFactory_CreateConnection2(ILibWrapper_WebRTC_ConnectionFactory factory, ILibWrapper_WebRTC_Connection_OnConnect OnConnectHandler, ILibWrapper_WebRTC_Connection_OnDataChannel OnDataChannelHandler, ILibWrapper_WebRTC_Connection_OnSendOK OnConnectionSendOK, int extraMemorySize)
{
	ILibWrapper_WebRTC_ConnectionStruct *retVal = (ILibWrapper_WebRTC_ConnectionStruct*)ILibMemory_SmartAllocateEx(sizeof(ILibWrapper_WebRTC_ConnectionStruct), extraMemorySize);

	retVal->OnConnected = OnConnectHandler;
	retVal->OnDataChannel = OnDataChannelHandler;
	retVal->OnSendOK = OnConnectionSendOK;

	retVal->mFactory = (ILibWrapper_WebRTC_ConnectionFactoryStruct*)factory;

	ILibSparseArray_Lock(retVal->mFactory->Connections);
	retVal->id = (retVal->mFactory->NextConnectionID)++;
	ILibSparseArray_Add(retVal->mFactory->Connections, retVal->id, retVal);
	ILibSparseArray_UnLock(retVal->mFactory->Connections);

	retVal->DataChannels = ILibSparseArray_Create(ILibWrapper_WebRTC_Connection_DataChannelsBucketSize, &ILibWrapper_WebRTC_Connection_DataChannelBucketizer);

	return retVal;
}
ILibWrapper_WebRTC_Connection ILibWrapper_WebRTC_ConnectionFactory_CreateConnection(ILibWrapper_WebRTC_ConnectionFactory factory, ILibWrapper_WebRTC_Connection_OnConnect OnConnectHandler, ILibWrapper_WebRTC_Connection_OnDataChannel OnDataChannelHandler, ILibWrapper_WebRTC_Connection_OnSendOK OnConnectionSendOK)
{
	return(ILibWrapper_WebRTC_ConnectionFactory_CreateConnection2(factory, OnConnectHandler, OnDataChannelHandler, OnConnectionSendOK, 0));
}
void ILibWrapper_WebRTC_Connection_SetStunServers(ILibWrapper_WebRTC_Connection connection, char** serverList, int serverLength)
{
	ILibWrapper_WebRTC_ConnectionStruct *obj = (ILibWrapper_WebRTC_ConnectionStruct*) connection;
	int i;

	if(serverLength<=0){return;}

	if((obj->stunServerFlags = (char*)malloc(serverLength))==NULL){ILIBCRITICALEXIT(254);}
	memset(obj->stunServerFlags, 0, serverLength);

	if ((obj->stunServerList = (char**)malloc(serverLength * sizeof(char*))) == NULL) { ILIBCRITICALEXIT(254); }
	obj->stunServerListLength = serverLength;
	for(i=0;i<serverLength;++i)
	{
		int sz = (int)strnlen_s(serverList[i], ILibWrapper_WebRTC_MaxStunServerPathLength);
		obj->stunServerList[i] = (char*)malloc(sz+1);
		(obj->stunServerList[i])[sz] = 0;
		memcpy_s(obj->stunServerList[i], sz + 1, serverList[i], sz);
	}
}

void ILibWrapper_WebRTC_ConnectionFactory_PerformSTUN(ILibWrapper_WebRTC_ConnectionFactory factory, struct sockaddr_in* StunServer)
{
	ILibStunClient_PerformStun(((ILibWrapper_WebRTC_ConnectionFactoryStruct*)factory)->mStunModule, StunServer, factory);
}
int ILibWrapper_WebRTC_PerformStunEx(ILibWrapper_WebRTC_ConnectionStruct *connection, int MappingDetection)
{
	int i,delimiter;
	struct sockaddr_in6 stunServer;
	unsigned short port;
	char temp[255];
	char *host;

	if(connection->stunServerListLength > 0)
	{
		for(i=0;i<connection->stunServerListLength;++i)
		{
			if(connection->stunServerFlags[i] > 1) {continue;} // 0 = Unknown, 1 = Success, 2 = ERROR

			delimiter = ILibString_IndexOf(connection->stunServerList[i], (int)strnlen_s(connection->stunServerList[i], ILibWrapper_WebRTC_MaxStunServerPathLength), ":", 1);
			if(delimiter>0) 
			{
				memcpy_s(temp, sizeof(temp), connection->stunServerList[i], delimiter);
				temp[delimiter] = 0;
				host = temp;
				port = (unsigned short)atoi(connection->stunServerList[i] + delimiter + 1);
			}
			else
			{
				host = connection->stunServerList[i];
				port = 3478;
			}
			if(ILibResolve(host, "http", &stunServer)==0)
			{
				switch(stunServer.sin6_family)
				{
					case AF_INET:
						((struct sockaddr_in*)&stunServer)->sin_port = htons(port);
						break;
					case AF_INET6:
						stunServer.sin6_port = htons(port);
						break;
				}
				connection->stunIndex = i;
				if(MappingDetection == 0)
				{
					ILibStunClient_PerformStun(connection->mFactory->mStunModule, (struct sockaddr_in*)&stunServer, connection);
				}
				else
				{
					ILibStunClient_PerformNATBehaviorDiscovery(connection->mFactory->mStunModule, (struct sockaddr_in*)&stunServer, connection);
				}
				break;
			}
			else
			{
				connection->stunServerFlags[i] = 2; // Could not resolve, so skip, and use another server
			}
		}
		return(i<connection->stunServerListLength?0:1);
	}
	else
	{
		return 1;
	}
}
int ILibWrapper_WebRTC_PerformStun(ILibWrapper_WebRTC_ConnectionStruct *connection)
{
	return(ILibWrapper_WebRTC_PerformStunEx(connection, 0));
}


int ILibWrapper_WebRTC_Connection_DoesPeerSupportUnreliableMode(ILibWrapper_WebRTC_Connection connection)
{
	ILibWrapper_WebRTC_ConnectionStruct *obj = (ILibWrapper_WebRTC_ConnectionStruct*) connection;
	return(ILibSCTP_DoesPeerSupportFeature(obj->dtlsSession, 0xC000));
}

char* ILibWrapper_WebRTC_Connection_SetOffer(ILibWrapper_WebRTC_Connection connection, char* offer, int offerLen, ILibWrapper_WebRTC_OnConnectionCandidate onCandidates)
{
	ILibWrapper_WebRTC_ConnectionStruct *obj = (ILibWrapper_WebRTC_ConnectionStruct*) connection;
	char* sdp;
	int isActive;
	char *un,*up;

	if(obj->remoteOfferBlock!=NULL) {ILibMemory_Free(obj->remoteOfferBlock);}
	if(obj->offerBlock!=NULL) {free(obj->offerBlock);}
	
	char *remoteOfferBlock = ILibWrapper_SdpToBlock(offer, offerLen, &isActive, &(obj->remoteUsername), &(obj->remotePassword));
	if (remoteOfferBlock == NULL) { return(NULL); }

	if (obj->remoteOfferBlock != NULL) { ILibMemory_Free(obj->remoteOfferBlock); ((int*)ILibMemory_Extra(remoteOfferBlock))[0] = 1; }
	obj->remoteOfferBlock = remoteOfferBlock;

	offer[offerLen] = 0;
	ILibRemoteLogging_printf(ILibChainGetLogger(obj->mFactory->ChainLink.ParentChain), ILibRemoteLogging_Modules_WebRTC_STUN_ICE, ILibRemoteLogging_Flags_VerbosityLevel_1, "[ILibWrapperWebRTC] Set ICE/Offer: <br/>%s", offer);

	obj->offerBlockLen = ILibStun_SetIceOffer2(obj->mFactory->mStunModule, obj->remoteOfferBlock, (int)ILibMemory_Size(obj->remoteOfferBlock), obj->offerBlock != NULL ? obj->localUsername : NULL, obj->offerBlock != NULL ? 8 : 0, obj->offerBlock != NULL ? obj->localPassword : NULL, obj->offerBlock != NULL ? 32 : 0, &obj->offerBlock);
	if (obj->offerBlockLen == 0) { return(NULL); }

	ILibWrapper_BlockToSDP(obj->offerBlock, obj->offerBlockLen, obj->isOfferInitiator, &un, &up, &sdp);

	ILibRemoteLogging_printf(ILibChainGetLogger(obj->mFactory->ChainLink.ParentChain), ILibRemoteLogging_Modules_WebRTC_STUN_ICE, ILibRemoteLogging_Flags_VerbosityLevel_1, "[ILibWrapperWebRTC] Return ICE/Response: <br/>%s", sdp);

	ILibWebRTC_SetUserObject(obj->mFactory->mStunModule, un, obj);
	memcpy_s(obj->localUsername, sizeof(obj->localUsername), un, strnlen_s(un, sizeof(obj->localUsername)));
	memcpy_s(obj->localPassword, sizeof(obj->localPassword), up, strnlen_s(up, sizeof(obj->localPassword)));

	free(un);
	free(up);

	if(onCandidates != NULL)
	{
		obj->OnCandidates = onCandidates;
		if(ILibWrapper_WebRTC_PerformStun(obj)!=0)
		{
			// No STUN Servers to try
			onCandidates(connection, NULL);
		}
	}

	return(sdp);
}

void ILibWrapper_WebRTC_Connection_SetRemoteParameters(ILibWrapper_WebRTC_Connection connection, char *username, int usernameLen, char *password, int passwordLen, char *certHash, int certHashLen)
{
	UNREFERENCED_PARAMETER(connection);
	UNREFERENCED_PARAMETER(username);
	UNREFERENCED_PARAMETER(usernameLen);
	UNREFERENCED_PARAMETER(password);
	UNREFERENCED_PARAMETER(passwordLen);
	UNREFERENCED_PARAMETER(certHash);
	UNREFERENCED_PARAMETER(certHashLen);
}

int ILibWrapper_WebRTC_Connection_GetLocalOfferBlock(ILibWrapper_WebRTC_Connection connection, char **localOfferBlock)
{
	*localOfferBlock = ((ILibWrapper_WebRTC_ConnectionStruct*)connection)->offerBlock;
	return(((ILibWrapper_WebRTC_ConnectionStruct*)connection)->offerBlockLen);
}
void ILibWrapper_WebRTC_Connection_CreateLocalParameters_ICE(ILibWrapper_WebRTC_Connection connection)
{
	ILibWrapper_WebRTC_Connection_GenerateOffer(connection, NULL);
}

void ILibWrapper_WebRTC_Connection_GetLocalParameters_ICE(ILibWrapper_WebRTC_Connection connection, char **username, int *usernameLen, char **password, int *passwordLen)
{
	ILibWrapper_WebRTC_ConnectionStruct *obj = (ILibWrapper_WebRTC_ConnectionStruct*) connection;
	ILibWrapper_BlockToUserNamePWD(obj->offerBlock, obj->offerBlockLen, username, usernameLen, password, passwordLen);
}
void ILibWrapper_WebRTC_Connection_GetLocalParameters_DTLS(ILibWrapper_WebRTC_Connection connection, char **hash, int *hashLen)
{
	ILibWrapper_WebRTC_ConnectionStruct *obj = (ILibWrapper_WebRTC_ConnectionStruct*) connection;
	*hashLen = (int)sizeof(obj->mFactory->tlsServerCertThumbprint);
	*hashLen = *hashLen * 2;
	*hashLen = *hashLen + (*hashLen / 2) + 1;

	*hash = (char*)malloc(*hashLen);
	if (*hash == NULL) { ILIBCRITICALEXIT(254); }

	util_tohex2(obj->mFactory->tlsServerCertThumbprint, (int)sizeof(obj->mFactory->tlsServerCertThumbprint), *hash);
	*hashLen = (int)strnlen_s(*hash, *hashLen);
}

ILibTransport* ILibWrapper_WebRTC_Connection_GetRawTransport(ILibWrapper_WebRTC_Connection connection)
{
	return(((ILibWrapper_WebRTC_ConnectionStruct*)connection)->dtlsSession);
}
void* ILibWrapper_WebRTC_Connection_GetChain(ILibWrapper_WebRTC_Connection connection)
{
	return(((ILibWrapper_WebRTC_ConnectionStruct*)connection)->mFactory->ChainLink.ParentChain);
}

char* ILibWrapper_WebRTC_Connection_GenerateOffer(ILibWrapper_WebRTC_Connection connection, ILibWrapper_WebRTC_OnConnectionCandidate onCandidates)
{
	ILibWrapper_WebRTC_ConnectionStruct *obj = (ILibWrapper_WebRTC_ConnectionStruct*) connection;

	char* offer;
	int offerLen = ILibStun_GenerateIceOffer(obj->mFactory->mStunModule, &offer, obj->localUsername, obj->localPassword);
	char *sdp;
	char* username,*password;

	obj->isOfferInitiator = 1;
	ILibWrapper_BlockToSDP(offer, offerLen, obj->isOfferInitiator, &username, &password, &sdp);

	free(username);
	free(password);

	obj->offerBlock = offer;
	obj->offerBlockLen = offerLen;
	obj->OnCandidates = onCandidates;

	if(onCandidates != NULL)
	{
		if(ILibWrapper_WebRTC_PerformStun(obj)!=0)
		{
			// No STUN Servers to try
			onCandidates(connection, NULL);
		}
	}

	return(sdp);
}

char* ILibWrapper_WebRTC_Connection_AddServerReflexiveCandidateToLocalSDP(ILibWrapper_WebRTC_Connection connection, struct sockaddr_in6* candidate)
{
	ILibWrapper_WebRTC_ConnectionStruct *obj = (ILibWrapper_WebRTC_ConnectionStruct*) connection;
	char *sdp;
	char *username;
	char *password;
	char address[255];
	
	if(candidate!=NULL)
	{
		ILibInet_ntop2((struct sockaddr*)candidate, address, 255);
		ILibWrapper_BlockToSDPEx(obj->offerBlock, obj->offerBlockLen, &username, &password, &sdp, address, ntohs(candidate->sin6_port));
	}
	else
	{
		ILibWrapper_BlockToSDPEx(obj->offerBlock, obj->offerBlockLen, &username, &password, &sdp, NULL, 0);
	}
	

	free(username);
	free(password);

	return(sdp);
}

void ILibWrapper_WebRTC_Connection_Pause(ILibWrapper_WebRTC_Connection connection)
{
	ILibWrapper_WebRTC_ConnectionStruct *cs = (ILibWrapper_WebRTC_ConnectionStruct*)connection;
	ILibSCTP_Pause(cs->dtlsSession);
}
void ILibWrapper_WebRTC_Connection_Resume(ILibWrapper_WebRTC_Connection connection)
{
	ILibWrapper_WebRTC_ConnectionStruct *cs = (ILibWrapper_WebRTC_ConnectionStruct*)connection;
	ILibSCTP_Resume(cs->dtlsSession);
}

ILibTransport_DoneState ILibWrapper_WebRTC_DataChannel_SendEx(ILibWrapper_WebRTC_DataChannel* dataChannel, char* data, int dataLen, int dataType)
{
	ILibWrapper_WebRTC_ConnectionStruct *connection = (ILibWrapper_WebRTC_ConnectionStruct*)dataChannel->parent;

	return(ILibSCTP_SendEx(connection->dtlsSession, dataChannel->streamId, data, dataLen, dataType));
}
ILibTransport_DoneState ILibWrapper_WebRTC_DataChannel_Send(ILibWrapper_WebRTC_DataChannel* dataChannel, char* data, int dataLen)
{
	return(ILibWrapper_WebRTC_DataChannel_SendEx(dataChannel, data, dataLen, 53));
}
ILibTransport_DoneState ILibWrapper_WebRTC_DataChannel_SendString(ILibWrapper_WebRTC_DataChannel* dataChannel, char* data, int dataLen)
{
	return(ILibWrapper_WebRTC_DataChannel_SendEx(dataChannel, data, dataLen, 51));
}

ILibWrapper_WebRTC_DataChannel* ILibWrapper_WebRTC_DataChannel_Create(ILibWrapper_WebRTC_Connection connection, char* channelName, int channelNameLen, ILibWrapper_WebRTC_DataChannel_OnDataChannelAck OnAckHandler)
{
	return(ILibWrapper_WebRTC_DataChannel_CreateEx(connection, channelName, channelNameLen, ILibWrapper_GetNextStreamId((ILibWrapper_WebRTC_ConnectionStruct*)connection), OnAckHandler));
}
void ILibWrapper_WebRTC_DataChannel_Close(ILibWrapper_WebRTC_DataChannel* dataChannel)
{
	ILibWrapper_WebRTC_ConnectionStruct *connection = (ILibWrapper_WebRTC_ConnectionStruct*)dataChannel->parent;
	ILibWebRTC_CloseDataChannel(connection->dtlsSession, dataChannel->streamId);
}
ILibWrapper_WebRTC_DataChannel* ILibWrapper_WebRTC_DataChannel_CreateEx(ILibWrapper_WebRTC_Connection connection, char* channelName, int channelNameLen, unsigned short streamId, ILibWrapper_WebRTC_DataChannel_OnDataChannelAck OnAckHandler)
{
	ILibWrapper_WebRTC_DataChannel *retVal = (ILibWrapper_WebRTC_DataChannel*)ILibChain_Link_Allocate(sizeof(ILibWrapper_WebRTC_DataChannel), (int)ILibMemory_ExtraSize(connection));
	retVal->Header.transport.ChainLink.MetaData = "ILibWrapper_WebRTC_DataChannel";
	retVal->parent = connection;
	if ((retVal->channelName = (char*)malloc(channelNameLen + 1)) == NULL) { ILIBCRITICALEXIT(254); }
	memcpy_s(retVal->channelName, channelNameLen + 1, channelName, channelNameLen);
	retVal->channelName[channelNameLen] = 0;

	retVal->streamId = streamId;
	retVal->OnAck = OnAckHandler;

	ILibSparseArray_Lock(((ILibWrapper_WebRTC_ConnectionStruct*)connection)->DataChannels);
	ILibSparseArray_Add(((ILibWrapper_WebRTC_ConnectionStruct*)connection)->DataChannels, retVal->streamId, retVal);
	ILibSparseArray_UnLock(((ILibWrapper_WebRTC_ConnectionStruct*)connection)->DataChannels);

	ILibWrapper_InitializeDataChannel_Transport(retVal);

	ILibWebRTC_OpenDataChannel(((ILibWrapper_WebRTC_ConnectionStruct*)connection)->dtlsSession, streamId, channelName, channelNameLen);
	return retVal;
}
void ILibWrapper_WebRTC_ConnectionFactory_SetTurnServer(ILibWrapper_WebRTC_ConnectionFactory factory, struct sockaddr_in6* turnServer, char* username, int usernameLength, char* password, int passwordLength, ILibWebRTC_TURN_ConnectFlags turnSetting)
{
	ILibWrapper_WebRTC_ConnectionFactoryStruct *cf = (ILibWrapper_WebRTC_ConnectionFactoryStruct*)factory;
	cf->mTurnSetting = turnSetting;

	if (turnServer != NULL) {memcpy_s(&(cf->mTurnServer), sizeof(struct sockaddr_in6), turnServer, INET_SOCKADDR_LENGTH(turnServer->sin6_family));}

	if (turnSetting == ILibWebRTC_TURN_DISABLED)
	{
		ILibWebRTC_SetTurnServer(cf->mStunModule, NULL, NULL, 0, NULL, 0, ILibWebRTC_TURN_DISABLED);
	}
	else
	{
		ILibWebRTC_SetTurnServer(cf->mStunModule, turnServer, username, usernameLength, password, passwordLength, turnSetting);
	}
}
void ILibWrapper_WebRTC_Connection_SetUserData(ILibWrapper_WebRTC_Connection connection, void *user1, void *user2, void *user3)
{
	ILibWrapper_WebRTC_ConnectionStruct *obj = (ILibWrapper_WebRTC_ConnectionStruct*)connection;
	obj->User1 = user1;
	obj->User2 = user2;
	obj->User3 = user3;
}
void ILibWrapper_WebRTC_Connection_GetUserData(ILibWrapper_WebRTC_Connection connection, void **user1, void **user2, void **user3)
{
	ILibWrapper_WebRTC_ConnectionStruct *obj = (ILibWrapper_WebRTC_ConnectionStruct*)connection;
	if(user1!=NULL) {*user1 = obj->User1;}
	if(user2!=NULL) {*user2 = obj->User2;}
	if(user3!=NULL) {*user3 = obj->User3;}
}
void ILibWrapper_WebRTC_DataChannel_OnDebug(void *dtlsSession, char* debugField, int debugValue)
{
	ILibWrapper_WebRTC_ConnectionStruct *cs = (ILibWrapper_WebRTC_ConnectionStruct*)ILibWebRTC_GetUserObjectFromDtlsSession(dtlsSession);
	if(cs!=NULL && cs->OnDebugEvent!=NULL) {cs->OnDebugEvent(cs, debugField, debugValue);}
}
#ifdef _WEBRTCDEBUG
int ILibWrapper_WebRTC_Connection_Debug_Set(ILibWrapper_WebRTC_Connection connection, char* debugFieldName, ILibWrapper_WebRTC_Connection_Debug_OnEvent eventHandler)
{
	ILibWrapper_WebRTC_ConnectionStruct *cs = (ILibWrapper_WebRTC_ConnectionStruct*)connection;
	cs->OnDebugEvent = eventHandler;
	return(ILibSCTP_Debug_SetDebugCallback(cs->dtlsSession, debugFieldName, &ILibWrapper_WebRTC_DataChannel_OnDebug));
}
void ILibWrapper_WebRTC_ConnectionFactory_SetSimulatedLossPercentage(ILibWrapper_WebRTC_ConnectionFactory factory, int lossPercentage)
{
	ILibSCTP_SetSimulatedInboundLossPercentage(((ILibWrapper_WebRTC_ConnectionFactoryStruct*)factory)->mStunModule, lossPercentage);
}
#endif
#endif
