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

#ifndef _MINCORE

#include <windows.h>
#include <setupapi.h>
#include <initguid.h>
#include <winioctl.h>
#include "HECIWin.h"
#include "heci_if.h"

#define false 0
#define true 1
#define HECI_MAX_LINE_LEN 300 

DEFINE_GUID(GUID_DEVINTERFACE_HECI, 0xE2D1FF34, 0x3458, 0x49A9, 0x88, 0xDA, 0x8E, 0x69, 0x15, 0xCE, 0x9B, 0xE5);
DEFINE_GUID(HECI_PTHI_GUID        , 0x12F80028, 0xB4B7, 0x4b2d, 0xAC, 0xA8, 0x46, 0xE0, 0xFF, 0x65, 0x81, 0x4c);
DEFINE_GUID(LMS_GUID              , 0x6733a4db, 0x0476, 0x4e7b, 0xb3, 0xaf, 0xbc, 0xfc, 0x29, 0xbe, 0xe7, 0xa7);
DEFINE_GUID(MKHI_GUID             , 0x8E6A6715, 0x9ABC, 0x4043, 0x88, 0xEF, 0x9E, 0x39, 0xC6, 0xF6, 0x3E, 0x0F);

//VOID _displayHECIError(UINT32 errorCode,DWORD lastError);
//VOID _displayHECIData(UINT32 messageId);
int heci_doIoctl(struct MEImodule* module, DWORD code, void *inbuf, int inlen, void *outbuf, int outlen);

struct MEImodule MEI_global;

/***************************** public functions *****************************/

unsigned int heci_GetBufferSize(struct MEImodule* module) { if (module != NULL) return module->_bufSize; else return MEI_global._bufSize; }
unsigned char heci_GetProtocolVersion(struct MEImodule* module) { if (module != NULL) return module->_protocolVersion; else return MEI_global._protocolVersion; }
bool heci_IsInitialized(struct MEImodule* module) { if (module != NULL) return module->_initialized; else return MEI_global._initialized; }

bool heci_GetHeciVersion(struct MEImodule* module, HECI_VERSION *version)
{
	if (module == NULL) module = &MEI_global;
	
	if (module->m_haveHeciVersion)
	{
		memcpy_s(version, sizeof(HECI_VERSION), &(module->m_heciVersion), sizeof(HECI_VERSION));
		return true;
	}

	return false;
}

bool heci_Init(struct MEImodule* module, HECI_CLIENTS client)
{
    PSP_DEVICE_INTERFACE_DETAIL_DATA deviceDetail = NULL;
    HDEVINFO hDeviceInfo;
    DWORD bufferSize;
	SP_DEVICE_INTERFACE_DATA interfaceData;  
    LONG ii = 0;
	int result;
	HECI_CLIENT properties;
	GUID guid;

	if (client == PTHI_CLIENT) guid = HECI_PTHI_GUID;
	if (client == LMS_CLIENT) guid = LMS_GUID;
	if (client == MKHI_CLIENT) guid = MKHI_GUID;
	if (module == NULL) module = &MEI_global;
	module->_verbose = false;

	if (module->_initialized) {
		heci_Deinit(module);
	}

	// Find all devices that have our interface
	hDeviceInfo = SetupDiGetClassDevs((LPGUID)&GUID_DEVINTERFACE_HECI, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    if (hDeviceInfo == INVALID_HANDLE_VALUE) {        
		if (module->_verbose) {
            //_displayHECIError(GET_CLASS_DEVS,GetLastError());
		}
        return false; //GET_CLASS_DEVS;
    }

	// Setup the interface data struct
    interfaceData.cbSize = sizeof(interfaceData);
	for (ii = 0; 
         SetupDiEnumDeviceInterfaces(hDeviceInfo, NULL, (LPGUID)&GUID_DEVINTERFACE_HECI, ii, &interfaceData);
         ++ii) {
        // Found our device instance
        if (!SetupDiGetDeviceInterfaceDetail(hDeviceInfo, &interfaceData, NULL,  0, &bufferSize, NULL)) {
			DWORD err = GetLastError();
            if (err != ERROR_INSUFFICIENT_BUFFER) {
				if (module->_verbose) {
                     //_displayHECIError(GET_INTERFACE_DETAIL,err);
				}
                continue;
            }
        }

        // Allocate a big enough buffer to get detail data
        deviceDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA) malloc(bufferSize);
        if (deviceDetail == NULL) {
			if (module->_verbose) {
                //_displayHECIError(ALLOCATE_MEMORY_ERROR,0);
			}
            continue;
        }

        // Setup the device interface struct
        deviceDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

        // Try again to get the device interface detail info
        if (!SetupDiGetDeviceInterfaceDetail(hDeviceInfo, &interfaceData, deviceDetail, bufferSize, NULL, NULL))
		{
			/*
			if (_verbose)
			{
                DWORD err = GetLastError();
                _displayHECIError(GET_INTERFACE_DETAIL,err);
			}
			*/
            free(deviceDetail);
			deviceDetail = NULL;
            continue;
        }

		break;
    }
	SetupDiDestroyDeviceInfoList(hDeviceInfo);

	if (deviceDetail == NULL) {
		if (module->_verbose) {
            //_displayHECIError(FIND_HECI_FAILURE,0);
		}
		return false; //FIND_HECI_FAILURE;
	}

	module->_handle = CreateFile(deviceDetail->DevicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
	free(deviceDetail);

	if (module->_handle == INVALID_HANDLE_VALUE) {
		if (module->_verbose) {			
            //_displayHECIError(CREATE_HECI_FILE_FAILURE,GetLastError());
		}
		return false; //CREATE_HECI_FILE_FAILURE;
	}
	module->_initialized = true;

	result = heci_doIoctl(module, (DWORD)IOCTL_HECI_GET_VERSION, NULL, 0, &(module->m_heciVersion), sizeof(HECI_VERSION));
	if (result != sizeof(HECI_VERSION)) {
		if (module->_verbose) {
            //_displayHECIError(GET_HECI_DRIVER_VERSION_FAILURE,0);
		}
		heci_Deinit(module);
		return false; //GET_HECI_DRIVER_VERSION_FAILURE;
	}
	module->m_haveHeciVersion = true;

	if (module->_verbose) {
        //_displayHECIData(HECI_DRIVER_VERSION);
		//_ftprintf(stdout,_T("%d.%d.%d.%d\n"), (m_heciVersion).major, (m_heciVersion).minor, (m_heciVersion).hotfix, (m_heciVersion).build);
	}

	memset(&properties, 0, sizeof(properties));
	result = heci_doIoctl(module, (DWORD)IOCTL_HECI_CONNECT_CLIENT, (void*)(&guid), sizeof(GUID), &properties, sizeof(properties));
	if (result != sizeof(properties))
	{
		if (module->_verbose) {
            //_displayHECIError(HECI_CONNECT_TO_PTHI_CLIENT_FAILURE,0);
		}
		//Deinit();
		return false; //HECI_CONNECT_TO_PTHI_CLIENT_FAILURE;
	}
	module->_bufSize = properties.MaxMessageLength;

	return true;
}

void heci_Deinit(struct MEImodule* module)
{
	if (module == NULL) module = &MEI_global;
	if (module->_initialized == false) return;
	module->_initialized = false;
	module->_bufSize = 0;

	if (module->_handle != INVALID_HANDLE_VALUE)
	{
		CloseHandle(module->_handle);
		module->_handle = INVALID_HANDLE_VALUE;
	}
}

int heci_ReceiveMessage(struct MEImodule* module, unsigned char *buffer, int len, unsigned long timeout) // Timeout default is 2000
{
	DWORD bytesRead = 0;
	int res;
	HANDLE h_event = NULL;
	OVERLAPPED overlapped;
	DWORD error;
	DWORD eventRes;
	
	if (module == NULL) module = &MEI_global;
	if ((h_event = CreateEvent(NULL, FALSE, FALSE, NULL)) == 0) goto out;
	overlapped.hEvent = h_event;
	overlapped.Offset = 0;
	overlapped.OffsetHigh = 0;

	res = ReadFile(module->_handle, buffer, len, &bytesRead, &overlapped);
	error = GetLastError();
	if ((0 == res) && (ERROR_IO_PENDING != error)) {
		if (module->_verbose) {
			//_displayHECIError(READ_FILE,GetLastError());
		}
		bytesRead = (DWORD)-1;
		goto out;
	}

	eventRes = WaitForSingleObject(h_event, timeout);
	if (eventRes == WAIT_TIMEOUT) {
		bytesRead = 0;
		goto out;
	}
	
	res = GetOverlappedResult(module->_handle, &overlapped, &bytesRead, true);
	
	if (res == 0) {
		if (module->_verbose) {
            //_displayHECIError(READ_FILE,GetLastError());
		}
		bytesRead = (DWORD)-1;
		goto out;
	}

out:
	if (h_event != NULL) CloseHandle(h_event);
	if (bytesRead <= 0) heci_Deinit(module);

	return bytesRead;
}

int heci_SendMessage(struct MEImodule* module, const unsigned char *buffer, int len, unsigned long timeout)  // Timeout default is 2000
{
	DWORD bytesWritten = 0;
	int res;
	HANDLE h_event = NULL;
	OVERLAPPED overlapped;
	DWORD lastError;
	DWORD eventRes;

	if (module == NULL) module = &MEI_global;
	if ((h_event = CreateEvent(NULL, FALSE, FALSE, NULL)) == 0) goto out;
	overlapped.hEvent = h_event;
	overlapped.Offset = 0;
	overlapped.OffsetHigh = 0;
    
	res = WriteFile(module->_handle, buffer, len, &bytesWritten, &overlapped);
	
    lastError = GetLastError();
	if ((0 == res) && (ERROR_IO_PENDING !=lastError )) {
		if (module->_verbose) {
            //_displayHECIError(WRITE_FILE,GetLastError());
		}
		bytesWritten = (DWORD)-1;
		goto out;
	}

	eventRes = WaitForSingleObject(h_event, timeout);
	if (eventRes == WAIT_TIMEOUT) {
		if (module->_verbose) {
            //_displayHECIError(WRITE_FILE_TIME_OUT,0);
		}
		bytesWritten = 0;
		goto out;
	}

	res = GetOverlappedResult(module->_handle, &overlapped, &bytesWritten, false);

	if (res == 0) {
		if (module->_verbose) {
            //_displayHECIError(WRITE_FILE,GetLastError());
		}
		bytesWritten = (DWORD)-1;
		goto out;
	}

out:
	if (h_event != NULL) CloseHandle(h_event);
	if (bytesWritten <= 0) heci_Deinit(module);

	return bytesWritten;
}

int heci_doIoctl(struct MEImodule* module, DWORD code, void *inbuf, int inlen, void *outbuf, int outlen)
{
	int res;
	DWORD bytesRead = 0;
	HANDLE h_event = NULL;
	OVERLAPPED overlapped;

	if (module == NULL) module = &MEI_global;
	if (!module->_initialized) return -1;

	if ((h_event = CreateEvent(NULL, FALSE, FALSE, NULL)) == 0) goto out;
	overlapped.hEvent = h_event;
	overlapped.Offset = 0;
	overlapped.OffsetHigh = 0;

	res = DeviceIoControl(module->_handle, code, inbuf, inlen, outbuf, outlen, &bytesRead, &overlapped);
	
	if ((0 == res) && (ERROR_IO_PENDING != GetLastError())) {
		if (module->_verbose) {
			 //_displayHECIError(IOCTL_COMMAND,GetLastError());
		}
		bytesRead = (DWORD)-1;
		goto out;
	}

	WaitForSingleObject(h_event, INFINITE);

	res = GetOverlappedResult(module->_handle, &overlapped, &bytesRead, true);
	if (res == 0) {
		if (module->_verbose) {
            //_displayHECIError(IOCTL_COMMAND,GetLastError());
		}
		bytesRead = (DWORD)-1;
		goto out;
	}

out:
	if (h_event != NULL) CloseHandle(h_event);
	if (bytesRead == (DWORD)-1) heci_Deinit(module);

	return bytesRead;
}

TCHAR *_getErrMsg(DWORD err)
{
	static TCHAR buffer[1024];
	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
					NULL,
					err,
					0,
					buffer,
					sizeof(buffer) - 1,
					0);

	return buffer;
}

/*
// Display a HECI error message
void _displayHECIError(UINT32 errorCode, DWORD lastError)
{
    TCHAR str[HECI_MAX_LINE_LEN];
    TCHAR *msg; 
    LoadString(GetModuleHandle(NULL), HECI_ERROR_MESSAGE, str, sizeof(str)/sizeof(TCHAR));
    _ftprintf(stderr, _T("%s"), str);
	_ftprintf(stderr, _T("%s"), L" ");
	LoadString(GetModuleHandle(NULL), errorCode , str, sizeof(str)/sizeof(TCHAR));
    if(0!= lastError)
    {
         msg = _getErrMsg(lastError);
	    _ftprintf(stderr, _T("%s (%d): %s\n"),str, lastError, msg);
    }
    else
    {
        _ftprintf(stderr, _T("%s\n"),str);
    }
}

// Display a HECI data message
void _displayHECIData(UINT32 messageId)
{
    TCHAR str[HECI_MAX_LINE_LEN];
    LoadString(GetModuleHandle(NULL), messageId , str, sizeof(str)/sizeof(TCHAR));
    _ftprintf(stdout,_T("%s"),str);
	_ftprintf(stdout,_T("%s"),L" ");
}
*/

#endif
