/******************************************************************************
UDP HPI NETWORK implementation (Windows)

See
<http://www.sockets.com/winsock.htm>

Sequence of operations:
On SubSysOpen() a message is broadcast to all ASI2416 adapters. Any responses
received are put into the hpi_adapter structure, which is a linked list of
adapters.

On AdapterOpen() the specific socket is created and "bound" to the adapter. On
AdapterClose() the socket is released.


Discovery thread notes
----------------------

HPINET_MessageDiscovery() thread interacts with the linked list of
struct hpi_adapter (s) in the following fashion.

consecutiveLostResponses is used to count how many times the discovery thread has run
without the adapter responding. When the "consecutiveLostResponses" exceeds 10 (10 seconds),
the control information for the adapter is deleted, so that it will be refreshed
next time the adapter is "discovered". This allows an ASI2416 to be powered down,
reconfigured, and have the topology update after it is plugged back in.

On receiving a response of a 'new' adapter, the adapter is added to the list of
known adapters.

On receiving a response from an adapter that is marked as 'dead' the adapter is
marked as alive. Also, consecutiveLostResponses is reset.

Note that the discovery thread is never responsible for marking an adapter as
dead.

UDP message function
--------------------

HPINET_Message() on first call for a particular adapter will read a list of
all controls and store them in the adapter structure. It will also load the
control cache for the first time.

Subsequent control get calls will check the control cache for the cached value
and update the cache if required (based on elapsed time since last update).If
the control is a peak meter and the control is not cached
HPI_ERROR_NETWORK_TOO_MANY_CLIENTS is returned. Other conditions that return
too many clients include, from call to DSP with function
HPI_MIXER_GET_CONTROL_MULTIPLE_VALUES and if the numbers of controls in the
cache is 0 and the client is attempting to read a peak meter.

POSSIBLE IMPROVEMENTS - suspend discovery thread during a firmware upgrade by adding
a gnDiscovery suspend global count.


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 )


*******************************************************************************/
#define SOURCEFILE_NAME "hpiudp.c"

#ifndef WIN32_LEAN_AND_MEAN
#	define WIN32_LEAN_AND_MEAN
#endif

#ifdef HPI_BUILD_EXPORTUDPMSGENTRY
#define HPI_EXPORT __declspec(dllexport) __stdcall
#else
#define HPI_EXPORT
#endif

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>

#define HPIUDP_USE_CACHE
#define HPIUDP_USE_DISCOVERY_THREAD

#define HPI_DISCOVERY_SOURCE_BROADCAST 1
#define HPI_DISCOVERY_SOURCE_MANUAL 0

#ifdef HPI_OS_WIN32_USER
#	include <Winsock2.h>
#	define CreateMutexPtr(h,a,b,c)   do { *h = CreateMutex(a,b,c); } while (0)
#	define MUTEX_HANDLE HANDLE
#else
#	include <sys/types.h>
#	include <sys/socket.h>
#	include <arpa/inet.h>
#	include <sys/time.h>
#	include <resolv.h>
#	include <string.h>
#	include <errno.h>
#	include <unistd.h>
#	include <pthread.h>

#	define SOCKET int
#	define TCHAR char
#	define SOCKADDR struct sockaddr
#	define TEXT(T) T
#	define MUTEX_HANDLE pthread_mutex_t*

#	define WSAETIMEDOUT EAGAIN
#	define INVALID_SOCKET -1
#	define SOCKET_ERROR -1

#	define closesocket close
#	define WSACleanup()
static inline int WSAGetLastError(void) { int r = errno; errno = 0; return r; }
#	define WaitForSingleObject(h, t) pthread_mutex_lock(h)
#	define ReleaseMutex(h) pthread_mutex_unlock(h)
#	define CreateMutexPtr(h, a,b,c) do {*h = malloc(sizeof(pthread_mutex_t)); pthread_mutexattr_t Attr; pthread_mutexattr_settype(&Attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(*h, &Attr) ;} while (0)
#	define CloseHandle(h) do { if (h) { pthread_mutex_destroy(h); free(h); }} while (0)


#endif

#include "hpi_internal.h"
#include "hpi_version.h"
#include "hpimsginit.h"
#include "hpinet.h"
#include "hpidebug.h"
#include "hpicmn.h"
#include "hpiudp_cache.h"

typedef void HPI_message_function(struct hpi_message *phm,
				  struct hpi_response *phr,
				  const unsigned int timeout);
struct hpi_adapter;

#define HPIUDP_INVALID_MAC_WORD 0xffffffff
/* Represents an adapter added but not found */
#define INVALID_SERIAL 0x80000000



struct hpi_adapter {
	HPI_message_function *hpi_message;
	uint32_t serialNumber;
	uint32_t adapterType;
	uint16_t adapterIndex;
	uint16_t discovery_source;
	uint8_t packet_id;
	struct in_addr addr;
	unsigned int macWord2;
	unsigned int macWord1;
	unsigned int macWord0;
	unsigned int adapterIsOpen;
	unsigned int mixerIsOpen;
	unsigned int isDead;
	unsigned int isDuplicate;
	unsigned int consecutiveLostResponses;
	SOCKET sock;
	MUTEX_HANDLE hSocketMutex;
#ifdef HPIUDP_USE_CACHE
#ifdef HPIUDP_CACHE_SHARED_MEM
	HANDLE hFileMappedContext;
#endif
	struct hpiudp_cache_context *cache;
#endif
	struct hpi_adapter *next;	/* can make a linked list of them */
};

struct int_range_entity {
	struct hpi_entity header; // range
	int min;
	int max;
	int step;
};

struct int_constraints_entity {
	struct hpi_entity header; // sequence
	struct int_range_entity range;
};

struct int_entity {
	struct hpi_entity header;
	int value;
};

struct ip4_entity {
	struct hpi_entity header;
	in_addr_t value;
};

struct char32_entity {
		struct hpi_entity header;
		char value[32];
};

struct flag_entity {
	struct hpi_entity header;
	struct int_entity value;
	struct char32_entity classname;
	struct int_entity flags;
	struct int_constraints_entity limits;
};

struct net_addr_entity {
	struct hpi_entity header;
	struct ip4_entity value;
	struct char32_entity classname;
	struct int_entity flags;
};

struct subsys_option {
	int id;
	struct hpi_entity *info;
	struct hpi_entity *value;
};

#define DISCOVERY_TIMEOUT_SECONDS 0
#define DISCOVERY_TIMEOUT_MS      100
#define DEAD_THRESHOLD 10

/** global: List of adapters known by this HPI */
struct hpi_adapter *adapterListHead = NULL;
static int gnSocketCleanupRequired = 0;
static int gnSuspendDiscovery = 0;
static int gnSubsysLoaded = 0;
/* Use to look up the size of valid part of response for a given object type */
static uint8_t aResSize[HPI_OBJ_MAXINDEX + 1] = HPI_RESPONSE_SIZE_BY_OBJECT;

#define ent_hdr(st, et, er) {sizeof(struct st##_entity), entity_type_##et, entity_role_##er}

struct flag_entity enableNetworking = {
	ent_hdr(flag, sequence, parameter_port),
#if defined HPI_BUILD_MULTIINTERFACE && ! defined HPI_OS_LINUX
	{ent_hdr(int, int, value), 0},
#else
	{ent_hdr(int, int, value), 1},
#endif
	{ent_hdr(char32, cstring, classname), "Network Enabled"},
	{ent_hdr(int, int, flags), entity_flag_writeable | entity_flag_readable},
	{	ent_hdr(int_constraints, sequence, value_constraint),
		{ent_hdr(int_range, int, range), 0, 1, 1}
	},
};

struct net_addr_entity net_addr = {
	ent_hdr(net_addr, sequence, parameter_port),
	{ent_hdr(ip4, ip4_address, value), INADDR_ANY},
	{ent_hdr(char32, cstring, classname), "Network Address"},
	{ent_hdr(int, int, flags), entity_flag_writeable | entity_flag_readable}
};

struct net_addr_entity net_mask = {
	ent_hdr(net_addr, sequence, parameter_port),
	{ent_hdr(ip4, ip4_address, value), INADDR_ANY},
	{ent_hdr(char32, cstring, classname), "Network Mask"},
	{ent_hdr(int, int, flags), entity_flag_writeable | entity_flag_readable}
};

struct flag_entity enableBroadcastDiscovery = {
	ent_hdr(flag, sequence, parameter_port),
	{ent_hdr(int, int, value), 1},
	{ent_hdr(char32, cstring, classname), "UDP Broadcast Discovery Enabled"},
	{ent_hdr(int, int, flags), entity_flag_writeable | entity_flag_readable},
	{	ent_hdr(int_constraints, sequence, value_constraint),
		{ent_hdr(int_range, int, range), 0, 1, 1}
	},
};

struct flag_entity enableUnicastDiscovery = {
	ent_hdr(flag, sequence, parameter_port),
	{ent_hdr(int, int, value), 0},
	{ent_hdr(char32, cstring, classname), "UDP Unicast Discovery Enabled"},
	{ent_hdr(int, int, flags), entity_flag_writeable | entity_flag_readable},
	{	ent_hdr(int_constraints, sequence, value_constraint),
		{ent_hdr(int_range, int, range), 0, 1, 1}
	},
};

struct net_addr_entity disc_list = {
	ent_hdr(net_addr, sequence, parameter_port),
	{ent_hdr(ip4, ip4_address, value), INADDR_NONE},
	{ent_hdr(char32, cstring, classname), "UDP Adapter IP Add"},
	{ent_hdr(int, int, flags), entity_flag_writeable}
};

static struct subsys_option opts[] = {
	{HPI_SUBSYS_OPT_NET_ENABLE, &enableNetworking.header, &enableNetworking.value.header},
	{HPI_SUBSYS_OPT_NET_BROADCAST, &enableBroadcastDiscovery.header, &enableBroadcastDiscovery.value.header},
	{HPI_SUBSYS_OPT_NET_UNICAST, &enableUnicastDiscovery.header, &enableUnicastDiscovery.value.header},
	{HPI_SUBSYS_OPT_NET_ADDR, &net_addr.header, &net_addr.value.header},
	{HPI_SUBSYS_OPT_NET_MASK, &net_mask.header, &net_mask.value.header},
	{HPI_SUBSYS_OPT_NET_ADAPTER_ADDRESS_ADD, &disc_list.header, NULL}
};

/** Local prototypes */

// HPI type functions.
hpi_err_t SubsysOpen(void);
hpi_err_t SubsysClose(void);
hpi_err_t SubsysSetInterface(const char *szIF);
//static void SubsysCreateAdapter(struct hpi_message *phm, struct hpi_response *phr);
static void SubsysGetNumAdapters(struct hpi_message *phm, struct hpi_response *phr);
static hpi_err_t SubsysGetAdapter(int nIndex,
			    uint16_t *pwAdapterIndex, uint16_t *pwAdapterType);
static void SubsysOptionInfo(struct hpi_message *phm, struct hpi_response *phr);
static void SubsysOptionGet(struct hpi_message *phm, struct hpi_response *phr);
static void SubsysOptionSet(struct hpi_message *phm, struct hpi_response *phr);

static void AdapterOpen(struct hpi_message *phm, struct hpi_response *phr);
static void AdapterClose(struct hpi_message *phm, struct hpi_response *phr);
static int AdapterGetProperty(struct hpi_message *phm, struct hpi_response *phr,
		unsigned int timeout);

// utility functions
int HPINET_checkReceivePacket(struct hpi_adapter *pA, const int wFunction,
			      char *packet, struct hpi_response *phr);

unsigned int HPINET_createTransmitPacket(
	char **ppacket, ///< Pointer to buffer pointer. May be changed to phm for zero copy
	struct hpi_message *phm,	///< The HPI message structure that is to be transmitted.
	unsigned int packet_id
);

int HPINET_CheckSockError(const int nRet,
			  const TCHAR * szFn, struct hpi_response *phr);
static struct hpi_adapter *findAdapter(const uint16_t wAdapterIndex);
int CountAdapters(void);

int hpi_net_ex(struct hpi_adapter *pA,
	struct hpi_message *phm, struct hpi_response *phr, const unsigned int timeout);

void HPI_NET(struct hpi_message *phm,
	     struct hpi_response *phr, const unsigned int timeout_ms);

int HPINET_Message(struct hpi_adapter *pA,
		   struct hpi_message *phm,
		   struct hpi_response *phr,
		   const unsigned int nThisTimeout_ms);

/*****************************************************************************
* Discovery thread section.
*****************************************************************************/
#ifdef HPI_OS_WIN32_USER
struct sThread {
	HANDLE hThread;
	LPTHREAD_START_ROUTINE ThreadProc;
	DWORD ThreadID;
	HANDLE sem;
};
#else
#include <pthread.h>
struct sThread {
	pthread_t thread;
	pthread_mutex_t mutex;
	pthread_cond_t cond;
	int stop;
};
#endif

struct sThread discoveryThread;
int CreateDiscoveryThread(struct sThread *pThread);
int StopDiscoveryThread(struct sThread *pThread);

/*=================================================================*/

#if defined HPI_OS_LINUX || defined HPI_OS_OSX
#define MILLI_TO_NANO 1000000
void Sleep(int millisecs)
{
	struct timespec poll_millisecs;

	poll_millisecs.tv_sec = 0;
	poll_millisecs.tv_nsec = millisecs * MILLI_TO_NANO;
	nanosleep(&poll_millisecs, 0);
}
#endif

static int DiscoverySendUnicast(SOCKET sock, const void *buf, size_t len)
{
	int nSent;
	int failed = 0;
	int succeeded = 0;
	struct sockaddr_in destAdr;
	struct hpi_adapter *pA = adapterListHead;

	destAdr.sin_family = AF_INET;
	destAdr.sin_port = htons(HPI_ETHERNET_UDP_PORT);

	HPI_DEBUG_LOG0(DEBUG,"DiscoverySendUnicast: entry\n");
	while (pA) {
		HPI_DEBUG_LOG1(DEBUG,"DiscoverySendUnicast: loop 0x%x\n",pA->addr.s_addr);
		if (pA->discovery_source == HPI_DISCOVERY_SOURCE_MANUAL) {

			destAdr.sin_addr.s_addr = pA->addr.s_addr;

			// Write the packet
			HPI_DEBUG_LOG1(DEBUG,"MessageDiscoveryUnicast: Send msg using sendto(0x%x)\n",pA->addr.s_addr);
			nSent = sendto(sock,	// SOCKET s
					buf,	// const char* buf
					(int)len,	// int length
					0,	// int flags
					(SOCKADDR *) & destAdr,	// const struct sockaddr* to
					// socket address, seems weird to typecast it,
					// but MS does exactly this in their example.
					(int)sizeof(destAdr));	// int total length
			if (nSent < 0) {
				HPI_DEBUG_LOG0(ERROR, "Failed to send discovery message\n");
				failed++;
			} else
				succeeded++;

			/* This sleep is required because
			  * If Cobranet chip gets more than 2(?) ARP requests in
			    quick succession, later ones will be dropped.

			  * Windows may drop UDP packets if ARP request must be
			    made.  See http://support.microsoft.com/kb/233401
			*/
			Sleep(5);
		}
		pA = pA->next;
	}
	// error if some failed and none succeeded.
	return (failed && !succeeded);
}

static int DiscoverySendBroadcast(SOCKET sock, const void *buf, size_t len)
{
	struct sockaddr_in destAdr;
	int nSent, intBool;

	destAdr.sin_family = AF_INET;
	destAdr.sin_port = htons(HPI_ETHERNET_UDP_PORT);
	destAdr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
	// set broadcast permission
	intBool = 1;
	setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (void *)&intBool,
			    sizeof(intBool));
	//HPINET_CheckSockError(status, TEXT("setsockopt SO_BROADCAST"), phr);

	// Write the packet
	HPI_DEBUG_LOG0(DEBUG,"HPINET_MessageDiscovery:Send msg using sendto()\n");
	nSent = sendto(sock,	// SOCKET s
			buf,	// const char* buf
			(int)len,	// int length
			0,	// int flags
			(SOCKADDR *) &destAdr,	// const struct sockaddr* to
					// socket address, seems weird to typecast it,
					// but MS does exactly this in their example.
			(int)sizeof(destAdr));	// int total length

	return nSent;
}

/** Add an adapter to the discovered adapters list using the information in the HPI response buffer or,
	if the adapter already exists, return a pointer to it.

	This function populates the discovered adapters list and updates index, type,
	duplicate flag and 'dead' flag for existing adapters. The discovery list contains discovered
	adapters with an unique nonzero serial number and any number of adapters with zero-ed serial
	number field to indicate they are awaiting discovery.
*/
static struct hpi_adapter *AddAdapterToList(
	struct hpi_response *phr,
	in_addr_t ip_addr,
	uint16_t discovery_source)
{
	struct hpi_adapter *pA = adapterListHead;
	struct hpi_adapter *pAlast = adapterListHead;
	struct hpi_adapter *pFound = NULL;

	int adapters_with_same_index = 0;

	/* search list for existing adapter */
	while (pA) {
		/* adapter with no serial is placeholder awaiting discovery process */
		if ((pA->serialNumber == INVALID_SERIAL) ||
		    (phr->u.ax.info.dwSerialNumber == INVALID_SERIAL))
			goto next;

		// Update adapter attributes if there is a match
		if (phr->u.ax.info.dwSerialNumber == pA->serialNumber) {
			// IP address may have changed
			pA->adapterIndex = phr->u.ax.info.wAdapterIndex;
			pA->adapterType = phr->u.ax.info.wAdapterType;
			pA->addr.s_addr = ip_addr;
			pA->consecutiveLostResponses = 0;
			// Resurrect adapter *after* updating its attributes (adapter find happens in a separate thread)
			if (pA->isDead) {
				HPI_DEBUG_LOG2(INFO,
					"DISCOVER AddAdapterToList: (alive again) adapter %d with address %s\n",
					phr->u.ax.info.wAdapterIndex,
					inet_ntoa(pA->addr));
				// interface resurrected!
				pA->isDead = 0;
			}
			pFound = pA;
		}

		if (pA->adapterIndex == phr->u.ax.info.wAdapterIndex) {
			// Make this entry a duplicate if we encountered a live adapter with the same index before
			pA->isDuplicate = adapters_with_same_index > 0;
			if (adapters_with_same_index > 0)
				HPI_DEBUG_LOG3(DEBUG,"AddAdapterToList: adapter idx %d serial %d with address %x marked as duplicate\n",
					pA->adapterIndex, pA->serialNumber,
					ip_addr);
			// Do not count dead adapters as duplicates because we want the first live adapter
			// with this index to be usable
			if (!pA->isDead)
				adapters_with_same_index++;
		}

	next:
		pAlast = pA; // if adapter not found, this will point to last in list
		pA = pA->next;
	}

	if (pFound) {
		 // if adapter was found after a restart, its socket will have been closed
		if (!pFound->sock) {
			pFound->sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
			if (pFound->sock == INVALID_SOCKET) {
				HPI_DEBUG_LOG0(ERROR, "AddAdapterToList: Error opening socket\n");
				pFound->sock = 0;
			} else {
				struct hpi_message hm;
				struct hpi_response hr;
				HPI_InitMessageResponse(&hm, &hr,
					HPI_OBJ_ADAPTER, HPI_ADAPTER_OPEN);
				hm.wAdapterIndex = pFound->adapterIndex;
				HPINET_Message(pFound, &hm, &hr, HPI_ETHERNET_TIMEOUT_MS);
				pFound->adapterIsOpen = 1;
			}
		}
		return pFound;
	}

	// Create new entry
	pA = calloc(1, sizeof(*pA));
	if (!pA) {
		HPI_DEBUG_LOG0(ERROR, "AddAdapterToList: Failed to allocate memory for adapter\n");
		return NULL;
	}

	// Populate entry
	pA->discovery_source = discovery_source;
	pA->addr.s_addr = ip_addr;

	{
		HPI_DEBUG_LOG3(INFO,
			"DISCOVER - AddAdapterToList: adding Adapter index=%d, type=%04X, IP=%s\n",
			phr->u.ax.info.wAdapterIndex,
			phr->u.ax.info.wAdapterType,
			inet_ntoa(pA->addr));
	}

	/* Create socket for sending/receiving datagrams */
	if (!pA->sock)
		pA->sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (pA->sock == INVALID_SOCKET) {
		HPI_DEBUG_LOG0(ERROR, "AddAdapterToList: Error opening socket\n");
		pA->sock = 0;
	}

	if (phr->version == 0)
		pA->packet_id = HPI_ETHERNET_PACKET_ID;
	else
		pA->packet_id = 0;

	pA->macWord2 = HPIUDP_INVALID_MAC_WORD;
	pA->macWord1 = HPIUDP_INVALID_MAC_WORD;
	pA->macWord0 = HPIUDP_INVALID_MAC_WORD;
	pA->serialNumber = phr->u.ax.info.dwSerialNumber;
	pA->adapterIndex = phr->u.ax.info.wAdapterIndex;
	pA->adapterType = phr->u.ax.info.wAdapterType;
	pA->adapterIsOpen = 0;
	pA->hpi_message = HPI_NET;

	pA->isDuplicate = adapters_with_same_index > 0;
	if (adapters_with_same_index > 0)
		HPI_DEBUG_LOG3(DEBUG,"AddAdapterToList: adapter idx %d serial %d with address %x marked as duplicate\n",
			pA->adapterIndex, pA->serialNumber,	ip_addr);

	// Add to list
	if (pAlast)
		pAlast->next = pA;
	else
		adapterListHead = pA;

	// Clear error before returning
	phr->wError = 0;
	return pA;
}

static void ManuallyAddAdapter(in_addr_t ip_addr)
{
	struct hpi_response hr;
	struct hpi_adapter *pA;

	/* Placeholder until discovered */
	hr.u.ax.info.dwSerialNumber = INVALID_SERIAL;
	hr.u.ax.info.wAdapterIndex = 0;
	hr.u.ax.info.wAdapterType = 0;

	pA = AddAdapterToList(&hr, ip_addr, HPI_DISCOVERY_SOURCE_MANUAL);
	/* mark dead. If found in next scan, will get full info, and be marked alive */
	pA->isDead = 1;
	if (!enableUnicastDiscovery.value.value) {
		enableUnicastDiscovery.value.value = 1;
		HPI_DEBUG_LOG0(DEBUG, "Unicast discovery implicitly enabled\n");
	}

}

static int AddressUnreachable(in_addr_t ip_addr)
{
	// NOTE mask and addresses are in network order
	in_addr_t mask = net_mask.value.value;

	if (net_addr.value.value == INADDR_ANY)
		return 0;

	if( (net_addr.value.value & mask) != (ip_addr & mask) )
		return 1;
	return 0;
}

static void AdapterDeadCheck(struct hpi_adapter *pA, int nCloseOnDead)
{
	int was_dead = pA->isDead;
	if (pA->consecutiveLostResponses > DEAD_THRESHOLD) {
		/* mark as dead before removing the cache */
		pA->isDead = 1;
		if (nCloseOnDead) {
#ifdef HPIUDP_USE_CACHE
			/* remove control information */
			if (pA->mixerIsOpen)
				hpiudp_cache_free(&pA->cache);
#endif
			pA->mixerIsOpen = 0;
			pA->adapterIsOpen = 0;

			/* close the adapter socket */
			if (pA->sock) {
				closesocket(pA->sock);
				pA->sock = 0;
			}
		}

		if (!was_dead)
			HPI_DEBUG_LOG2(INFO,
				"DISCOVER HPINET_MessageDiscovery: DEAD - adapter %d with address %s\n",
				pA->adapterIndex, inet_ntoa(pA->addr));
	}
}


/*=================================================================*/
/** Discover all ASI products on the network. Runs once a second.
  */
int HPINET_MessageDiscovery(void)
{
	struct hpi_adapter *pA;
	struct hpi_message hm;
	struct hpi_response hr;
	struct sockaddr_in destAdr;
	char packet[HPINET_ASI_DATA_SIZE + sizeof(struct asi_pkt)];
	char *ppacket = packet;
	unsigned int packet_len;
	int num_adapters = 0;
	int rcv_buffer_size = 0;
	int status;
	int nSize;
	unsigned int nFromLen;
#if defined HPI_OS_LINUX || defined HPI_OS_OSX
	struct timeval timeout = {
		.tv_sec = DISCOVERY_TIMEOUT_SECONDS,
		.tv_usec = DISCOVERY_TIMEOUT_MS * 1000,
	};
#else
	int timeout = DISCOVERY_TIMEOUT_SECONDS * 1000 + DISCOVERY_TIMEOUT_MS;
#endif

	SOCKET sock;

	if (!enableUnicastDiscovery.value.value && !enableBroadcastDiscovery.value.value) {
		HPI_DEBUG_LOG0(DEBUG, "All forms of adapter discovery are disabled\n");
		return 0;
	}
	/* Create socket for sending/receiving datagrams */
	HPI_DEBUG_LOG0(DEBUG, "HPINET_MessageDiscovery: Opening socket\n");
	sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (sock == INVALID_SOCKET) {
		HPI_DEBUG_LOG0(ERROR, "Error opening socket\n");
		gnSocketCleanupRequired = 0;
		WSACleanup();	// terminate use of WS2_32.DLL
		return -1;
	}
	/* Increment consecutiveLostResponses. It will be reset if the adapter responds. */
	pA = adapterListHead;
	while (pA) {
		pA->consecutiveLostResponses++;
		pA = pA->next;
	}

	/* Bind the socket to an IP address ? */
	{
		/* bind it */
		struct sockaddr_in service;
		service.sin_family = AF_INET;
		service.sin_addr.s_addr = net_addr.value.value;
		service.sin_port = 0;
		status = bind(sock, (SOCKADDR *) &service, sizeof(service));
		HPINET_CheckSockError(status, TEXT("bind()"), &hr);
		HPI_DEBUG_LOG1(DEBUG, "Bind socket to %x\n",net_addr.value.value);
	}

	HPI_InitMessageResponse(&hm, &hr,
		HPI_OBJ_ADAPTER, HPI_ADAPTER_GET_INFO);

	/* Use an invalid adapter index for broadcast, distinguishes from valid
	unicast message at HPI level */
	hm.wAdapterIndex = HPI_ADAPTER_INDEX_INVALID;

	packet_len = HPINET_createTransmitPacket(&ppacket, &hm, HPI_ETHERNET_PACKET_ID);

	// Set up the destination address structure
	destAdr.sin_family = AF_INET;
	destAdr.sin_port = htons(HPI_ETHERNET_UDP_PORT);
	/*
	Future - ASI2416 does not pick up broadcast messages on a specific subnet

	broadcastAdr = ntohl(interface_in_addr);
	if(IN_CLASSA(broadcastAdr))
		broadcastAdr |= IN_CLASSA_HOST;
	else if (IN_CLASSB(broadcastAdr))
		broadcastAdr |= IN_CLASSB_HOST;
	else if (IN_CLASSC(broadcastAdr))
		broadcastAdr |= IN_CLASSC_HOST;
	else
	*/
	// set send timeout option
	status =  setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO,
				(void *)&timeout, sizeof(timeout));
	HPINET_CheckSockError(status, TEXT("setsockopt(SO_SNDTIMEO)"), &hr);

	// set receive timeout option
	status = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
				(void *)&timeout, sizeof(timeout));
	HPINET_CheckSockError(status, TEXT("setsockopt(SO_RCVTIMEO)"), &hr);

	// Set receive buffer size 128 bytes (larger than HPI GET_INFO response) * 1024 devices.
	// Note, by default Windows uses a size of 8K.
	rcv_buffer_size = 128 * 1024;
	status = setsockopt(sock, SOL_SOCKET, SO_RCVBUF,
		(void *)&rcv_buffer_size, sizeof(rcv_buffer_size));
	HPINET_CheckSockError(status, TEXT("setsockopt(SO_RCVBUF)"), &hr);

	if (enableUnicastDiscovery.value.value) {
		int bytes_sent;
		HPI_DEBUG_LOG0(DEBUG,"HPINET_MessageDiscovery: DiscoverySendUnicast()\n");
		bytes_sent = DiscoverySendUnicast(sock, ppacket, packet_len);
		if (bytes_sent < 0){
			HPINET_CheckSockError(status, TEXT("DiscoverySendUnicast()"), &hr);
			closesocket(sock);
			return HPINET_CheckSockError(status, TEXT("closesocket(sock)"), &hr);
		}
	}

	if (enableBroadcastDiscovery.value.value) {
		int bytes_sent;
		HPI_DEBUG_LOG0(DEBUG,"HPINET_MessageDiscovery: DiscoverySendBroadcast()\n");
		bytes_sent = DiscoverySendBroadcast(sock, ppacket, packet_len);
		if (bytes_sent < 0){
			HPINET_CheckSockError(status, TEXT("DiscoverySendBroadcast()"), &hr);
			closesocket(sock);
			return HPINET_CheckSockError(status, TEXT("closesocket(sock)"), &hr);
		}
	}

	while (1) {
		int serr;

		nFromLen = sizeof(destAdr);
		nSize = recvfrom(sock,			// SOCKET s
				(char *)packet,	// char * buffer
				sizeof(packet),	// int length
				0,			// int flags
				(SOCKADDR *) &destAdr,	// struct sockaddr*
					//will hold source addr upon return
				&nFromLen); 	// int * in,out size in bytes
						// of destAdr.
		HPI_DEBUG_LOG0(DEBUG,"HPINET_MessageDiscovery:Get msg using recvfrom()\n");

		// exit the while loop after we get the first timeout.
		serr = WSAGetLastError();
		if (serr == WSAETIMEDOUT) {
			HPI_DEBUG_LOG0(DEBUG,"HPINET_MessageDiscovery: TIMEOUT\n");
			break;
		}

		if (HPINET_CheckSockError(nSize, TEXT("recfrom()"), &hr)) {
			closesocket(sock);
			return -1;
		}

		memset(&hr,0,sizeof(hr));
		hr.wSize = sizeof(hr);
		if (HPINET_checkReceivePacket(NULL, hm.wFunction, packet, &hr)) {
			if(hr.wError == HPI_ERROR_RESPONSE_MISMATCH) {
				continue;
			} else {
				closesocket(sock);
				return -1;
			}
		}

		if (hr.u.ax.info.dwSerialNumber == INVALID_SERIAL) {
			HPI_DEBUG_LOG3(INFO,
				"HPINET_MessageDiscovery ERROR: Found adapter %d  with invalid (%x) serial at address %x\n",
				hr.u.ax.info.wAdapterIndex,
				INVALID_SERIAL,
				destAdr.sin_addr.s_addr);
			continue;
		}

		if (hr.u.ax.info.wAdapterType == 0) {
			HPI_DEBUG_LOG1(INFO,
				"HPINET_MessageDiscovery ERROR: Found adapter type 0x0 with address %x\n",
				destAdr.sin_addr.s_addr);
			continue;
		}

		if ( AddressUnreachable(destAdr.sin_addr.s_addr) ){
			HPI_DEBUG_LOG3(INFO,
				"HPINET_MessageDiscovery ERROR: Found adapter %d with unreachable address %x %x\n",
				hr.u.ax.info.wAdapterIndex,
				destAdr.sin_addr.s_addr,
				net_addr.value.value);
			continue;
		}

		HPI_DEBUG_LOG2(DEBUG, "HPINET_MessageDiscovery: found adapter %d with address %s\n",
			hr.u.ax.info.wAdapterIndex, inet_ntoa(destAdr.sin_addr));

		AddAdapterToList(&hr, destAdr.sin_addr.s_addr, HPI_DISCOVERY_SOURCE_BROADCAST);
		num_adapters++;
	}

	closesocket(sock);

	/* Check consecutiveLostResponses. Control information will be removed if
	 * threshold is exceeded. This forces the control discovery code
	 * to rerun when the ASI2416 is reconnected to the network.
	 */
#if 0
	pA = adapterListHead;
	while (pA) {
		AdapterDeadCheck(pA, 0);
		pA = pA->next;
	}
#endif

	/* returning with zero adapters is not an error */
	/*
	if (!num_adapters)
		return -1;
	*/

	return 0;
}

/*=================================================================*/
/** Set the network receive timeout
  */
static void setTimeout(
	struct hpi_adapter *pA,	///< Adapter information
	const int nTimeOut_ms ///< Timeout in milliseconds to wait for network packet
)
{
#if defined HPI_OS_LINUX || defined HPI_OS_OSX
	struct timeval timeout = {
		.tv_sec = nTimeOut_ms / 1000,
		.tv_usec = (nTimeOut_ms % 1000) * 1000,
	};
#else
	int timeout = nTimeOut_ms;
#endif

	setsockopt(pA->sock, SOL_SOCKET, SO_RCVTIMEO,
			(void *)&timeout, sizeof(timeout));
	setsockopt(pA->sock, SOL_SOCKET, SO_SNDTIMEO,
			(void *)&timeout, sizeof(timeout));
}

static void GrabMutex(struct hpi_adapter *pA)
{
	// Initialize or acquire the mutex
	if(pA->hSocketMutex)
		WaitForSingleObject(pA->hSocketMutex, INFINITE);
	else
		CreateMutexPtr(&pA->hSocketMutex,NULL,TRUE,TEXT("HPIUDP"));
}

/*=================================================================*/

/* future restructuring?:

if (cache not set up)
	CacheSetup()
if (cache set up)
	CacheConsult(msg,res)

if (control not cached) {
	MessageTxRx(msg,res)
	if (no error)
		CacheUpdate(msg,res)
*/

int HPINET_Message_Send(struct hpi_adapter *pA,	///< Adapter structure
		   struct hpi_message *phm	///< Message to send
		   )
{
	int nSent;
	char packet[HPINET_ASI_DATA_SIZE + sizeof(struct asi_pkt)];
	char *ppacket = packet;
	struct sockaddr_in destAdr;
	unsigned int packet_len;

	packet_len = HPINET_createTransmitPacket(&ppacket, phm, pA->packet_id);

	HPI_DEBUG_MESSAGE(DEBUG, phm);

	if (phm->wFunction == HPI_ADAPTER_START_FLASH)
		gnSuspendDiscovery = 20; /* seconds */

	destAdr.sin_family = AF_INET;
	destAdr.sin_port = htons(HPI_ETHERNET_UDP_PORT);
	destAdr.sin_addr.s_addr = pA->addr.s_addr;
	// Write the packet
	nSent = sendto(pA->sock,
			ppacket,
			packet_len,
			0,
			/* const struct sockaddr* to socket address,
			  seems weird to typecast it,
			  but MS does exactly this in their example. */
			(SOCKADDR *) &destAdr,
			(int)sizeof(destAdr));
	return nSent;
}


/** Send an HPI message via UDP and get a response back from the ASI2416.
  * All calling functions must set phr->wSize to a size that represents the
  * sizeof in bytes of the structure pointed to by phr.
  *
  */
int HPINET_Message(struct hpi_adapter *pA,	///< Adapter structure
		   struct hpi_message *phm,	///< Message to send
		   struct hpi_response *phr,	///< array of responses received
		   const unsigned int nThisTimeout_ms	///< timeout in milliseconds
    )
{
	int nCachingError = 0;
	int nRet;
	int nHandled = 0;

	if (pA->isDead) {
		phr->wError = HPI_ERROR_NETWORK_TIMEOUT;
		phr->wSize = aResSize[HPI_OBJ_ADAPTER];	// tweak the size to preserve error reporting.
		return -1;
	}

	setTimeout(pA, nThisTimeout_ms);

	GrabMutex(pA);

#ifdef HPIUDP_USE_CACHE
	if (!pA->isDead) {
		nRet =  hpiudp_cache_message(
				pA,
				pA->adapterIndex,
				&pA->mixerIsOpen,
				&pA->cache,
				&hpi_net_ex,
				phm, phr,
				&nHandled, &nCachingError);
		if (nHandled) {
			ReleaseMutex(pA->hSocketMutex);
			return nRet;
		}
	}
#endif

	/* Network Message/Response transaction with adapter */
	{
		int nSize,nSent;
		HPI_RESPONSEX hrx;
		char packet[HPINET_ASI_DATA_SIZE + sizeof(struct asi_pkt)];
		int rx_socket_error = -1;
		int rx_packet_error = -1;
		int rx_packet_error_timeout = 4;


		nSent = HPINET_Message_Send(pA,phm);

		if (HPINET_CheckSockError(nSent, TEXT("HPINET_Message_Send()"), phr)) {
			ReleaseMutex(pA->hSocketMutex);
			phr->wSize = sizeof(struct hpi_message_header);
			phr->wError = HPI_ERROR_NETWORK_TIMEOUT;
			return -1;
		}

		if ((phm->wFunction == HPI_ADAPTER_CLOSE)
		    && (phm->u.ax.restart.key2 == 0xdead)) {
			// we sent an adapter restart, no response will come
			HPI_InitResponse(phr, HPI_OBJ_ADAPTER, HPI_ADAPTER_CLOSE, 0);
			pA->isDead = 1;
			gnSuspendDiscovery = 0;
			ReleaseMutex(pA->hSocketMutex);
			return 0;
		}

		while( rx_packet_error && --rx_packet_error_timeout ) {
			nSize = recvfrom(pA->sock, packet, sizeof(packet),
				 0, NULL, NULL);

#ifdef HPI_OS_WIN32_USER
			/* add explicit test for timeout error here so we can dump ID and IP */
			if (nSize == WSAETIMEDOUT) {
				HPI_DEBUG_LOG5(INFO,
					"DISCOVER: HPIUDP MESG/RESP TIMEOUT index %d IP %d.%d.%d.%d\n",
					pA->adapterIndex,
					pA->addr.S_un.S_un_b.s_b1,
					pA->addr.S_un.S_un_b.s_b2,
					pA->addr.S_un.S_un_b.s_b3,
					pA->addr.S_un.S_un_b.s_b4);
			}
#endif

			rx_socket_error = HPINET_CheckSockError(nSize, TEXT("recvfrom()"), phr);
			if( rx_socket_error )
				break;

			memset(&hrx,0,sizeof(hrx));
			hrx.resp.wSize = sizeof(hrx);
			rx_packet_error = HPINET_checkReceivePacket(
							pA,
							phm->wFunction,
							packet,
							&hrx.resp);

			if( rx_packet_error && (hrx.resp.wError == HPI_ERROR_RESPONSE_MISMATCH) ) {
				continue;
			} else {
				break;
			}
		}

		if (rx_socket_error) {
			pA->consecutiveLostResponses++;
			AdapterDeadCheck(pA, 1);
			phr->wSize = sizeof(struct hpi_message_header);
			phr->wError = HPI_ERROR_NETWORK_TIMEOUT;
			ReleaseMutex(pA->hSocketMutex);
			return -1;
		} else {
			pA->isDead = 0;
			pA->consecutiveLostResponses = 0;
		}

		ReleaseMutex(pA->hSocketMutex);

		if (phm->wFunction == HPI_MIXER_OPEN)
			pA->mixerIsOpen = 1;

		if (!hrx.resp.wError) {
#ifdef HPIUDP_USE_CACHE
			// sync up the cache
			if ((phm->wFunction == HPI_CONTROL_SET_STATE)
				&& (pA->cache != NULL)) {
				HpiCmn_ControlCache_SyncToMsg(pA->cache->controlCache,
					phm, &hrx.resp);
			}
#endif
			if (nCachingError)
				hrx.resp.wError = (uint16_t)nCachingError;
		}
		// copy the large response buffer to the smaller one
		if (phr->wSize < hrx.resp.wSize) {
			phr->wSize = sizeof(struct hpi_message_header);
			phr->wError = HPI_ERROR_RESPONSE_BUFFER_TOO_SMALL;
			phr->wSpecificError = hrx.resp.wSize;
		} else
			memcpy(phr, &hrx, hrx.resp.wSize);
	}

	return 0;
}

/*=================================================================*/
/** Basic message processing function.
  *
  */
int hpi_net_ex(struct hpi_adapter *pA, struct hpi_message *phm, struct hpi_response *phr, const unsigned int timeout)
{
	uint16_t resp_size = phr->wSize;
	int sleep_in_milliseconds = 0;
	int status = 0;
	int i = 1;

	/*
	* Since HPIUDP sends and receives UDP messages, messages can
	* be lost. Furthermore, the UDP protocol does not have a built
	* in retry mechanism. Here we retry a couple of times before
	* giving up, based on the assumption that a lost packet in either
	* direction is due to a temporary resource limitation somewhere
	* in the communication path.
	*/

	rand();	/* refresh random number sequence often */

	status = HPINET_Message(pA, phm, phr, timeout);

	/*
	* Retry on network timeout (retry after random sleep time in the
	* range of 125..375 ms)
	*/
	while ((HPI_ERROR_NETWORK_TIMEOUT == phr->wError) && (i <= 2)) {
		sleep_in_milliseconds = 125 + (rand() % 250);
		Sleep(sleep_in_milliseconds);
		HPI_DEBUG_LOG3(DEBUG,
			"HPIUDP message Adap=%d timeout retry #%d in %d milliseconds\n",
			phm->wAdapterIndex, i, sleep_in_milliseconds);
		HPI_InitResponse(phr, phm->wObject, phm->wFunction, 0);
		phr->wSize = resp_size;
		status = HPINET_Message(pA, phm, phr, timeout);
		if (HPI_ERROR_NETWORK_TIMEOUT != phr->wError)
			HPI_DEBUG_LOG3(DEBUG, "HPIUDP message Adap=%d retry %d success wError %d\n", phm->wAdapterIndex, i, phr->wError);
		else
			HPI_DEBUG_LOG2(DEBUG, "HPIUDP message Adap=%d retry %d failed\n", phm->wAdapterIndex, i);
		i++;
	}

	return status;
}

void HPI_NET(struct hpi_message *phm, struct hpi_response *phr, const unsigned int timeout)
{
	struct hpi_adapter *pA;

	pA = findAdapter(phm->wAdapterIndex);

	if (pA) {
		hpi_net_ex(pA, phm, phr, timeout);
	} else
		HPI_DEBUG_LOG1(INFO, "Adapter %d not found\n", phm->wAdapterIndex);
}
/*=================================================================*/
/** Send extended HPI message.
  *
  * Extended message may include an appended data element.
  * and/or have a non-default timeout.
  *
  * phr->wSize must be set before calling this function, to indicate the actual
  * size available for the returned response.
  * If it is  zero, response size defaults to sizeof(struct hpi_response)
  */
#ifndef HPI_BUILD_MULTIINTERFACE
void HPI_MessageUDP(
	struct hpi_message *phm,
	struct hpi_response *phr,
	const unsigned int timeout)
{
	HPI_NET(phm, phr, timeout);
}
#endif

/*=================================================================*/
/** Main entry point into this module.
  */
#ifdef HPI_BUILD_MULTIINTERFACE
void HPI_EXPORT HPI_MessageUdp(struct hpi_message *phm, struct hpi_response *phr, unsigned int timeout)
#else
void HPI_Message(struct hpi_message *phm, struct hpi_response *phr)
#endif
{
	#ifdef HPI_BUILD_MULTIINTERFACE
	unsigned int local_timeout = timeout;
	#else
	unsigned int local_timeout = HPI_ETHERNET_TIMEOUT_MS;
	#endif

	HPI_DEBUG_MESSAGE(DEBUG, phm);

	if (phm->wObject == HPI_OBJ_SUBSYSTEM) {
		/* Subsys messages never get sent to adapters */
		switch (phm->wFunction) {
		case HPI_SUBSYS_OPEN:
			enableNetworking.value.value = 1;
			HPI_InitResponse(phr, phm->wObject, phm->wFunction,
				SubsysOpen());
			break;

		case HPI_SUBSYS_CLOSE:
			HPI_InitResponse(phr, phm->wObject, phm->wFunction,
				SubsysClose());
			break;

		case HPI_SUBSYS_SET_NETWORK_INTERFACE:
			enableNetworking.value.value = 1;
			SubsysOpen();
			HPI_InitResponse(phr, phm->wObject, phm->wFunction,
				SubsysSetInterface(phm->u.s.resource.r.net_if));
			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:
			if (enableNetworking.value.value) {
				SubsysOpen();
				SubsysGetNumAdapters(phm, phr);
			} else {
				HPI_InitResponse(phr, phm->wObject, phm->wFunction,
					HPI_ERROR_INVALID_OPERATION);
			}
			break;

		case HPI_SUBSYS_GET_ADAPTER:
			if (enableNetworking.value.value) {
				SubsysOpen();
			} else {
				HPI_InitResponse(phr, phm->wObject, phm->wFunction,
					HPI_ERROR_INVALID_OPERATION);
				break;
			}
			HPI_InitResponse(phr, phm->wObject, phm->wFunction, 0);
			phr->wError = SubsysGetAdapter(phm->wObjIndex, &phr->u.s.wAdapterIndex,
											&phr->u.s.wAdapterType);
			break;

		case HPI_SUBSYS_OPTION_INFO:
			SubsysOptionInfo(phm, phr);
			break;
		case HPI_SUBSYS_OPTION_GET:
			SubsysOptionGet(phm, phr);
			break;
		case HPI_SUBSYS_OPTION_SET:
			SubsysOptionSet(phm, phr);
			break;
		default:
			HPI_InitResponse(phr, phm->wObject,
				phm->wFunction, HPI_ERROR_INVALID_FUNC);

		}
		return;
	}

	if (enableNetworking.value.value) {
		SubsysOpen();
	} else {
		HPI_InitResponse(phr, phm->wObject, phm->wFunction,
			HPI_ERROR_INVALID_OPERATION);
		return;
	}

	switch (phm->wFunction) {
	case HPI_ADAPTER_OPEN:
		AdapterOpen(phm, phr);
		return;

	case HPI_ADAPTER_CLOSE:
		AdapterClose(phm, phr);
		return;

	case HPI_ADAPTER_GET_PROPERTY:
		if (AdapterGetProperty(phm, phr, local_timeout) == 0)
			return;
		else
			break;
#if 0
	case HPI_OSTREAM_WRITE:
		OstreamWrite(phm, phr, timeout);
		return;
	case HPI_ISTREAM_READ:
		IstreamRead(phm, phr, timeout);
		return;
#endif

	default:
		break;
	}

	/* timeout modification for mixer store messages */
	if ((phm->wObject == HPI_OBJ_MIXER) && (phm->wFunction == HPI_MIXER_STORE) )
		local_timeout = 10000; /* 10 seconds */

	HPI_NET(phm, phr, local_timeout);

	if (phr->wSize == 0) {	// This should not happen. If it does this is a coding error.
		// However, still fill in enough of the response to return an error to help debugging.
		phr->wError = HPI_ERROR_PROCESSING_MESSAGE;
		phr->wSize = sizeof(struct hpi_response_header);
	}

}

/*=================================================================*/
/** Call this to set the IP address.
  */
uint16_t SubsysSetInterface(const char *szInterfaceToUse)
{
	in_addr_t a = inet_addr(szInterfaceToUse);
	in_addr_t mask;

	if (a == INADDR_NONE)
		return HPI_ERROR_INVALID_CONTROL_VALUE;

	net_addr.value.value = a;
	HPI_DEBUG_LOG1(INFO, "SubsysSetInterface interface: %x\n", a);

	/* Classful  network  addresses  are  now  obsolete,  having been
	superseded by Classless Inter-Domain Routing (CIDR), which
	divides addresses into network and host components at arbitrary
	bit (rather than byte) boundaries. */

	if ((a & 128)== 0) // Class A   eg 10.0.0.0-10.255.255.255
		mask = 0x000000FF;
	else if ((a & 64)== 0) // Class B eg 172.16.0.0-172.16.255.255
		mask = 0x0000FFFF;
	else				// Class C eg 192.168.0.0-192.168.0.255
		mask = 0x00FFFFFF;

	net_mask.value.value = mask;
	HPI_DEBUG_LOG1(INFO, "SubsysSetInterface mask: %x\n", mask);

	return 0;
}

/*=================================================================*/
/** Local version of subsys open
Windows: initializes the winsock2 interface.
Linux: Reads environment config

Sends one foreground discovery broadcast, then starts the discovery thread.
*/
uint16_t SubsysOpen(void)
{
	if (gnSubsysLoaded)
		return 0;

	/* Seed the random number generator */
	srand((unsigned)time(NULL));

#if defined HPI_OS_WIN32_USER && defined HPI_BUILD_DEBUG
	HpiOs_Win32_Debug_Setup();
#endif
		
#ifdef HPI_OS_WIN32_USER
	// start the network interface if not already started.
	{
		WSADATA wsaData;
		int iResult = 0;
		// Initialize Winsock under windows.
		iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
		if (iResult != 0) {
			HPI_DEBUG_LOG1(ERROR, "WSAStartup failed: %d\n",
				       iResult);
			return HPI_ERROR_INVALID_OBJ;
		} else
			gnSocketCleanupRequired = 1;
	}
#endif

#if defined HPI_OS_LINUX || defined HPI_OS_OSX
	{
		char *l;
		int len, i = 0;
		l = getenv("LIBHPIUDP_ADAPTER_LIST");
		if (l)
			HPI_DEBUG_LOG1(NOTICE, "Adapter list from environment '%s'\n", l);
		if (l) {
			len = strlen(l);
			while (len) {
				for (i = 0; i < len-1; i++) {
					if (l[i] == ',') {
						l[i] = 0;
						break;
					}
				}
				ManuallyAddAdapter(inet_addr(l));
				l = &l[i+1];
				len = strlen(l);
			}

		}
	}
#endif

	HPINET_MessageDiscovery();
#ifdef HPIUDP_USE_DISCOVERY_THREAD
	CreateDiscoveryThread(&discoveryThread);
#endif

	gnSubsysLoaded = 1;
	return 0;
}

/*=================================================================*/
/** Close the subsystem just cleans up open adpaters and terminates winsock2.
*/
uint16_t SubsysClose(void)
{
	struct hpi_adapter *pA = adapterListHead;
	struct hpi_adapter *next;

	StopDiscoveryThread(&discoveryThread);
	while (pA) {
		next = pA->next;

		if (pA->adapterIsOpen) {
			/* close the socket */
#ifdef HPI_OS_WIN32_USER
			closesocket(pA->sock);
#else
			close(pA->sock);
#endif
		}
#ifdef HPIUDP_USE_CACHE
		hpiudp_cache_free(&pA->cache);
#endif
		/*
		 * Normally one would expect to close the adapter here,
		 * but we don't bother because it is an extra network transaction
		 * that has no effect on the adapter itself.
		 */

		free(pA);
		pA = next;
	}
	adapterListHead = NULL;
	if (gnSocketCleanupRequired) {
		gnSocketCleanupRequired = 0;
		WSACleanup();	// terminate use of WS2_32.DLL
	}
	gnSubsysLoaded = 0;
	return 0;
}

/*=================================================================*/
/** Return the number of accessible adapters i.e. Adapters with duplicate index or unreachable
  * adapters are not included in the count.
  */
static void SubsysGetNumAdapters(struct hpi_message *phm, struct hpi_response *phr)
{
	int nCount = 0;
	struct hpi_adapter *pA = adapterListHead;
	HPI_UNUSED(phm);

	while (pA) {
		// A valid entry must have a non-zero serial number.
		// Count entries that are not dead or a duplicate
		if ((pA->serialNumber != INVALID_SERIAL) && !pA->isDead && !pA->isDuplicate)
			++nCount;
		pA = pA->next;
	}

	HPI_InitResponse(phr, HPI_OBJ_SUBSYSTEM, HPI_SUBSYS_GET_NUM_ADAPTERS, 0);
	phr->u.s.wNumAdapters = (uint16_t)nCount;
}

/*=================================================================*/
/** Give an iterator index, return an adapter index and type.
  *
  * Adapters are ordered so that first all accessible adapters are iterated
  * returning zero as error code, then all live adapters with duplicate index
  * are iterated returning HPI_ERROR_DUPLICATE_ADAPTER_NUMBER, then all known
  * unreachable adapters are iterated returning HPI_ERROR_NETWORK_TIMEOUT.
  *
  * The number of accessible adapters is the same as the number of adapters
  * indicated by SubsysGetNumAdapter() and will be returned for iterator indexes
  * from 0 to SubsysGetNumAdapter() - 1.
  *
  * Calling this function with an iterator index >= to (accessible adapters +
  * duplicate adapters + unreachable adapters) will return HPI_ERROR_INVALID_OBJ_INDEX
  * to indicate the end of iteration.
  *
  */
hpi_err_t SubsysGetAdapter(int nIterIndex,
		uint16_t *pwAdapterIndex, uint16_t *pwAdapterType)
{
	int nCount = 0;
	struct hpi_adapter *pA = adapterListHead;

	// Enumerate adapters we can access (not dead, not duplicate)
	while (pA) {
		// A valid entry must have a non-zero serial number.
		// Skip entries that are dead or a duplicate
		if ((pA->serialNumber == INVALID_SERIAL) || pA->isDead || pA->isDuplicate) {
			pA = pA->next;
			continue;
		}
		if (nCount++ == nIterIndex) {
			*pwAdapterIndex = (uint16_t)pA->adapterIndex;
			*pwAdapterType = (uint16_t)pA->adapterType;
			return 0;
		}
		pA = pA->next;
	}

	// Start again from the beginning, this time enumerate duplicate adapters
	pA = adapterListHead;
	while (pA) {
		// A valid entry must have a non-zero serial number.
		// Skip entries that are dead, duplicate adapters are OK
		if ((pA->serialNumber == INVALID_SERIAL) || pA->isDead || !pA->isDuplicate) {
			pA = pA->next;
			continue;
		}
		if (nCount++ == nIterIndex) {
			*pwAdapterIndex = (uint16_t)pA->adapterIndex;
			*pwAdapterType = (uint16_t)pA->adapterType;
			return HPI_ERROR_DUPLICATE_ADAPTER_NUMBER;
		}
		pA = pA->next;
	}

	// Start again from the beginning, this time enumerate dead adapters
	pA = adapterListHead;
	while (pA) {
		// A valid entry must have a non-zero serial number.
		// Return only dead entries
		if ((pA->serialNumber == INVALID_SERIAL) || !pA->isDead) {
			pA = pA->next;
			continue;
		}
		if (nCount++ == nIterIndex) {
			*pwAdapterIndex = (uint16_t)pA->adapterIndex;
			*pwAdapterType = (uint16_t)pA->adapterType;
			return HPI_ERROR_NETWORK_TIMEOUT;
		}
		pA = pA->next;
	}

	return HPI_ERROR_INVALID_OBJ_INDEX;
}

#ifndef C99
#define C99(x) x
#endif

//#include "hpi_entity_print.c"

static int entity_to_in_addr(struct hpi_entity *message, 	in_addr_t *addr)
{
	if (message->type == entity_type_cstring) {
		struct char32_entity *string = (struct char32_entity *)message;
		*addr = inet_addr(string->value);
		if (*addr == INADDR_NONE)
			return HPI_ERROR_INVALID_CONTROL_VALUE;
	} else if (message->type == entity_type_ip4_address) {
		struct ip4_entity *ip4 = (struct ip4_entity *)message;
		*addr = ip4->value;
	} else if (message->type == entity_type_int) {
		struct int_entity *integer = (struct int_entity *)message;
		*addr = htonl(integer->value);
	} else
		return HPI_ERROR_ENTITY_TYPE_MISMATCH;

	return 0;
}

static void SubsysNetDiscoveryAdd(struct hpi_message *phm, struct hpi_response *phr)
{
	struct hpi_entity *message = (struct hpi_entity *)&phm->u;
	in_addr_t addr;

#ifdef _HPI_ENTITY_PRINT_C_
	printf("SubsysNetDiscovery %d:", phm->wObjIndex);
	hpi_entity_print(message, 0, 0, NULL);
	printf("\n");
#endif

	phr->wError = (uint16_t)entity_to_in_addr(message, &addr);
	if (phr->wError)
		return;

	HPI_DEBUG_LOG1(DEBUG, "SubsysNetDiscovery: Adding IP ( addr 0x%08x )\n", addr);
	ManuallyAddAdapter(addr);
	phr->wError = 0;
	HPI_DEBUG_LOG0(DEBUG, "SubsysNetDiscovery: exit\n");
}

static void SubsysOptionInfo(struct hpi_message *phm, struct hpi_response *phr)
{
	struct hpi_entity *entity;
	struct hpi_entity *response = (struct hpi_entity *)&phr->u;
	unsigned int esize, rsize, i;

	for (i = 0; i < ARRAY_SIZE(opts); i++) {
		if (opts[i].id == phm->wObjIndex)
			break;
	}
	if (i == ARRAY_SIZE(opts)) {
		phr->wError = HPI_ERROR_INVALID_OBJ_INDEX;
		return;
	}

	entity = opts[i].info;
	esize = entity->size;

	if (!esize) {
		phr->wError = HPI_ERROR_INVALID_OBJ_INDEX;
		return;
	}

	rsize = esize + sizeof(struct hpi_response_header);

	if (rsize > phr->wSize) {
		phr->wError = HPI_ERROR_RESPONSE_BUFFER_TOO_SMALL;
		phr->wSpecificError = (uint16_t)rsize;
		return;
	}

#ifdef _HPI_ENTITY_PRINT_C_
	printf("SubsysOptionInfo %d %d %d:", phm->wObjIndex, phr->wSize, rsize);
	hpi_entity_print(entity, 0, 0, NULL);
	printf("\n");
#endif

	memcpy(response, entity, esize);
	phr->wSize = (uint16_t)rsize;
	phr->wError = 0;
}

static void SubsysOptionGet(struct hpi_message *phm, struct hpi_response *phr)
{
	struct hpi_entity *entity;
	struct hpi_entity *response = (struct hpi_entity *)&phr->u;
	unsigned int esize, rsize, i;

	if (phm->wObjIndex == HPI_SUBSYS_OPT_NET_ADAPTER_ADDRESS_ADD) {
		phr->wError = HPI_ERROR_UNIMPLEMENTED;
		return;
	}

	for (i = 0; i < ARRAY_SIZE(opts); i++) {
		if (opts[i].id == phm->wObjIndex)
			break;
	}
	if (i == ARRAY_SIZE(opts)) {
		phr->wError = HPI_ERROR_INVALID_OBJ_INDEX;
		return;
	}

	entity = opts[i].value;
	esize = entity->size;

	if (!esize) {
		phr->wError = HPI_ERROR_INVALID_OBJ_INDEX;
		return;
	}

	rsize = esize + sizeof(struct hpi_response_header);

	if (rsize > phr->wSize) {
		phr->wError = HPI_ERROR_RESPONSE_BUFFER_TOO_SMALL;
		phr->wSpecificError = (uint16_t)rsize;
		return;
	}
#ifdef _HPI_ENTITY_PRINT_C_
	printf("SubsysOptionGet %d %d %d:", phm->wObjIndex, phr->wSize, rsize);
	hpi_entity_print(entity, 0, 0, NULL);
	printf("\n");
#endif

	memcpy(response, entity, esize);
	phr->wSize = (uint16_t)rsize;
	phr->wError = 0;
}

static void SubsysOptionSet(struct hpi_message *phm, struct hpi_response *phr)
{
	struct hpi_entity *message = (struct hpi_entity *)&phm->u;
	struct hpi_entity *entity;
	unsigned int i;

#ifdef _HPI_ENTITY_PRINT_C_
	printf("SubsysOptionSet %d:", phm->wObjIndex);
	hpi_entity_print(message, 0, 0, NULL);
	printf("\n");
#endif

	if (phm->wObjIndex == HPI_SUBSYS_OPT_NET_ADAPTER_ADDRESS_ADD) {
		SubsysNetDiscoveryAdd(phm, phr);
		return;
	}

	for (i = 0; i < ARRAY_SIZE(opts); i++) {
		if (opts[i].id == phm->wObjIndex)
			break;
	}
	if (i == ARRAY_SIZE(opts)) {
		phr->wError = HPI_ERROR_INVALID_OBJ_INDEX;
		return;
	}

	entity = opts[i].value;

	if (!entity) {
		phr->wError = HPI_ERROR_INVALID_FUNC;
		return;
	}


	if ( message->size != entity->size ) {
		phr->wError = HPI_ERROR_ENTITY_SIZE_MISMATCH;
		return;
	}

	if ( message->type != entity->type ) {
		phr->wError = HPI_ERROR_ENTITY_TYPE_MISMATCH;
		return;
	}

	memcpy(entity, message, message->size);
	phr->wError = 0;
}

/*=================================================================*/
/** Open an adapter. This creates a socket for talking to the specific adapter.
  *
  */
static void AdapterOpen(struct hpi_message *phm, struct hpi_response *phr)
{
	struct hpi_adapter *pA;

	pA = findAdapter(phm->wAdapterIndex);

	if (pA) {
		if(!pA->sock){
			/* Create socket for sending/receiving datagrams */
			pA->sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
			if (pA->sock == INVALID_SOCKET) {
				HPI_DEBUG_LOG0(ERROR, "Error opening socket\n");
				phr->wError = HPI_ERROR_NETWORK_TIMEOUT;
				return;
			}
		}

		HPINET_Message(pA, phm, phr, HPI_ETHERNET_TIMEOUT_MS);
		pA->adapterIsOpen = 1;

#ifdef HPIUDP_CACHE_SHARED_MEM
		{
			PSECURITY_DESCRIPTOR pSD;
			SECURITY_ATTRIBUTES sa;
			TCHAR szFilename[64];
			// Create cache for list of controls and control settings.
			/* security stuff copied from WINNT SDK */
			pSD = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR,
								SECURITY_DESCRIPTOR_MIN_LENGTH);
			if (pSD == NULL)
				return;
			if (!InitializeSecurityDescriptor
				(pSD, SECURITY_DESCRIPTOR_REVISION))
				return;
			// Add a NULL DACL to the security descriptor..
			if (!SetSecurityDescriptorDacl
				(pSD, TRUE, (PACL) NULL, FALSE)) {
				LocalFree((HLOCAL) pSD);
				return;
			}
			sa.nLength = sizeof(sa);
			sa.lpSecurityDescriptor = pSD;
			sa.bInheritHandle = TRUE;

			_stprintf(szFilename, TEXT("AsiUdpMapping_%d"),
				  pA->adapterIndex);
			pA->hFileMappedContext =
			    CreateFileMapping(INVALID_HANDLE_VALUE, &sa,
					PAGE_READWRITE, 0,
					 sizeof(struct
						hpiudp_cache_context),
					szFilename);
			if (pA->hFileMappedContext) {
				int nZero = 0;

				// if CreateFileMapping() returned ERROR_SUCCESS, this is the first open that
				// has occurred, so we need to set all the memory to zero.
				if (GetLastError() == ERROR_SUCCESS)
					nZero = 1;

				pA->cache =
				    MapViewOfFile(pA->hFileMappedContext,
						FILE_MAP_ALL_ACCESS, 0, 0,
						0);
				if (nZero)
					memset(pA->cache, 0,
						sizeof(struct
						hpiudp_cache_context));
			}
			LocalFree((HLOCAL) pSD);
		}

#endif

	} else {
		phr->wError = HPI_ERROR_INVALID_OBJ_INDEX;
		phr->wSize = sizeof(struct hpi_response_header);
	}
}

/*=================================================================*/
/** Close an adapter.
  *
  */
static void AdapterClose(struct hpi_message *phm, struct hpi_response *phr)
{
	struct hpi_adapter *pA;

	pA = findAdapter(phm->wAdapterIndex);
	if (pA && pA->adapterIsOpen) {
		HPINET_Message(pA, phm, phr, HPI_ETHERNET_TIMEOUT_MS);
		pA->adapterIsOpen = 0;

		/*
		 * Only close the socket mutex if we are not doing
		 * an adapter restart.
		 */
		if (phm->u.ax.restart.key1 != 0x0FF &&
			phm->u.ax.restart.key2 != 0xdead)
		{
			CloseHandle(pA->hSocketMutex);
			pA->hSocketMutex = 0;
		}
		closesocket(pA->sock);
		pA->sock = 0;
	} else {
		/* case where pA was not found */
		HPI_InitResponse(phr, HPI_OBJ_ADAPTER, HPI_ADAPTER_CLOSE, HPI_ERROR_INVALID_OBJ);
		if ( pA )
			/* pA found, but adapter not open */
			phr->wError = HPI_ERROR_OBJ_NOT_OPEN;
	}
}

static int AdapterGetProperty(struct hpi_message *phm, struct hpi_response *phr,
		unsigned int timeout)
{
	struct hpi_adapter *pA = findAdapter(phm->wAdapterIndex);
	if (!pA) {
		phr->wError = HPI_ERROR_INVALID_OBJ_INDEX;
		return 0;
	}
	HPI_InitResponse(phr, HPI_OBJ_ADAPTER, HPI_ADAPTER_GET_PROPERTY, 0);

	switch (phm->u.ax.property_set.wProperty) {
	case HPI_ADAPTER_PROPERTY_IP_ADDRESS:
		{
			uint32_t tmp = ntohl(pA->addr.s_addr);
			phr->u.ax.property_get.wParameter1 = (uint16_t)((tmp & 0xFFFF0000) >> 16);
			phr->u.ax.property_get.wParameter2 = (uint16_t)(tmp & 0x0000FFFF);
		}
		break;
	case HPI_ADAPTER_PROPERTY_MAC_ADDRESS_MSB:
		if ((pA->macWord2 == HPIUDP_INVALID_MAC_WORD) ||
			(pA->macWord1 == HPIUDP_INVALID_MAC_WORD)) {
			HPI_NET(phm, phr, timeout);
			if(phr->wError)
				break;
			if (phr->wSize == 0) {	// This should not happen. If it does this is a coding error.
				// However, still fill in enough of the response to return an error to help debugging.
				phr->wError = HPI_ERROR_PROCESSING_MESSAGE;
				phr->wSize = sizeof(struct hpi_response_header);
			} else {
				pA->macWord2 = phr->u.ax.property_get.wParameter1;
				pA->macWord1 = phr->u.ax.property_get.wParameter2;
			}
		} else {
			phr->u.ax.property_get.wParameter1 = pA->macWord2 & 0xffff;
			phr->u.ax.property_get.wParameter2 = pA->macWord1 & 0xffff;
		}
		break;
	case HPI_ADAPTER_PROPERTY_MAC_ADDRESS_LSB:
		if(pA->macWord0 == HPIUDP_INVALID_MAC_WORD) {
			HPI_NET(phm, phr, timeout);
			if(phr->wError)
				break;
			if (phr->wSize == 0) {	// This should not happen. If it does this is a coding error.
				// However, still fill in enough of the response to return an error to help debugging.
				phr->wError = HPI_ERROR_PROCESSING_MESSAGE;
				phr->wSize = sizeof(struct hpi_response_header);
			} else {
				pA->macWord0 = phr->u.ax.property_get.wParameter1;
			}
		} else {
			phr->u.ax.property_get.wParameter1 = pA->macWord0 & 0xffff;
			phr->u.ax.property_get.wParameter2 = 0;
		}
		break;
	default:
		return 1;
	}
	return 0;
}

/*=================================================================*/
/** Given an index, this function returns a pointer to the adapter structure.
  *
  */
struct hpi_adapter *findAdapter(const uint16_t wAdapterIndex)
{
	struct hpi_adapter *a;

	a = adapterListHead;
	while (a) {
		// A valid entry must have a non-zero serial number.
		// Return the first matcing entry that is not dead or a duplicate
		if ((a->serialNumber != INVALID_SERIAL) && a->adapterIndex == wAdapterIndex &&
		     !a->isDead && !a->isDuplicate)
			break;
		a = a->next;
	}

	return a;
}

/*=================================================================*/
#if 0
/* Stream objects. */
static void OstreamWrite(struct hpi_message *phm, struct hpi_response *phr,
		unsigned int timeout)
{
	unsigned int nIndex = 0;
	unsigned int nThisIteration = 0;
	unsigned int nToDo = 0;
	size_t uBufferCount = 0;
	unsigned int nBytesPerIteration = (HPINET_MSG_SIZEOF_DATA & ~0x3);
	HPI_RESPONSEX hrx;
	HPI_MESSAGEX hmx;
	struct hpi_adapter *pA = findAdapter(phm->wAdapterIndex);

	nToDo = phm->u.d.u.data.dwDataSize;

	GrabMutex(pA);
	while (nToDo) {
		nThisIteration = nToDo;
		if(nThisIteration >= nBytesPerIteration)
			nThisIteration = nBytesPerIteration;

		if(nToDo == nThisIteration)
			uBufferCount = 0xffff;

		memcpy(&hmx,phm,sizeof(struct hpi_message));
		memcpy(&hmx.data[0],&(phm->u.d.u.data.pbData[nIndex]),nThisIteration);
		hmx.msg.u.d.u.data.pbData = (uint8_t *)uBufferCount;
		hmx.msg.u.d.u.data.dwDataSize = nThisIteration;
		hmx.msg.wSize += (uint16_t)nThisIteration;
		hrx.resp.wSize = sizeof(hrx.resp);
		if(uBufferCount == 0xffff){ // last one
			/* do wait for a response */
			HPINET_Message(pA, &hmx.msg, &hrx.resp, timeout);
		} else {
			/* don't wait for a response */
			HPINET_Message_Send(pA, &hmx.msg);
			hrx.resp.wError = 0;
		}

		if( hrx.resp.wError)
			break;
		nToDo -= nThisIteration;
		nIndex += nThisIteration;
		uBufferCount++;
	}
	ReleaseMutex(pA->hSocketMutex);

	memcpy(phr,&hrx.resp,sizeof(struct hpi_response));
	return;
}

static void IstreamRead(struct hpi_message *phm, struct hpi_response *phr,
		unsigned int timeout)
{
	unsigned int nIndex = 0;
	unsigned int nThisIteration = 0;
	unsigned int nToDo = 0;
	unsigned int nBytesPerIteration = (HPINET_RESP_SIZEOF_DATA & ~0x3);
	HPI_RESPONSEX hrx;
	HPI_MESSAGEX hmx;

	nToDo = phm->u.d.u.data.dwDataSize;

	while (nToDo) {
		nThisIteration = nToDo;
		if(nThisIteration > nBytesPerIteration)
			nThisIteration = nBytesPerIteration;
		memcpy(&hmx,phm,sizeof(struct hpi_message));
		hmx.msg.u.d.u.data.dwDataSize = nThisIteration;
		hrx.resp.wSize = sizeof(hrx);
		HPI_NET(&hmx.msg, &hrx.resp, timeout);
		if( hrx.resp.wError)
			break;
		memcpy(&(phm->u.d.u.data.pbData[nIndex]),&hmx.data[0],nThisIteration);
		nToDo -= nThisIteration;
		nIndex += nThisIteration;
	}
	memcpy(phr,&hrx.resp,sizeof(struct hpi_response));
	return;
}
#endif
/*=================================================================*/
/** DriverOpen
  *
  */
#ifdef HPI_BUILD_MULTIINTERFACE
uint16_t HPI_EXPORT HPI_DriverOpenUdp(void)
#else
uint16_t HPI_DriverOpen(void)
#endif
{
#if defined HPI_OS_LINUX || defined HPI_OS_OSX
	char * psDebugLevel;

	psDebugLevel = getenv("LIBHPI_DEBUG_LEVEL");
	if ((psDebugLevel) && (*psDebugLevel >= '0') && (*psDebugLevel <= '9'))
		hpiDebugLevel = *psDebugLevel - '0';
#else
	//HPI_DebugLevelSet(HPI_DEBUG_LEVEL_DEBUG);
#endif
	return 1;
}

/*=================================================================*/
/** DriverClose
  *
  */
#ifdef HPI_BUILD_MULTIINTERFACE
void HPI_EXPORT HPI_DriverCloseUdp(void)
#else
void HPI_DriverClose(void)
#endif
{

}



/*=================================================================*/
/** Prepend ASI packet ID + version to HPI message (if ID is nonzero)
   \return Returns size of created packet
*/
unsigned int HPINET_createTransmitPacket(
	/** Points to buffer pointer.
	If packet_id==0, *packet is set to phm (zero copy)
	otherwise 2 byte packet id is copied **ppacket, followed by *phm
	*/
	char **ppacket,
	struct hpi_message *phm, ///< The HPI message structure that is to be transmitted.
	unsigned int packet_id ///< 2 byte packet ID
)
{
	unsigned int size;

#if 0
	// replace adapter index with sequence number for packet loss debug
	static int seq;
	phm->wAdapterIndex = seq++;
#endif
	if (!packet_id) {
		// return pointer to original message
		*ppacket = (char *)phm;
		size = phm->wSize;
	} else {
		struct asi_pkt *asi_info;

		asi_info = (struct asi_pkt *)*ppacket;
		// put extra bytes at start of buffer
		asi_info->ASIpktID = (uint8_t)packet_id;
		asi_info->ASIpktVersion = HPI_ETHERNET_PACKET_HOSTED_VIA_HMI_V1;
		// then append actual HPI message
		memcpy(*ppacket + sizeof(struct asi_pkt), phm, phm->wSize);	// copy the message across
		size = phm->wSize + sizeof(struct asi_pkt);
	}
	return size;
}

/*=================================================================*/
/** A utility function to check a received UDP packet to make
  * sure it came from an ASI client. It also copies the received data into
  * a response structure.
  * \return Returns 0 on succes, otherwise an error.
  */
int HPINET_checkReceivePacket(
	struct hpi_adapter *pA, ///< Adapter info
	const int wFunction, ///< The message function.
	char *packet,	///< The raw data received.
	struct hpi_response *phr	///< The response structure that will contain the response on return.
)
{
	struct hpi_response *phr_local;
	struct asi_pkt *asi_info = (struct asi_pkt *)packet;
	int hpi_ofs = 0;

	if ((asi_info->ASIpktID == HPI_ETHERNET_PACKET_ID) &&
	    (asi_info->ASIpktVersion ==	HPI_ETHERNET_PACKET_HOSTED_VIA_HMI_V1))	{
		hpi_ofs = 2;
	}

	phr_local = (struct hpi_response *)&packet[hpi_ofs];

	if(phr->wSize == 0) {
		HPI_DEBUG_LOG0(ERROR, "assert(phr->wSize!=0)\n");
		assert(phr->wSize != 0);
	}

	if (phr_local->wType == HPI_TYPE_REQUEST) {
		HPI_DEBUG_LOG0(DEBUG, "HPI request (from somewhere!) instead of response\n");
		phr->wError = HPI_ERROR_RESPONSE_MISMATCH;
		return -1;
	}

	if (phr_local->wType != HPI_TYPE_RESPONSE) {
		HPI_DEBUG_LOG1(DEBUG, "Invalid HPI packet type %d\n", phr_local->wType);
		phr->wError = HPI_ERROR_INVALID_RESPONSE;
		return -1;
	}

	if (pA) {
		pA->isDead = 0;
		pA->consecutiveLostResponses = 0;
	}

	if(phr_local->wSize > phr->wSize) {
		HPI_DEBUG_LOG2(ERROR, "response size %d > local buffer size %d\n",
			phr_local->wSize, phr->wSize);
		phr->wError = HPI_ERROR_RESPONSE_BUFFER_TOO_SMALL;
		phr->wSpecificError = phr_local->wSize;
		phr->wSize = sizeof(struct hpi_response_header);
		return -1;
	}

	memcpy(phr, &packet[hpi_ofs], phr_local->wSize);
	if (phr->wError) {
		HPI_DEBUG_LOG2(DEBUG, "HPI Error %d, %d\n", phr->wError, phr->wSpecificError);
	}
	if( wFunction != phr->wFunction ) {
		HPI_DEBUG_LOG2(INFO, "assert( wFunction (0x%x) == phr->wFunction (0x%x) )\n",
			wFunction, phr->wFunction);
		phr->wError = HPI_ERROR_RESPONSE_MISMATCH;
		return -1;
	}

	return 0;
}

/*=================================================================*/
/** Checks for a socket error code and logs a message.
  * \return Returns 0 on succes, otherwise an error.
  */
int HPINET_CheckSockError(const int nRet,	///< status return from the winsock2 function
	const TCHAR * szFn,			///< string to be embedded in the error output. Typically something like "sndto", ie the function that generated the error.
	struct hpi_response *phr			///< the HPI response structure
)
{
#if defined HPI_OS_LINUX || defined HPI_OS_OSX
	if (nRet < 0) {
		char *errstr = strerror(errno);

		HPI_DEBUG_LOG3(DEBUG, "Error %d, %s() gave %s.\n", errno, szFn, errstr);
		phr->wError = HPI_ERROR_NETWORK_TIMEOUT;
		HPI_DEBUG_LOG1(DEBUG, "HPI Error %d\n", phr->wError);
		errno = 0;
		return -1;
	}
#else
	if (nRet == SOCKET_ERROR) {
		int serr = WSAGetLastError();

		if (serr == WSAETIMEDOUT)
			HPI_DEBUG_LOG1(ERROR,"WinSock error, %s gave WSAETIMEDOUT > TIMEOUT.\n",
				szFn);
		else if (serr == WSAENOTSOCK)
			HPI_DEBUG_LOG1(ERROR,"WinSock error, %s gave WSAENOTSOCK > NOT A SOCKET.\n",
				szFn);
		else if (serr == WSAENOBUFS)
			HPI_DEBUG_LOG1(ERROR,"WinSock error, %s gave WSAENOBUFS > No buffer space available.\n",
				szFn);
		else
			HPI_DEBUG_LOG2(ERROR,"WinSock error, %s gave %d\n",
				szFn, serr);
		phr->wError = HPI_ERROR_NETWORK_TIMEOUT;
		phr->wSize = aResSize[HPI_OBJ_ADAPTER];	// tweak the size to preserve error reporting.
		// don't need to log the HPI error and the winsock error above.  One is enough 
		// HPI_DEBUG_LOG1(ERROR, " HPI Error %d\n", phr->wError);
		return -1;
	}
#endif
	return 0;
}

/*****************************************************************************
* Discovery thread section.
*****************************************************************************/
#ifdef HPIUDP_USE_DISCOVERY_THREAD
#ifdef HPI_OS_WIN32_USER
#define UDP_THREAD_NAME _T("HPIUDP_thread_exit")

/*
The thread function that does device discovery.
*/
DWORD DiscoveryThread(struct sThread *pThread)
{
	int rslt;

	while(1)
	{
		DWORD status=WaitForSingleObject(pThread->sem,1000);
		/* if sem becomes signalled, we need to exit */
		if (status == WAIT_OBJECT_0)
			ExitThread( TRUE );
		if (gnSuspendDiscovery == 0){
			rslt = HPINET_MessageDiscovery();
			if(rslt!=0)
				HPI_DEBUG_LOG1(ERROR, "HPINET_MessageDiscovery() returned %d.\n",rslt);
		}else{
			gnSuspendDiscovery--;
		}
	}
}

/*
Create the thread to discover UDP devices.
Return value of NULL indicates a failure.
*/
int CreateDiscoveryThread(struct sThread *pThread)
{
	discoveryThread.ThreadProc = (LPTHREAD_START_ROUTINE)DiscoveryThread;
	pThread->sem = CreateSemaphore(NULL,0,1,UDP_THREAD_NAME);
	pThread->hThread = CreateThread(
		NULL,		/* no security */
		0,		/* default stacksize */
		pThread->ThreadProc,	/* pointer to code */
		pThread,		/* parameters to pass */
		0,		/* default flags */
		&pThread->ThreadID);
	return (pThread->hThread)?1:0;
}
/*
Stop the thread used to discover UDP devices.
*/
int StopDiscoveryThread(struct sThread *pThread)
{
	DWORD status;
	int nReturn;
	ReleaseSemaphore(pThread->sem,1,NULL);
	status=WaitForSingleObject(pThread->hThread,10000);
	if (status==WAIT_OBJECT_0)
		nReturn = 1;
	else{
		nReturn = 0;	/* the thread did not close itself properly */
		HPI_DEBUG_LOG0(ERROR, "Stop thread wait timed out!  Must kill thread.  TFEE\n");
		TerminateThread(pThread->hThread,0);
	}
	CloseHandle(pThread->hThread);
	CloseHandle(pThread->sem);
	return nReturn;
}
#else
void *DiscoveryThread(void *arg)
{
	struct sThread *pThread = (struct sThread*)arg;
	//HPI_DEBUG_LOG0(DEBUG, "Discovery thread starting\n");
	while(1)
	{
		struct timeval now;
		struct timespec timeout;
		int retcode;

		pthread_mutex_lock(&pThread->mutex);
		gettimeofday(&now, NULL);
		timeout.tv_sec = now.tv_sec + 2;
		timeout.tv_nsec = now.tv_usec * 1000;
		retcode = 0;

		retcode = pthread_cond_timedwait(&pThread->cond, &pThread->mutex, &timeout);
		if (retcode == ETIMEDOUT) {
			if (gnSuspendDiscovery == 0){
				 if (enableNetworking.value.value)
					HPINET_MessageDiscovery();
				// returns -1 for all sorts of reasons, including "no adapters found" - not really an error here
			} else {
				gnSuspendDiscovery--;
			}
		} else {
		/* operate on data protected by mutex */
		}
		pthread_mutex_unlock(&pThread->mutex);

		if (pThread->stop)
			break;

	}
	return arg;
}

int CreateDiscoveryThread(struct sThread *pThread)
{
	pThread->stop = 0;
	pthread_mutex_init(&pThread->mutex, NULL);
	pthread_cond_init(&pThread->cond, NULL);
	pthread_create(&pThread->thread, NULL, DiscoveryThread, (void*)pThread);

	return 0;
}

int StopDiscoveryThread(struct sThread *pThread)
{
	pThread->stop = 1;
	pthread_cond_signal( &pThread->cond);
	pthread_join(pThread->thread, NULL);
	return 0;
}
#endif
#else
/* dummy discovery thread functions */
int CreateDiscoveryThread(struct sThread *pThread){return 0;};
int StopDiscoveryThread(struct sThread *pThread){return 0;};
int DiscoveryThread(struct sThread *pThread){return 0;};
#endif
