/*********************************************************************
* Copyright (c) Intel Corporation 2011 - 2020
* SPDX-License-Identifier: Apache-2.0
**********************************************************************/

#ifndef _MINCORE

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "LMEConnection.h"
#include "LMS_if.h"
#include "../microstack/ILibParsers.h"

#ifdef _POSIX
#define UNREFERENCED_PARAMETER(P) (P)
#endif

#define MEI_IO_TIMEOUT 1000

#ifndef FALSE
#define FALSE 0
#endif

#ifndef TRUE
#define TRUE 1
#endif

const unsigned int LME_RX_WINDOW_SIZE = 4096;
int LME_sendMessage(struct LMEConnection* module, unsigned char *buffer, int len);
void LME_doRX(struct LMEConnection* module, unsigned char *rxBuffer, unsigned int bytesRead);
void LME_apfGlobalRequest(struct LMEConnection* module, unsigned char *rxBuffer, unsigned int bytesRead, int *status);
void LME_Deinit(struct LMEConnection* module);

bool LME_IsInitialized(struct LMEConnection* module)
{
	// Lock il(_initLock);
	bool ret = (bool)(module->initState == INIT_STATE_CONNECTED);
	return ret;
}

#ifdef WIN32
DWORD WINAPI LME_Thread(void* obj)
#else
void LME_Thread(void* obj)
#endif
{
#ifdef WIN32
	HECI_VERSION version;
#endif
	int len = 0;
	int bufsize = 0;
	unsigned char* data = NULL;
	struct LMEConnection* module = (struct LMEConnection*)obj;

	//printf("LMS THREAD START\r\n");

	do {
		bufsize = heci_GetBufferSize(&(module->mei));
		if ((data = (unsigned char*)malloc(bufsize)) == NULL) ILIBCRITICALEXIT(254);

		do {
			len = heci_ReceiveMessage(&(module->mei), data, bufsize, 0xFFFFFFFF);
			//printf("LMS THREAD READ LEN=%d\r\n", len);
			if (len > 0 && data != NULL) LME_doRX(module, data, len);
		} while (len >= 0);
		
		module->cb(module, module->cbParam, NULL, 0);
		module->initState = INIT_STATE_DISCONNECTED;
		free(data);
		
		// printf("LMS TRYING RECONNECT\r\n");

		while (module->exit == 0 && module->initState == INIT_STATE_DISCONNECTED)
		{
			// Setup the MEI interface with the LME GUID
			#ifdef WIN32
				if (heci_Init(&(module->mei), LMS_CLIENT) == TRUE && heci_GetHeciVersion(&(module->mei), &version) == TRUE && version.major >= LMS_PROTOCOL_VERSION) { module->initState = INIT_STATE_CONNECTED; }
			#else
				if (heci_Init(&(module->mei), 1) == TRUE && heci_GetProtocolVersion(&(module->mei)) >= LMS_PROTOCOL_VERSION) { module->initState = INIT_STATE_CONNECTED; }
			#endif

			if (module->exit == 0 && module->initState == INIT_STATE_DISCONNECTED)
			{
				// printf("LMS THREAD SLEEP\r\n");
#ifdef WIN32
				Sleep(2000);
#else
				sleep(2);
#endif
			}
			//if (module->initState == INIT_STATE_CONNECTED) printf("LMS THREAD RECONNECT\r\n");
		}
	}
	while (module->exit == 0);

	// printf("LMS THREAD QUIT\r\n");
	module->exit = 2;

#ifdef WIN32
	return 0;
#endif
}

// Setup the LME connection
bool LME_Init(struct LMEConnection* module, MEICallback cb, void *param)
{
#ifdef WIN32
	HECI_VERSION version;
#endif

	// Setup the state object
	memset(module, 0, sizeof(struct LMEConnection));
	module->initState = INIT_STATE_DISCONNECTED;
	module->cb = cb;
	module->cbParam = param;

	// Setup the MEI interface with the LME GUID
#ifdef WIN32
	if (heci_Init(&(module->mei), LMS_CLIENT) == FALSE || heci_GetHeciVersion(&(module->mei), &version) == FALSE || version.major < LMS_PROTOCOL_VERSION) { module->initState = INIT_STATE_DISCONNECTED; return FALSE; }
#else
	if (heci_Init(&(module->mei), 1) == FALSE || heci_GetProtocolVersion(&(module->mei)) < LMS_PROTOCOL_VERSION) { module->initState = INIT_STATE_DISCONNECTED; return FALSE; }
#endif

	module->initState = INIT_STATE_CONNECTED;
	if ((module->txBuffer = (unsigned char*)malloc(LME_GetMeiBufferSize(module))) == NULL) ILIBCRITICALEXIT(254);

	// Create the thread that will read the MEI/LME stream
	ILibSpawnNormalThread((voidfp)(&LME_Thread), module);

	return TRUE;
}

// Disconnect the LME connection
void LME_Deinit(struct LMEConnection* module)
{
	//printf("LME_Deinit()\r\n");
	if (module == NULL) return;
	if (module->initState == INIT_STATE_CONNECTED) { heci_Deinit(&(module->mei)); }
	module->initState = INIT_STATE_DISCONNECTED;
}

// Exit LME
void LME_Exit(struct LMEConnection* module)
{
	int l = 0;

	//printf("LME_Exit()\r\n");
	if (module == NULL) return;
	if (module->exit == 0) module->exit = 1;
	LME_Deinit(module);
#ifdef WIN32
	while (module->exit != 2 && l < 40) { Sleep(100); l++; }
#else
	/*
	while (module->exit != 2 && l < 4)
	{
		printf("LME_Holding %d\r\n", l);
		Sleep(1);
		l++;
	}
	*/
#endif
	if (module->txBuffer != NULL && l < 40) { free(module->txBuffer); module->txBuffer = NULL; }
}

// Send the APF disconnect message to the MEI
bool LME_Disconnect(struct LMEConnection* module, APF_DISCONNECT_REASON_CODE reasonCode)
{
	unsigned char buf[sizeof(APF_DISCONNECT_MESSAGE)];
	APF_DISCONNECT_MESSAGE *disconnectMessage = (APF_DISCONNECT_MESSAGE *)buf;
	memset(disconnectMessage, 0, sizeof(buf));
	disconnectMessage->MessageType = APF_DISCONNECT;
	disconnectMessage->ReasonCode = htonl(reasonCode);
	return (LME_sendMessage(module, buf, sizeof(buf)) == sizeof(buf));
}

// Send the AFP service accept message to the MEI
bool LME_ServiceAccept(struct LMEConnection* module, char* serviceName)
{
	int len;
	int res;
	int servicenamelen = (int)strnlen_s(serviceName, _MAX_PATH);
	unsigned char *buf;
	unsigned char *pCurrent;

	if (!LME_IsInitialized(module)) return FALSE;
	if ((buf = (unsigned char*)malloc(sizeof(APF_SERVICE_ACCEPT_MESSAGE) + servicenamelen)) == NULL) ILIBCRITICALEXIT(254);
	pCurrent = buf;

	*pCurrent = APF_SERVICE_ACCEPT;
	++pCurrent;
	*((unsigned int *)pCurrent) = htonl(servicenamelen);
	pCurrent += 4;

	memcpy_s(pCurrent, sizeof(APF_SERVICE_ACCEPT_MESSAGE) + servicenamelen, serviceName, servicenamelen);
	pCurrent += servicenamelen;

	len = (int)(pCurrent - buf);
	res = LME_sendMessage(module, buf, len);
	free(buf);

	return (res == len);
}

bool LME_ProtocolVersion(struct LMEConnection* module, unsigned int majorversion, unsigned int minorversion, unsigned int triggerreason)
{
	APF_PROTOCOL_VERSION_MESSAGE protVersion;
	memset(&protVersion, 0, sizeof(protVersion));
	protVersion.MessageType = APF_PROTOCOLVERSION;
	protVersion.MajorVersion = htonl(majorversion);
	protVersion.MinorVersion = htonl(minorversion);
	protVersion.TriggerReason = htonl(triggerreason);
	return (LME_sendMessage(module, (unsigned char *)&protVersion, sizeof(protVersion)) == sizeof(protVersion));
}

bool LME_TcpForwardReplySuccess(struct LMEConnection* module, unsigned int port)
{
	APF_TCP_FORWARD_REPLY_MESSAGE message;
	memset(&message, 0, sizeof(message));
	message.MessageType = APF_REQUEST_SUCCESS;
	message.PortBound = htonl(port);
	return (LME_sendMessage(module, (unsigned char *)&message, sizeof(message)) == sizeof(message));
}

bool LME_SendShortMessage(struct LMEConnection* module, unsigned char buf)
{
	return (LME_sendMessage(module, &buf, sizeof(buf)) == sizeof(buf));
}
#define pCurrentOffset ((int)(pCurrent - buf))
bool LME_ChannelOpenForwardedRequest(struct LMEConnection* module, unsigned int senderChannel, char* connectedIP, unsigned int connectedPort, char* originatorIP, unsigned int originatorPort)
{
	int res;
	int connectedIPlen = (int)strnlen_s(connectedIP, _MAX_PATH);
	int originatorIPlen = (int)strnlen_s(originatorIP, _MAX_PATH);
	unsigned char *buf;
	unsigned char *pCurrent;
	int mallocSize = 5 + APF_STR_SIZE_OF(APF_OPEN_CHANNEL_REQUEST_FORWARDED) + 16 + connectedIPlen + 8 + originatorIPlen + 4;
	if (!LME_IsInitialized(module)) return FALSE;

	if ((buf = (unsigned char*)malloc(mallocSize)) == NULL) ILIBCRITICALEXIT(254);
	pCurrent = buf;

	if (strnlen_s(originatorIP, _MAX_PATH) > 63) { free(buf); return FALSE; }
	*pCurrent = APF_CHANNEL_OPEN;
	++pCurrent;

	*((unsigned int *)pCurrent) = htonl(APF_STR_SIZE_OF(APF_OPEN_CHANNEL_REQUEST_FORWARDED));
	pCurrent += sizeof(unsigned int);

	memcpy_s(pCurrent, mallocSize - pCurrentOffset, APF_OPEN_CHANNEL_REQUEST_FORWARDED, APF_STR_SIZE_OF(APF_OPEN_CHANNEL_REQUEST_FORWARDED));
	pCurrent += APF_STR_SIZE_OF(APF_OPEN_CHANNEL_REQUEST_FORWARDED);

	*((unsigned int *)pCurrent) = htonl(senderChannel);
	pCurrent += sizeof(unsigned int);

	*((unsigned int *)pCurrent) = htonl(LME_RX_WINDOW_SIZE);
	pCurrent += sizeof(unsigned int);

	*((unsigned int *)pCurrent) = 0xFFFFFFFF;
	pCurrent += sizeof(unsigned int);

	*((unsigned int *)pCurrent) = htonl(connectedIPlen);
	pCurrent += sizeof(unsigned int);

	memcpy_s(pCurrent, mallocSize - pCurrentOffset, connectedIP, connectedIPlen);
	pCurrent += connectedIPlen;

	*((unsigned int *)pCurrent) = htonl(connectedPort);
	pCurrent += sizeof(unsigned int);

	*((unsigned int *)pCurrent) = htonl((unsigned int)originatorIPlen);
	pCurrent += sizeof(unsigned int);

	memcpy_s(pCurrent, mallocSize - pCurrentOffset, originatorIP, originatorIPlen);
	pCurrent += originatorIPlen;

	*((unsigned int *)pCurrent) = htonl(originatorPort);
	pCurrent += sizeof(unsigned int);

	res = LME_sendMessage(module, buf, (int)(pCurrent - buf));
	free(buf);

	return (res == pCurrent - buf);
}

bool LME_ChannelOpenReplySuccess(struct LMEConnection* module, unsigned int recipientChannel, unsigned int senderChannel)
{
	APF_CHANNEL_OPEN_CONFIRMATION_MESSAGE message;
	message.MessageType = APF_CHANNEL_OPEN_CONFIRMATION;
	message.RecipientChannel = htonl(recipientChannel);
	message.SenderChannel = htonl(senderChannel);
	message.InitialWindowSize = htonl(LME_RX_WINDOW_SIZE);
	message.Reserved = 0xFFFFFFFF;
	return (LME_sendMessage(module, (unsigned char*)&message, sizeof(message)) == sizeof(message));
}

bool LME_ChannelOpenReplyFailure(struct LMEConnection* module, unsigned int recipientChannel, unsigned int reason)
{
	APF_CHANNEL_OPEN_FAILURE_MESSAGE message;
	message.MessageType = APF_CHANNEL_OPEN_FAILURE;
	message.RecipientChannel = htonl(recipientChannel);
	message.ReasonCode = htonl(reason);
	message.Reserved = 0x00000000;
	message.Reserved2 = 0x00000000;
	return (LME_sendMessage(module, (unsigned char*)&message, sizeof(message)) == sizeof(message));
}

bool LME_ChannelClose(struct LMEConnection* module, unsigned int recipientChannel, unsigned int senderChannel )
{
	APF_CHANNEL_CLOSE_MESSAGE message;
	UNREFERENCED_PARAMETER( senderChannel );

	message.MessageType = APF_CHANNEL_CLOSE;
	message.RecipientChannel = htonl(recipientChannel);
	return (LME_sendMessage(module, (unsigned char*)&message, sizeof(message)) == sizeof(message));
}

int LME_ChannelData(struct LMEConnection* module, unsigned int recipientChannel, unsigned int len, unsigned char *buffer)
{
	APF_CHANNEL_DATA_MESSAGE *message;
	if (len > (LME_GetMeiBufferSize(module) - sizeof(APF_CHANNEL_DATA_MESSAGE)) || module->txBuffer == NULL) return -1;
	message = (APF_CHANNEL_DATA_MESSAGE*)module->txBuffer;
	message->MessageType = APF_CHANNEL_DATA;
	message->RecipientChannel = htonl(recipientChannel);
	message->DataLength = htonl(len);
	memcpy_s(module->txBuffer + sizeof(APF_CHANNEL_DATA_MESSAGE), LME_GetMeiBufferSize(module) - sizeof(APF_CHANNEL_DATA_MESSAGE), buffer, len);
	return LME_sendMessage(module, (unsigned char *)message, sizeof(APF_CHANNEL_DATA_MESSAGE) + len) - sizeof(APF_CHANNEL_DATA_MESSAGE);
}

bool LME_ChannelWindowAdjust(struct LMEConnection* module, unsigned int recipientChannel, unsigned int len)
{
	APF_WINDOW_ADJUST_MESSAGE message;
	message.MessageType = APF_CHANNEL_WINDOW_ADJUST;
	message.RecipientChannel = htonl(recipientChannel);
	message.BytesToAdd = htonl(len);
	return (LME_sendMessage(module, (unsigned char *)&message, sizeof(message)) == sizeof(message));
}

int LME_sendMessage(struct LMEConnection* module, unsigned char *buffer, int len)
{
	int result;
	if (!LME_IsInitialized(module)) { return -1; }
	result = heci_SendMessage(&(module->mei), buffer, len, MEI_IO_TIMEOUT);
	if (result < 0) LME_Deinit(module);
	return result;
}

bool LME_checkMinMsgSize(unsigned char *buf, unsigned int bytesRead)
{
	switch (buf[0]) {
	case APF_DISCONNECT:
		if (bytesRead < sizeof(APF_DISCONNECT_MESSAGE)) { return FALSE; }
		break;
	case APF_SERVICE_REQUEST:
		if (bytesRead < sizeof(APF_SERVICE_REQUEST_MESSAGE)) { return FALSE; }
		if (bytesRead < (sizeof(APF_SERVICE_REQUEST_MESSAGE) + ntohl(((APF_SERVICE_REQUEST_MESSAGE *)buf)->ServiceNameLength))) { return FALSE; }
		break;
	case APF_USERAUTH_REQUEST:
		if (bytesRead < (3 * sizeof(unsigned int))) { return FALSE; }
		break;
	case APF_GLOBAL_REQUEST:
		if (bytesRead < (sizeof(APF_GENERIC_HEADER) + sizeof(UINT8))) { return FALSE; }
		if (bytesRead < (sizeof(APF_GENERIC_HEADER) + sizeof(UINT8) + ntohl(((APF_GENERIC_HEADER *)buf)->StringLength))) { return FALSE; }
		break;
	case APF_CHANNEL_OPEN:
		if (bytesRead < sizeof(APF_GENERIC_HEADER)) { return FALSE; }
		if (bytesRead < (sizeof(APF_GENERIC_HEADER) + ntohl(((APF_GENERIC_HEADER *)buf)->StringLength))) { return FALSE; }
		break;
	case APF_CHANNEL_OPEN_CONFIRMATION:
		if (bytesRead < sizeof(APF_CHANNEL_OPEN_CONFIRMATION_MESSAGE)) { return FALSE; }
		break;
	case APF_CHANNEL_OPEN_FAILURE:
		if (bytesRead < sizeof(APF_CHANNEL_OPEN_FAILURE_MESSAGE)) { return FALSE; }
		break;
	case APF_CHANNEL_CLOSE:
		if (bytesRead < sizeof(APF_CHANNEL_CLOSE_MESSAGE)) { return FALSE; }
		break;
	case APF_CHANNEL_DATA:
		if (bytesRead < sizeof(APF_CHANNEL_DATA_MESSAGE)) { return FALSE; }
		if (bytesRead < (sizeof(APF_CHANNEL_DATA_MESSAGE) + ntohl(((APF_CHANNEL_DATA_MESSAGE *)buf)->DataLength))) { return FALSE; }
		break;
	case APF_CHANNEL_WINDOW_ADJUST:
		if (bytesRead < sizeof(APF_WINDOW_ADJUST_MESSAGE)) { return FALSE; }
		break;
	case APF_PROTOCOLVERSION:
		if (bytesRead < sizeof(APF_PROTOCOL_VERSION_MESSAGE)) { return FALSE; }
		break;
	default:
		return FALSE;
	}
	return TRUE;
}

void LME_doRX(struct LMEConnection* module, unsigned char *rxBuffer, unsigned int bytesRead)
{
	if (bytesRead == 0) return;
	if (!LME_checkMinMsgSize(rxBuffer, bytesRead)) { LME_Deinit(module); return; }
	module->cb(module, module->cbParam, rxBuffer, bytesRead);
}

/*
void LME_apfChannelOpen(struct LMEConnection* module, unsigned char *rxBuffer, unsigned int bytesRead, int *status)
{
	APF_GENERIC_HEADER *pHeader = (APF_GENERIC_HEADER *)rxBuffer;

	if (_strnicmp((char *)pHeader->String, APF_OPEN_CHANNEL_REQUEST_DIRECT, APF_STR_SIZE_OF(APF_OPEN_CHANNEL_REQUEST_DIRECT)) == 0)
	{
		unsigned int senderChannel = 0;

		LME_apfChannelOpenDirect(module, rxBuffer, bytesRead, &senderChannel, status);
		if (LME_IsInitialized(module) && (*status == 1)) {
			if (plugin.retry(rxBuffer, bytesRead) != LMS_DROPPED) { LME_apfChannelOpenDirect(module, rxBuffer, bytesRead, NULL, status); }
		}

		if (LME_IsInitialized(module) && (*status == 1)) {
			LME_ChannelOpenReplyFailure(module, senderChannel, OPEN_FAILURE_REASON_CONNECT_FAILED);
		}
	}
}

void LME_apfChannelOpenDirect(struct LMEConnection* module, unsigned char *rxBuffer, unsigned int bytesRead, unsigned int *senderChannel, int *status)
{
	unsigned char *pCurrent;
	APF_GENERIC_HEADER *pHeader = (APF_GENERIC_HEADER *)rxBuffer;

	if (bytesRead < sizeof(APF_GENERIC_HEADER) +
	    ntohl(pHeader->StringLength) +
	    7 + (5 * sizeof(unsigned int))) {
		ILIBMESSAGE("apfChannelOpenDirect: Error receiving data from MEI\n");
		LME_Deinit(module);
		return;
	}

	pCurrent = rxBuffer + sizeof(APF_GENERIC_HEADER) +
		APF_STR_SIZE_OF(APF_OPEN_CHANNEL_REQUEST_DIRECT);

	LMEChannelOpenRequestMessage channelOpenRequest;
	channelOpenRequest.ChannelType = APF_CHANNEL_DIRECT;

	channelOpenRequest.SenderChannel = ntohl(*((unsigned int *)pCurrent));
	if (senderChannel) {
		*senderChannel = channelOpenRequest.SenderChannel;
	}
	pCurrent += sizeof(unsigned int);
	channelOpenRequest.InitialWindow = ntohl(*((unsigned int *)pCurrent));
	pCurrent += 2 * sizeof(unsigned int);

	unsigned int len = ntohl(*((unsigned int *)pCurrent));
	pCurrent += sizeof(unsigned int);
	channelOpenRequest.Address.append((char *)pCurrent, len);
	pCurrent += len;
	channelOpenRequest.Port = ntohl(*((unsigned int *)pCurrent));
	pCurrent += sizeof(unsigned int);

	module->_cb(module, module->_cbParam, &channelOpenRequest, sizeof(channelOpenRequest), status);
}
*/

/*
void LME_apfUserAuthRequest(struct LMEConnection* module, unsigned char *rxBuffer, unsigned int bytesRead, int *status)
{
	unsigned char *pCurrent = rxBuffer;

	++pCurrent;

	LMEUserAuthRequestMessage userAuthRequest;

	unsigned int len = ntohl(*((unsigned int *)pCurrent));
	pCurrent += sizeof(unsigned int);

	if ((bytesRead - (pCurrent - rxBuffer)) < len) {
		ILIBMESSAGE("_apfUserAuthRequest1: Error receiving data from MEI\n");
		LME_Deinit(module);
		return;
	}

	userAuthRequest.Username.append((char *)pCurrent, len);
	pCurrent += len;

	if ((unsigned int)(bytesRead - (pCurrent - rxBuffer)) < sizeof(unsigned int)) {
		ILIBMESSAGE("_apfUserAuthRequest2: Error receiving data from MEI\n");
		LME_Deinit(module);
		return;
	}

	len = ntohl(*((unsigned int *)pCurrent));
	pCurrent += sizeof(unsigned int);

	if ((bytesRead - (pCurrent - rxBuffer)) < len) {
		ILIBMESSAGE("_apfUserAuthRequest3: Error receiving data from MEI\n");
		LME_Deinit(module);
		return;
	}

	userAuthRequest.ServiceName.append((char *)pCurrent, len);
	pCurrent += len;

	if ((unsigned int)(bytesRead - (pCurrent - rxBuffer)) < sizeof(unsigned int)) {
		ILIBMESSAGE("_apfUserAuthRequest4: Error receiving data from MEI\n");
		LME_Deinit(module);
		return;
	}

	len = ntohl(*((unsigned int *)pCurrent));
	pCurrent += sizeof(unsigned int);

	if ((bytesRead - (pCurrent - rxBuffer)) < len) {
		ILIBMESSAGE("_apfUserAuthRequest5: Error receiving data from MEI\n");
		LME_Deinit(module);
		return;
	}

	userAuthRequest.MethodName.append((char *)pCurrent, len);
	pCurrent += len;

	if (_strnicmp(userAuthRequest.MethodName.c_str(), APF_AUTH_PASSWORD,
			userAuthRequest.MethodName.size()) == 0) {

		if ((unsigned int)(bytesRead - (pCurrent - rxBuffer)) < sizeof(unsigned int) + 1) {
			ILIBMESSAGE("_apfUserAuthRequest6: Error receiving data from MEI\n");
			LME_Deinit(module);
			return;
		}

		++pCurrent;

		len = ntohl(*((unsigned int *)pCurrent));
		pCurrent += sizeof(unsigned int);

		if ((bytesRead - (pCurrent - rxBuffer)) < len) {
			ILIBMESSAGE("_apfUserAuthRequest7: Error receiving data from MEI\n");
			LME_Deinit(module);
			return;
		}

		AuthPasswordData authData;
		authData.Password.append((char *)pCurrent, len);
		pCurrent += len;

		userAuthRequest.MethodData = &authData;
	}

	module->_cb(module, module->_cbParam, &userAuthRequest, sizeof(userAuthRequest), status);
}
*/

unsigned int LME_GetMeiBufferSize(struct LMEConnection* module)
{
	return heci_GetBufferSize(&(module->mei));
}

#endif
