/***********************************************************************/
/**

\file
Functions routing HPI messages to two or more modules (i.e. hpioct32 and hpiudp)

Copyright (C) 1997-2017 AudioScience, Inc. All rights reserved.

This software is provided 'as-is', without any express or implied warranty.
In no event will AudioScience Inc. be held liable for any damages arising
from the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not
   claim that you wrote the original software. If you use this software
   in a product, an acknowledgment in the product documentation would be
   appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
   misrepresented as being the original software.
3. This copyright notice and list of conditions may not be altered or removed
   from any source distribution.

AudioScience, Inc. <support@audioscience.com>

( This license is GPL compatible see http://www.gnu.org/licenses/license-list.html#GPLCompatibleLicenses )

 */
/***********************************************************************/

#ifdef HPI_BUILD_MULTIINTERFACE
#define SOURCEFILE_NAME "hpimux.c"

#include "hpi_internal.h"
#include "hpi_version.h"
#include "hpimsginit.h"
#include "hpidebug.h"

uint16_t HPI_DriverOpenIoctl(void);
void HPI_DriverCloseIoctl(void);
void HPI_MessageIoctl(HPI_MESSAGE *phm, HPI_RESPONSE *phr);

#if defined HPI_OS_LINUX && defined HPI_BUILD_SSX2
#include "hpissx2.h"
#define HPI_MESSAGE_LOCAL_ADAPTERS(m,r) HPI_MESSAGE_LOWER_LAYER(m,r,NULL)
#else
#define HPI_MESSAGE_LOCAL_ADAPTERS(m,r) HPI_MessageIoctl(m,r)
#endif
typedef uint16_t (*PHPI_DRIVEROPEN) (void);
typedef void (*PHPI_DRIVERCLOSE) (void);
typedef void (*PHPI_MESSAGE) (HPI_MESSAGE *phm, HPI_RESPONSE *phr,unsigned int timeout);

struct set_host_if {
	int pending;
	HPI_MESSAGE msg;
	char ip[256];
};

struct set_host_if host_if;

#ifdef HPI_BUILD_USEUDPDLL
#ifdef _WIN64
#define HPI_UDPDLLNAME	"asihpiudp64.dll"
#define HPI_DRIVEROPEN_SYMBOL "HPI_DriverOpenUdp"
#define HPI_DRIVERCLOSE_SYMBOL "HPI_DriverCloseUdp"
#define HPI_MESSAGEUDP_SYMBOL "HPI_MessageUdp"
#else
#define HPI_UDPDLLNAME	"asihpiudp32.dll"
#define HPI_DRIVEROPEN_SYMBOL "_HPI_DriverOpenUdp@0"
#define HPI_DRIVERCLOSE_SYMBOL "_HPI_DriverCloseUdp@0"
#define HPI_MESSAGEUDP_SYMBOL "_HPI_MessageUdp@12"
#endif
static uint16_t HPI_DriverOpenStub(void)
{
	return 0;
}

static void HPI_DriverCloseStub(void)
{
}

static void HPI_MessageStub(HPI_MESSAGE *phm, HPI_RESPONSE *phr,unsigned int timeout)
{
	HPI_InitResponse(phr, phm->wObject, phm->wFunction, HPI_ERROR_INVALID_OBJ_INDEX);
	if (phm->wFunction == HPI_SUBSYS_SET_NETWORK_INTERFACE) {
		host_if.pending = 1;
		memcpy( &host_if.msg, phm, sizeof(HPI_MESSAGE) );
		strncpy( host_if.ip, phm->u.s.resource.r.net_if, sizeof(host_if.ip) );
		host_if.msg.u.s.resource.r.net_if = host_if.ip;
		phr->wError = 0;
	}
}

static PHPI_DRIVEROPEN HPIMUX_DriverOpenUdp = HPI_DriverOpenStub;
static PHPI_DRIVERCLOSE HPIMUX_DriverCloseUdp = HPI_DriverCloseStub;
static PHPI_MESSAGE HPIMUX_MessageUdp = HPI_MessageStub;
static HMODULE hUdpModule = NULL;
static int udpLoadAlreadyFailed = 0;
#else
/* static linkage to hpiudp */
uint16_t HPI_DriverOpenUdp(void);
void HPI_DriverCloseUdp(void);
void HPI_MessageUdp(HPI_MESSAGE *phm, HPI_RESPONSE *phr, unsigned int timeout);

static PHPI_DRIVEROPEN HPIMUX_DriverOpenUdp = HPI_DriverOpenUdp;
static PHPI_DRIVERCLOSE HPIMUX_DriverCloseUdp = HPI_DriverCloseUdp;
static PHPI_MESSAGE HPIMUX_MessageUdp = HPI_MessageUdp;
#endif

static int nIoctlDriver = 0;
static int nUdpDriver = 0;
static int init = 0;

static uint16_t wNumIoctlAdapters = 0;
static uint16_t wNumUdpAdapters = 0;


uint16_t HPI_DriverOpen(void)
{
	HPI_DEBUG_LOG0(DEBUG,"HPI_DriverOpen()\n");
	nIoctlDriver = HPI_DriverOpenIoctl();
#if defined HPI_OS_LINUX
#if defined HPI_BUILD_SSX2
	if (nIoctlDriver) {
		struct hpi_message hm;
		struct hpi_response hr;
		int i;

		char * psSsxEnable;

		psSsxEnable = getenv("LIBHPI_SSX_ENABLE");
		if (psSsxEnable) {
			for (i = 0; i < strlen(psSsxEnable); i++) {
				if (psSsxEnable[i] == '1') {
					HPI_DEBUG_LOG1(INFO, "SSX2 enabled on adapter %d\n", i);
					HPI_InitMessageResponse( &hm, &hr,  HPI_OBJ_ADAPTER, HPI_ADAPTER_SET_PROPERTY );
					hm.u.ax.property_set.wProperty = HPI_ADAPTER_PROPERTY_ENABLE_SSX2;
					hm.u.ax.property_set.wParameter1 = 1;
					hm.u.ax.property_set.wParameter2 = 1;
					hm.wAdapterIndex = i;
					HPI_MESSAGE_LOCAL_ADAPTERS( &hm, &hr);
				}
			}
		}
	}
#endif
#endif

#ifdef HPI_BUILD_USEUDPDLL
	if (!udpLoadAlreadyFailed && !hUdpModule) {
		hUdpModule = LoadLibrary(TEXT(HPI_UDPDLLNAME));
		if (!hUdpModule) {
			HPI_DEBUG_LOG1(DEBUG,"Could not load %s\n",TEXT(HPI_UDPDLLNAME));
			HPI_DEBUG_LOG0(DEBUG, "UDP DLL Not Found\n");
			udpLoadAlreadyFailed = 1;
		} else {
			HPI_DEBUG_LOG1(DEBUG,
				"Successfully loaded %s\n",TEXT(HPI_UDPDLLNAME));
			HPI_DEBUG_LOG0(DEBUG, "UDP DLL Loaded\n");
			HPIMUX_DriverOpenUdp =
			    (PHPI_DRIVEROPEN) GetProcAddress(hUdpModule,
							     HPI_DRIVEROPEN_SYMBOL);
			if (HPIMUX_DriverOpenUdp)
				HPI_DEBUG_LOG0(DEBUG,
					"Successfully got proc address for _HPI_DriverOpenUdp@0\n");
			HPIMUX_DriverCloseUdp =
			    (PHPI_DRIVERCLOSE) GetProcAddress(hUdpModule,
							      HPI_DRIVERCLOSE_SYMBOL);
			HPIMUX_MessageUdp =
			    (PHPI_MESSAGE) GetProcAddress(hUdpModule,
							  HPI_MESSAGEUDP_SYMBOL);
		}
	}
#endif

	if (HPIMUX_DriverOpenUdp) {
		HPI_RESPONSE hr;
		nUdpDriver = HPIMUX_DriverOpenUdp();

		if( host_if.pending ) {
			HPIMUX_MessageUdp( &host_if.msg, &hr, HPI_ETHERNET_TIMEOUT_MS);
			host_if.pending = 0;
		}
	}

	HPI_DEBUG_LOG2(DEBUG,"HPI_DriverOpen ioctl=%d, udp=%d\n", nIoctlDriver, nUdpDriver);
	return (nIoctlDriver || nUdpDriver);
}

void HPI_DriverClose(void)
{
	HPI_DEBUG_LOG0(DEBUG,"HPI_DriverClose()\n");
	if (nIoctlDriver) {
		HPI_DEBUG_LOG0(DEBUG, "HPI_DriverCloseIoctl()\n");
		HPI_DriverCloseIoctl();
	}
	if (nUdpDriver) {
		HPI_DEBUG_LOG0(DEBUG, "HPI_DriverCloseUdp()\n");
		nUdpDriver = 0;
		HPIMUX_DriverCloseUdp();
#ifdef HPI_BUILD_USEUDPDLL
		FreeLibrary(hUdpModule);

		/* from load initialization */
		HPIMUX_DriverOpenUdp = HPI_DriverOpenStub;
		HPIMUX_DriverCloseUdp = HPI_DriverCloseStub;
		HPIMUX_MessageUdp = HPI_MessageStub;
		hUdpModule = NULL;
		udpLoadAlreadyFailed = 0;
#endif
	}
}

// This wrapper is required by hpifirmware.c  - TFE
void HPI_MessageUDP(HPI_MESSAGE *phm, HPI_RESPONSE *phr, const unsigned int nTimeout)
{
	HPIMUX_MessageUdp(phm, phr, nTimeout);
}

void GetNumAdapters(void)
{
	HPI_MESSAGE hm;
	HPI_RESPONSE hr_Ioctl;
	HPI_RESPONSE hr_Udp;

	HPI_InitMessageResponse(&hm, &hr_Ioctl, HPI_OBJ_SUBSYSTEM, HPI_SUBSYS_GET_NUM_ADAPTERS);
	HPI_InitResponse(&hr_Udp, HPI_OBJ_SUBSYSTEM, HPI_SUBSYS_GET_NUM_ADAPTERS, 0);

	if (nIoctlDriver) {
		HPI_DEBUG_LOG0(DEBUG,
			       "HPI_SUBSYS_GET_NUM_ADAPTERS - HPI_MESSAGE_LOCAL_ADAPTERS\n");
		HPI_MESSAGE_LOCAL_ADAPTERS(&hm, &hr_Ioctl);
		if (!hr_Ioctl.wError)
			wNumIoctlAdapters = hr_Ioctl.u.s.wNumAdapters;
		else
			wNumIoctlAdapters = 0;
	} else
		wNumIoctlAdapters = 0;

	if (nUdpDriver) {
		HPI_DEBUG_LOG0(DEBUG,
			       "HPI_SUBSYS_GET_NUM_ADAPTERS - HPI_MessageUdp\n");
		HPIMUX_MessageUdp(&hm, &hr_Udp, HPI_ETHERNET_TIMEOUT_MS);
		if (!hr_Udp.wError)
			wNumUdpAdapters = hr_Udp.u.s.wNumAdapters;
		else
			wNumUdpAdapters = 0;
	} else
		wNumUdpAdapters = 0;

	HPI_DEBUG_LOG2(DEBUG,
		       "HPI_SUBSYS_GET_NUM_ADAPTERS: Ioctl %d, Udp %d\n",
		       wNumIoctlAdapters,
		       wNumUdpAdapters);
}

void HPI_Message(HPI_MESSAGE *phm, HPI_RESPONSE *phr)
{
	HPI_DEBUG_MESSAGE(DEBUG, phm);

	if( !init ) {
		init = 1;
		memset(	&host_if, 0, sizeof(host_if) );
	}

	// check for various global messages
	if (phm->wObject == HPI_OBJ_SUBSYSTEM) {
		HPI_RESPONSE hr_Ioctl;
		HPI_RESPONSE hr_Udp;

		HPI_InitResponse(&hr_Ioctl, phm->wObject, phm->wFunction, 0);
		HPI_InitResponse(&hr_Udp, phm->wObject, phm->wFunction, 0);

		switch (phm->wFunction) {
			// subsys open - init the debug function
		case HPI_SUBSYS_OPEN: {
				HPI_InitResponse(phr, HPI_OBJ_SUBSYSTEM, HPI_SUBSYS_OPEN, 0);
				if (nIoctlDriver) {
					HPI_DEBUG_LOG0(DEBUG,
						"HPI_SUBSYS_OPEN - HPI_MESSAGE_LOCAL_ADAPTERS\n");
					HPI_MESSAGE_LOCAL_ADAPTERS(phm, &hr_Ioctl);
				}
				if (nUdpDriver) {
					HPI_DEBUG_LOG0(DEBUG,
						"HPI_SUBSYS_OPEN - HPI_MessageUdp\n");
					HPIMUX_MessageUdp(phm, &hr_Udp, HPI_ETHERNET_TIMEOUT_MS);
					if (hr_Udp.wError) {
						HPI_DEBUG_LOG0(DEBUG,
							"HPI_SUBSYS_OPEN - nUdpDriver = 0\n");
						nUdpDriver = 0;
					}
				}
			}
			break;

		case HPI_SUBSYS_CLOSE:
			HPI_InitResponse(phr, phm->wObject, phm->wFunction, 0);
			if (nIoctlDriver) {
				HPI_DEBUG_LOG0(DEBUG,
					       "HPI_SUBSYS_CLOSE - HPI_MESSAGE_LOCAL_ADAPTERS\n");
				HPI_MESSAGE_LOCAL_ADAPTERS(phm, phr);
			}
			if (nUdpDriver) {
				HPI_DEBUG_LOG0(DEBUG,
					       "HPI_SUBSYS_CLOSE - HPI_MessageUdp\n");
				HPIMUX_MessageUdp(phm, phr, HPI_ETHERNET_TIMEOUT_MS);
			}
			break;

		case HPI_SUBSYS_GET_VERSION:
			HPI_InitResponse(phr, phm->wObject, phm->wFunction, 0);
			phr->u.s.dwVersion = HPI_VER >> 8;	// break major.minor
			phr->u.s.dwData = HPI_VER;	// break major.minor.release
			break;

		case HPI_SUBSYS_GET_NUM_ADAPTERS:
			HPI_InitResponse(phr, phm->wObject, phm->wFunction, 0);
			GetNumAdapters();
			phr->u.s.wNumAdapters = wNumIoctlAdapters + wNumUdpAdapters;
			break;

		case HPI_SUBSYS_GET_ADAPTER:
			GetNumAdapters();
			if (phm->wObjIndex < wNumIoctlAdapters) {
				HPI_DEBUG_LOG0(DEBUG,
					       "HPI_SUBSYS_GET_ADAPTER - HPI_MESSAGE_LOCAL_ADAPTERS\n");
				HPI_MESSAGE_LOCAL_ADAPTERS(phm, phr);
			} else if (nUdpDriver) {
				phm->wObjIndex -= wNumIoctlAdapters;
				HPI_DEBUG_LOG0(DEBUG,
						   "HPI_SUBSYS_GET_ADAPTER - HPI_MessageUdp\n");
				HPIMUX_MessageUdp(phm, phr, HPI_ETHERNET_TIMEOUT_MS);
				// Despite nUdpDriver being true HPIUDP may be disabled,
				// so we must change the error code on the fly here.
				if (phr->wError && (phr->wError!=HPI_ERROR_DUPLICATE_ADAPTER_NUMBER)) {
					HPI_InitResponse(phr, phm->wObject, phm->wFunction,
						HPI_ERROR_INVALID_OBJ_INDEX);
				}
			} else {
				HPI_InitResponse(phr, phm->wObject, phm->wFunction,
					HPI_ERROR_INVALID_OBJ_INDEX);
			}
			break;

		case HPI_SUBSYS_SET_NETWORK_INTERFACE:
			HPIMUX_MessageUdp(phm, phr, HPI_ETHERNET_TIMEOUT_MS);
			break;

		case HPI_SUBSYS_OPTION_INFO:
		case HPI_SUBSYS_OPTION_GET:
		case HPI_SUBSYS_OPTION_SET:
			/* so far, only HPIUDP uses entity messages,
			but in future may require routing */
			HPIMUX_MessageUdp(phm, phr, HPI_ETHERNET_TIMEOUT_MS);
			break;

		default:
			/* Need to specifically determine which backend(s)
			receive subsys messages */
			HPI_DEBUG_LOG0(WARNING, "unrouted subsys message\n");
			HPI_InitResponse(phr, phm->wObject, phm->wFunction, HPI_ERROR_INVALID_FUNC);
			break;
		}
		return;
	}

	if (phm->wAdapterIndex >= HPI_MIN_NETWORK_ADAPTER_IDX) {
		HPI_DEBUG_LOG0(DEBUG, "DEFAULT - HPI_MessageUdp\n");
		HPIMUX_MessageUdp(phm, phr, HPI_ETHERNET_TIMEOUT_MS);
	} else {
		HPI_DEBUG_LOG0(DEBUG, "DEFAULT - HPI_MESSAGE_LOCAL_ADAPTERS\n");
		HPI_MESSAGE_LOCAL_ADAPTERS(phm, phr);
	}
}
#endif
