/*******************************************************************************//**
 * \file libsangoma.c
 * \brief Wanpipe API Code Library for Sangoma AFT T1/E1/Analog/BRI/Serial hardware
 *
 * Author(s):	Nenad Corbic <ncorbic@sangoma.com>
 *              David Rokhvarg <davidr@sangoma.com>
 *              Michael Jerris <mike@jerris.com>
 * 				Anthony Minessale II <anthmct@yahoo.com>
 *
 * Copyright:	(c) 2005-2008 Nenad Corbic <ncorbic@sangoma.com>
 *
 * * Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Sangoma Technologies nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Sangoma Technologies ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Sangoma Technologies BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *******************************************************************************
 */

#include "libsangoma-pvt.h"

#if defined(__WINDOWS__)
# include <setupapi.h>	/* SetupDiXXX() functions */
# include <initguid.h>	/* GUID instantination */
# include <devguid.h>	/* DEFINE_GUID() */
# include "public.h"	/* GUID_DEVCLASS_SANGOMA_ADAPTER */

# define MAX_COMP_INSTID	2096
# define MAX_COMP_DESC		2096
# define MAX_FRIENDLY		2096	
# define TMP_BUFFER_LEN		256

/*!+! jpboily used to tell get_out_flags no objects were signaled */
# define INVALID_INDEX (uint32_t) -1 
# define WP_ALL_BITS_SET ((uint32_t)-1)
#endif

int libsng_dbg_level = 0;

/*********************************************************************//**
 * WINDOWS Only Section
 *************************************************************************/

#if defined(__WINDOWS__)
#define	DBG_REGISTRY	if(libsng_dbg_level)libsng_dbg

/*
  \fn static void DecodeLastError(LPSTR lpszFunction)
  \brief Decodes the Error in radable format.
  \param lpszFunction error string

  Private Windows Only Function
 */
static void LibSangomaDecodeLastError(LPSTR lpszFunction) 
{ 
	LPVOID lpMsgBuf;
	DWORD dwLastErr = GetLastError();
	FormatMessage( 
		FORMAT_MESSAGE_ALLOCATE_BUFFER | 
		FORMAT_MESSAGE_FROM_SYSTEM | 
		FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL,
		dwLastErr,
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* Default language */
		(LPTSTR) &lpMsgBuf,
		0,
		NULL 
	);
	/* Display the string. */
	DBG_POLL("Last Error in %s(): %s (%d)\n", lpszFunction, (char*)lpMsgBuf, dwLastErr);
	/* Free the buffer. */
	LocalFree( lpMsgBuf );
} 

/*
  \fn static int handle_device_ioctl_result(int bResult)
  \brief Checks result code of ioctl
  \param bResult result of ioctl call

  Private Windows Only Function
 */
static u16 handle_device_ioctl_result(int bResult, char *caller_name)
{
	if(bResult == 0){
		/*error*/
		LibSangomaDecodeLastError(caller_name);
		return SANG_STATUS_IO_ERROR;
	}else{
		return SANG_STATUS_SUCCESS;
	}
}

/*
  \fn static int UdpManagementCommand(sng_fd_t fd, wan_udp_hdr_t* wan_udp)
  \brief Executes Driver Management Command
  \param fd device file descriptor
  \param wan_udp managemet cmd structure

  Private Windows Function
 */
int UdpManagementCommand(sng_fd_t fd, wan_udp_hdr_t* wan_udp)
{
	DWORD ln, bIoResult;
	unsigned char id = 0;

	wan_udp->wan_udphdr_request_reply = 0x01;
	wan_udp->wan_udphdr_id = id;
 	wan_udp->wan_udphdr_return_code = WAN_UDP_TIMEOUT_CMD;

	bIoResult = DeviceIoControl(
			fd,
			IoctlManagementCommand,
			(LPVOID)wan_udp,
			sizeof(wan_udp_hdr_t),
			(LPVOID)wan_udp,
			sizeof(wan_udp_hdr_t),
			(LPDWORD)(&ln),
			(LPOVERLAPPED)NULL
			);

	return handle_device_ioctl_result(bIoResult, __FUNCTION__);
}

/*
  \fn static int tdmv_api_ioctl(sng_fd_t fd, wanpipe_tdm_api_cmd_t *api_cmd)
  \brief Executes Driver TDM API Command Wrapper Function
  \param fd device file descriptor
  \param api_cmd tdm_api managemet cmd structure

  Private Windows Function
 */
static int tdmv_api_ioctl(sng_fd_t fd, wanpipe_tdm_api_cmd_t *api_cmd)
{
	DWORD ln, bIoResult;

	bIoResult = DeviceIoControl(
			fd,
			IoctlTdmApiCommand,
			(LPVOID)api_cmd,
			sizeof(wanpipe_tdm_api_cmd_t),
			(LPVOID)api_cmd,
			sizeof(wanpipe_tdm_api_cmd_t),
			(LPDWORD)(&ln),
			(LPOVERLAPPED)NULL
			);

	return handle_device_ioctl_result(bIoResult, __FUNCTION__);
}

/*
  \fn static USHORT DoReadCommand(sng_fd_t fd, RX_DATA_STRUCT * pRx)
  \brief  API READ Function
  \param drv device file descriptor
  \param pRx receive data structure

  Private Windows Function
  This function will NOT block because using IoctlReadCommandNonBlocking.
 */
static USHORT DoReadCommand(sng_fd_t fd, RX_DATA_STRUCT * pRx)
{
	DWORD ln, bIoResult;

	bIoResult = DeviceIoControl(
			fd,
			IoctlReadCommandNonBlocking,
			(LPVOID)NULL,/*NO input buffer!*/
			0,
			(LPVOID)pRx,
			sizeof(RX_DATA_STRUCT),
			(LPDWORD)(&ln),
			(LPOVERLAPPED)NULL);

	return handle_device_ioctl_result(bIoResult, __FUNCTION__);
}

/*
  \fn static UCHAR DoWriteCommand(sng_fd_t fd, TX_DATA_STRUCT * pTx)
  \brief API Write Function
  \param drv device file descriptor
  \param pRx receive data structure

  Private Windows Function
  In Legacy API mode this fuction will Block if data is busy.
  In API mode no function is allowed to Block
 */
static UCHAR DoWriteCommand(sng_fd_t fd,
							void *input_data_buffer, u32 size_of_input_data_buffer,
							void *output_data_buffer, u32 size_of_output_data_buffer
							)
{
	DWORD BytesReturned, bIoResult;

	bIoResult = DeviceIoControl(
			fd,
			IoctlWriteCommandNonBlocking,
			(LPVOID)input_data_buffer,
			size_of_input_data_buffer,
			(LPVOID)output_data_buffer,
			size_of_output_data_buffer,
			(LPDWORD)(&BytesReturned),
			(LPOVERLAPPED)NULL);

	return (UCHAR)handle_device_ioctl_result(bIoResult, __FUNCTION__);	
}

/*
  \fn static USHORT sangoma_poll_fd(sng_fd_t fd, API_POLL_STRUCT *api_poll_ptr)
  \brief Non Blocking API Poll function used to find out if Rx Data, Events or
			Free Tx buffer available
  \param drv device file descriptor
  \param api_poll_ptr poll device that stores polling information read/write/event
  \param overlapped pointer to system overlapped io structure.

  Private Windows Function
 */
static USHORT sangoma_poll_fd(sng_fd_t fd, API_POLL_STRUCT *api_poll_ptr)
{
	DWORD ln, bIoResult;

	bIoResult = DeviceIoControl(
			fd,
			IoctlApiPoll,
			(LPVOID)NULL,
			0L,
			(LPVOID)api_poll_ptr,
			sizeof(API_POLL_STRUCT),
			(LPDWORD)(&ln),
			(LPOVERLAPPED)NULL);

	return handle_device_ioctl_result(bIoResult, __FUNCTION__);
}

/*
  \fn static int logger_api_ioctl(sng_fd_t fd, wp_logger_cmd_t *logger_cmd)
  \brief Executes Logger API IOCTL
  \param fd device file descriptor
  \param logger_cmd Logger API command structure

  Private Windows Function
 */
static sangoma_status_t logger_api_ioctl(sng_fd_t fd, wp_logger_cmd_t *logger_cmd)
{
	DWORD ln, bIoResult;

	bIoResult = DeviceIoControl(
			fd,
			IoctlLoggerApiCommand,
			(LPVOID)logger_cmd,
			sizeof(wp_logger_cmd_t),
			(LPVOID)logger_cmd,
			sizeof(wp_logger_cmd_t),
			(LPDWORD)(&ln),
			(LPOVERLAPPED)NULL	);

	return handle_device_ioctl_result(bIoResult, __FUNCTION__);
}

/* This function is exported only for debugging purposes and NOT a part of API. */
sangoma_status_t _LIBSNG_CALL sangoma_cdev_ctrl_cmd(sng_fd_t fd, wanpipe_tdm_api_cmd_t *api_cmd)
{
	DWORD ln, bIoResult;

	bIoResult = DeviceIoControl(
			fd,
			IoctlCdevControlCommand,
			(LPVOID)api_cmd,
			sizeof(wanpipe_tdm_api_cmd_t),
			(LPVOID)api_cmd,
			sizeof(wanpipe_tdm_api_cmd_t),
			(LPDWORD)(&ln),
			(LPOVERLAPPED)NULL
			);

	return handle_device_ioctl_result(bIoResult, __FUNCTION__);
}

static sangoma_status_t init_sangoma_event_object(sangoma_wait_obj_t *sng_wait_obj, sng_fd_t fd)
{
	wanpipe_tdm_api_cmd_t tdm_api_cmd;
	sangoma_status_t sng_status;
   	char event_name[200];

	memset(&tdm_api_cmd, 0x00, sizeof(tdm_api_cmd));

	tdm_api_cmd.cmd = WP_CDEV_CMD_GET_INTERFACE_NAME;
	sng_status = sangoma_cdev_ctrl_cmd(fd, &tdm_api_cmd);
	if(SANG_ERROR(sng_status)){
		DBG_ERR("sangoma_cdev_ctrl_cmd() failed!\n");
		return sng_status;
	}

	DBG_EVNT("%s(): interface name: %s\n", __FUNCTION__, tdm_api_cmd.data);

	/* 1. The Sangoma Device Driver creates Notification Events in "\\BaseNamedObjects\\Global\\" 
	 * when first CreateFile() was called by a process. That means the Events will inherit
	 * the security attributes of the calling process, so the calling process will have
	 * the permissions to open the Events by calling OpenEvent(). Since Events are created
	 * "Global" subdirectory, the calling process does NOT need Administrator priveleges.
	 * The example name of the signaling object is: \\BaseNamedObjects\\Global\\wanpipe1_if1_signal.
	 * 2. The Events are deleted when the last HANDLE for a device is closed by CloseHandle()
	 * or automatically by the system when calling process exits. */
	_snprintf(event_name, sizeof(event_name), "Global\\%s_signal", tdm_api_cmd.data);

	sng_wait_obj->signal_object = OpenEvent(EVENT_ALL_ACCESS, TRUE, event_name);
	if(NULL == sng_wait_obj->signal_object){
		/* error */
		LibSangomaDecodeLastError(__FUNCTION__);
		return SANG_STATUS_GENERAL_ERROR;
	}

	return SANG_STATUS_SUCCESS;
}

static sangoma_status_t sangoma_wait_obj_poll(sangoma_wait_obj_t *sangoma_wait_object, int flags_in, uint32_t *flags_out)
{
	int err;
	sangoma_wait_obj_t *sng_wait_obj = sangoma_wait_object;
	 /*! api structure used by windows IoctlApiPoll call */
	API_POLL_STRUCT	api_poll;

	*flags_out = 0;

	memset(&api_poll, 0x00, sizeof(api_poll));
	api_poll.user_flags_bitmap = flags_in;

	/* This call is non-blocking - it will return immediatly. */
	if(sangoma_poll_fd(sng_wait_obj->fd, &api_poll)){
		/* error - ioctl failed */
		return SANG_STATUS_IO_ERROR;
	}

	if(api_poll.operation_status == SANG_STATUS_SUCCESS){
		*flags_out = api_poll.poll_events_bitmap;
		err = 0;
	}else{
		/* error - command failed */
		err = api_poll.operation_status;
	}

	if(*flags_out == 0){
		/*DBG_POLL("======%s(): *flags_out: 0x%X, flags_in: 0x%X\n", __FUNCTION__, *flags_out, flags_in);*/
	}
	return err;
}

static int sangoma_check_number_of_wait_objects(uint32_t number_of_objects)
{
	if(number_of_objects > MAXIMUM_WAIT_OBJECTS){
		DBG_ERR("'number_of_objects': %d is greater than the Maximum of: %d\n", 
			number_of_objects, MAXIMUM_WAIT_OBJECTS);
		return 1;
	}

	if(number_of_objects < 1){
		DBG_ERR("'number_of_objects': %d is less than the Minimum of: 1\n", 
			number_of_objects);
		return 1;
	}

	return 0;
}

static sangoma_status_t get_out_flags(IN sangoma_wait_obj_t *sng_wait_objects[],
									  IN uint32_t first_signaled_obj_index,
									  IN uint32_t in_flags[], OUT uint32_t out_flags[],
									  IN uint32_t number_of_sangoma_wait_objects,
									  OUT BOOL	*at_least_one_poll_set_flags_out)
{
	uint32_t i;

	if(at_least_one_poll_set_flags_out){
		*at_least_one_poll_set_flags_out = FALSE;
	}

	for(i = 0; i < number_of_sangoma_wait_objects; i++) {

		sangoma_wait_obj_t *sangoma_wait_object = sng_wait_objects[i];

		if (!sangoma_wait_object->signal_object) {
			return SANG_STATUS_DEV_INIT_INCOMPLETE;
		}

		if (!SANGOMA_OBJ_HAS_DEVICE(sangoma_wait_object)) {

			/* This object does not has a device, but, may have been signaled via sangoma_wait_obj_signal()
			 * test if the object is signaled, if it is, set SANG_WAIT_OBJ_IS_SIGNALED */

			if((i == first_signaled_obj_index)	||	/* !+! jpboily : Since WaitForMultipleObjects cleared the status
													 * of the FIRST signaled object, we make sure that the out_flag
													 * for this object is set.
													 * !+! davidr: Status of all OTHER objects will be checked
													 * by WaitForSingleObject(). */
				(WaitForSingleObject(sangoma_wait_object->signal_object, 0) == WAIT_OBJECT_0)) {
				out_flags[i] |= SANG_WAIT_OBJ_IS_SIGNALED;
			}
			continue;
		}

		if(sangoma_wait_obj_poll(sangoma_wait_object, in_flags[i], &out_flags[i])){
			return SANG_STATUS_GENERAL_ERROR;
		}

		if (out_flags[i] & in_flags[i]) {
			if (at_least_one_poll_set_flags_out) {
				*at_least_one_poll_set_flags_out = TRUE;
			}
		}

	}/* for(i = 0; i < number_of_sangoma_wait_objects; i++) */

	return SANG_STATUS_SUCCESS;
}

/*!
  \brief Brief description
 */
static LONG registry_get_string_value(HKEY hkey, LPTSTR szKeyname, OUT LPSTR szValue, OUT DWORD *pdwSize)
{
	LONG	iReturnCode;

	/* reading twice to set pdwSize to needed value */
	RegQueryValueEx(hkey, szKeyname, NULL, NULL, (unsigned char *)szValue, pdwSize);
	
	iReturnCode = RegQueryValueEx(hkey, szKeyname, NULL, NULL, (unsigned char *)szValue, pdwSize);
	if(ERROR_SUCCESS == iReturnCode){
		iReturnCode = 0;
	}
	DBG_REGISTRY("%s(): %s: %s: iReturnCode: %d\n", __FUNCTION__, szKeyname, szValue, iReturnCode);
	return iReturnCode;
}

/*!
  \brief Brief description
 */
LONG registry_set_string_value(HKEY hkey, LPTSTR szKeyname, IN LPSTR szValue)
{
	DWORD	dwSize;
	LONG	iReturnCode;

	dwSize = (DWORD)strlen(szValue) + 1;

    iReturnCode = RegSetValueEx(hkey, szKeyname, 0, REG_SZ, (unsigned char *)szValue, dwSize);
	DBG_REGISTRY("%s(): %s: %s\n", __FUNCTION__, szKeyname, szValue);

	if(ERROR_SUCCESS == iReturnCode){
		iReturnCode = 0;
	}
	return iReturnCode;
}

/*!
  \brief Convert an integer (iValue) to string and write it to registry
 */
LONG registry_set_integer_value(HKEY hkey, LPTSTR szKeyname, IN int iValue)
{
	DWORD	dwSize;
	char	szTemp[TMP_BUFFER_LEN];
	LONG	iReturnCode;

	sprintf(szTemp, "%u", iValue);

	dwSize = (DWORD)strlen(szTemp) + 1;
    iReturnCode = RegSetValueEx(hkey, szKeyname, 0, REG_SZ, (unsigned char *)szTemp, dwSize);

	if(ERROR_SUCCESS == iReturnCode){
		iReturnCode = 0;
	}

	DBG_REGISTRY("%s(): %s: %d: iReturnCode: %d\n", __FUNCTION__, szKeyname, iValue, iReturnCode);
	return iReturnCode;
}

/*!
 * \brief Go through the list of ALL "Sangoma Hardware Abstraction Driver" ports installed on the system.
 * Read Bus/Slot/Port information for a port and compare with what is searched for.
 */
HKEY registry_open_port_key(hardware_info_t *hardware_info)
{
	int				i, iRegistryReturnCode;
	SP_DEVINFO_DATA deid={sizeof(SP_DEVINFO_DATA)};
	HDEVINFO		hdi = SetupDiGetClassDevs((struct _GUID *)&GUID_DEVCLASS_SANGOMA_ADAPTER, NULL,NULL, DIGCF_PRESENT);
	char			DeviceName[TMP_BUFFER_LEN], PCI_Bus[TMP_BUFFER_LEN], PCI_Slot[TMP_BUFFER_LEN], Port_Number[TMP_BUFFER_LEN];
	DWORD			dwTemp;
	HKEY			hKeyTmp = (struct HKEY__ *)INVALID_HANDLE_VALUE;
	HKEY			hPortRegistryKey = (struct HKEY__ *)INVALID_HANDLE_VALUE;

	char    szCompInstanceId[MAX_COMP_INSTID];
	TCHAR   szCompDescription[MAX_COMP_DESC];
	DWORD   dwRegType;
	/* Possible Sangoma Port Names (refer to sdladrv.inf):
	Sangoma Hardware Abstraction Driver (Port 1)
	Sangoma Hardware Abstraction Driver (Port 2)
	Sangoma Hardware Abstraction Driver (Port 3)
	Sangoma Hardware Abstraction Driver (Port 4)
	Sangoma Hardware Abstraction Driver (Port 5)
	Sangoma Hardware Abstraction Driver (Port 6)
	Sangoma Hardware Abstraction Driver (Port 7)
	Sangoma Hardware Abstraction Driver (Port 8)
	Sangoma Hardware Abstraction Driver (Analog)
	Sangoma Hardware Abstraction Driver (ISDN BRI)	*/
	const TCHAR	*search_SubStr = "Sangoma Hardware Abstraction Driver";

	/* search for all (AFT) ports: */
	for (i = 0; SetupDiEnumDeviceInfo(hdi, i, &deid); i++){

		BOOL fSuccess = SetupDiGetDeviceInstanceId(hdi, &deid, (PSTR)szCompInstanceId, MAX_COMP_INSTID, NULL);
		if (!fSuccess){
			continue;
		}

		fSuccess = SetupDiGetDeviceRegistryProperty(hdi, &deid, SPDRP_DEVICEDESC, &dwRegType, (BYTE*)szCompDescription, MAX_COMP_DESC, NULL);
		if (!fSuccess){
			continue;
		}

		if (!strstr(szCompDescription, search_SubStr)) {	/* Windows can add #2 etc - do NOT consider */
			/* This is a "Sangoma Card" device, we are interested only in "Sangoma Port" devices. */
			continue;
		}

		DBG_REGISTRY("* %s\n", szCompDescription);

		hKeyTmp = SetupDiOpenDevRegKey(hdi, &deid, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_READ | KEY_SET_VALUE );
		if(hKeyTmp == INVALID_HANDLE_VALUE){
			DBG_REGISTRY("Error: Failed to open Registry key!!\n");
			continue;
		}

		PCI_Bus[0] = '\0';
		iRegistryReturnCode = registry_get_string_value(hKeyTmp, "PCI_Bus", PCI_Bus, &dwTemp);
		if(iRegistryReturnCode){
			continue;
		}

		PCI_Slot[0] = '\0';
		iRegistryReturnCode = registry_get_string_value(hKeyTmp, "PCI_Slot", PCI_Slot, &dwTemp);
		if(iRegistryReturnCode){
			continue;
		}

		Port_Number[0] = '\0';
		iRegistryReturnCode = registry_get_string_value(hKeyTmp, "Port_Number", Port_Number, &dwTemp);
		if(iRegistryReturnCode){
			continue;
		}

		if(	atoi(PCI_Bus)		== hardware_info->pci_bus_number	&&
			atoi(PCI_Slot)		== hardware_info->pci_slot_number	&&
			atoi(Port_Number)	== hardware_info->port_number		){

			hPortRegistryKey = hKeyTmp;

			/* read device name for debugging only */
			DeviceName[0] = '\0';
			registry_get_string_value(hPortRegistryKey, "DeviceName", DeviceName, &dwTemp);

			DBG_REGISTRY("Found Port's Registry key: DeviceName: %s, PCI_Bus: %s, PCI_Slot: %s, Port_Number: %s\n",
				DeviceName, PCI_Bus, PCI_Slot, Port_Number);
			break;
		}/* if() */
	}/* for() */

	SetupDiDestroyDeviceInfoList(hdi);

	DBG_REGISTRY("hPortRegistryKey: 0x%X\n", hPortRegistryKey);

	return hPortRegistryKey;
}

static char* timeslot_bitmap_to_string(int bitmap)
{
	int i = 0, range_counter = 0;
	int start_channel = -1, stop_channel = -1;
	static char tmp_string[256];

	if( WP_ALL_BITS_SET == bitmap ){
		return "ALL"; /* If all bits are set, use the "ALL" keyword. */
	}
	
	tmp_string[0] = '\0';

	/* all ranges between two zeros is what we will look for */
	for(i = 0; i < sizeof(bitmap) * 8; i++){

		if(start_channel < 0){
			/* found a starting one of a range */
			if(bitmap & (1 << i)){
				start_channel = i + 1;
			}
		}

		if(start_channel >= 0){
			if((bitmap & (1 << i)) == 0){
				
				/* we hit a zero, that means the previous channel is one */
				stop_channel = i;

			}else if(i == (sizeof(bitmap) * 8 - 1)){
				/* The most significant bit is set - there will be no delimiting zero.
				* It will also take care of "all channels" which is a special
				* case - there is a start but no stop channel because all bits
				* are set. result will be 1-32 */
				stop_channel = (sizeof(bitmap) * 8);
			}
		}

		if(start_channel >= 0 && stop_channel >= 0){

			if(range_counter){
				/* put '.' separator from the previous range */
				_snprintf(&tmp_string[strlen(tmp_string)], sizeof(tmp_string) - strlen(tmp_string), ".");
			}
							
			if(start_channel == stop_channel){
				/* the range contains a SINGLE channel */
				_snprintf(&tmp_string[strlen(tmp_string)], sizeof(tmp_string) - strlen(tmp_string),
					"%d", start_channel);
			}else{
				/* the range contains MULTIPLE channels */
				_snprintf(&tmp_string[strlen(tmp_string)], sizeof(tmp_string) - strlen(tmp_string),
					"%d-%d", start_channel, stop_channel);
			}

			start_channel = stop_channel = -1;
			range_counter++;
		}
	}/* for() */

	return tmp_string;
}

int registry_write_front_end_cfg(HKEY hPortRegistryKey, port_cfg_t *port_cfg)
{
	wandev_conf_t	*wandev_conf	= &port_cfg->wandev_conf;
	sdla_fe_cfg_t	*sdla_fe_cfg	= &wandev_conf->fe_cfg;
	sdla_te_cfg_t	*te_cfg			= &sdla_fe_cfg->cfg.te_cfg;
	sdla_remora_cfg_t *analog_cfg	= &sdla_fe_cfg->cfg.remora;
	sdla_bri_cfg_t	*bri_cfg		= &sdla_fe_cfg->cfg.bri;
	int iReturnCode = 0;

	/* Set Card/Port-wide values, which are independent of Media type, and
	 * stored in sdla_fe_cfg_t. */

	iReturnCode = registry_set_integer_value(hPortRegistryKey, "tdmv_law", FE_TDMV_LAW(sdla_fe_cfg) /* WAN_TDMV_MULAW/WAN_TDMV_ALAW */);
	if(iReturnCode){
		return iReturnCode;
	}

	iReturnCode = registry_set_integer_value(hPortRegistryKey, "ExternalSynchronization", FE_NETWORK_SYNC(sdla_fe_cfg) /* WANOPT_NO/WANOPT_YES */);
	if(iReturnCode){
		return iReturnCode;
	}

	iReturnCode = registry_set_integer_value(hPortRegistryKey, "FE_TXTRISTATE", FE_TXTRISTATE(sdla_fe_cfg) /* WANOPT_NO/WANOPT_YES */);
	if(iReturnCode){
		return iReturnCode;
	}

	iReturnCode = registry_set_integer_value(hPortRegistryKey, "HWEC_CLKSRC", wandev_conf->hwec_conf.clk_src /* is this port the HWEC clock source - WANOPT_NO/WANOPT_YES */);
	if(iReturnCode){
		return iReturnCode;
	}


	/* set Media specific values. */
	switch(sdla_fe_cfg->media)
	{
	case WAN_MEDIA_T1:
	case WAN_MEDIA_J1: /* the same as T1 */
	case WAN_MEDIA_E1:
		/* only T1/E1 Port can change the Media, all other ports ignore this parameter. */
		iReturnCode = registry_set_integer_value(hPortRegistryKey, "Media",		sdla_fe_cfg->media	/*WAN_MEDIA_T1*/);
		if(iReturnCode){
			break;
		}

		iReturnCode = registry_set_integer_value(hPortRegistryKey, "LDecoding",	sdla_fe_cfg->lcode	/*WAN_LCODE_B8ZS*/);
		if(iReturnCode){
			break;
		}

		iReturnCode = registry_set_integer_value(hPortRegistryKey, "Framing",		sdla_fe_cfg->frame	/*WAN_FR_ESF*/);
		if(iReturnCode){
			break;
		}

		iReturnCode = registry_set_integer_value(hPortRegistryKey, "ClkRefPort",		te_cfg->te_ref_clock);
		if(iReturnCode){
			break;
		}

		iReturnCode = registry_set_integer_value(hPortRegistryKey, "TE_IGNORE_YEL",	te_cfg->ignore_yel_alarm);
		if(iReturnCode){
			break;
		}

		iReturnCode = registry_set_integer_value(hPortRegistryKey, "ACTIVE_CH",		ENABLE_ALL_CHANNELS	/*must be hardcoded*/);
		if(iReturnCode){
			break;
		}

		iReturnCode = registry_set_integer_value(hPortRegistryKey, "TE_RBS_CH",		te_cfg->te_rbs_ch);	/*not used by DS chip code, only by PMC */
		if(iReturnCode){
			break;
		}

		iReturnCode = registry_set_integer_value(hPortRegistryKey, "LBO",				te_cfg->lbo			/*WAN_T1_LBO_0_DB*/);
		if(iReturnCode){
			break;
		}

		iReturnCode = registry_set_integer_value(hPortRegistryKey, "ClkMode",			te_cfg->te_clock	/*WAN_NORMAL_CLK*/);
		if(iReturnCode){
			break;
		}

		iReturnCode = registry_set_integer_value(hPortRegistryKey, "HighImpedanceMode",te_cfg->high_impedance_mode /*WANOPT_NO*/);
		if(iReturnCode){
			break;
		}

		iReturnCode = registry_set_integer_value(hPortRegistryKey, "TE_RX_SLEVEL",	te_cfg->rx_slevel	/*WAN_TE1_RX_SLEVEL_12_DB*/);
		if(iReturnCode){
			break;
		}

		/* write E1 signalling for both T1 and E1 */
		iReturnCode = registry_set_integer_value(hPortRegistryKey, "E1Signalling",	te_cfg->sig_mode	/*WAN_TE1_SIG_CCS*/);
		if(iReturnCode){
			break;
		}
		break;

	case WAN_MEDIA_56K:
		/* do nothing */
		iReturnCode = 0;
		break;

	case WAN_MEDIA_FXOFXS:
		/* Analog global (per-card) settings */
		iReturnCode = registry_set_string_value(hPortRegistryKey, "remora_fxo_operation_mode_name",	analog_cfg->opermode_name);
		if(iReturnCode){
			break;
		}

		iReturnCode = registry_set_integer_value(hPortRegistryKey, "RM_BATTTHRESH",		analog_cfg->battthresh);
		if(iReturnCode){
			break;
		}

		iReturnCode = registry_set_integer_value(hPortRegistryKey, "RM_BATTDEBOUNCE",	analog_cfg->battdebounce);
		if(iReturnCode){
			break;
		}

		iReturnCode = registry_set_integer_value(hPortRegistryKey, "RM_FXO_TAPPING",	analog_cfg->rm_mode);
		if(iReturnCode){
			break;
		}
		
		iReturnCode = registry_set_integer_value(hPortRegistryKey, "RM_FXO_TAPPING_OFF_HOOK_THRESHOLD",analog_cfg->ohthresh);
		if(iReturnCode){
			break;
		}

		iReturnCode = registry_set_integer_value(hPortRegistryKey, "RM_LCM", analog_cfg->rm_lcm);
		if(iReturnCode){
			break;
		}

		iReturnCode = registry_set_integer_value(hPortRegistryKey, "RM_FXSTXGAIN", analog_cfg->fxs_txgain);
		if(iReturnCode){
			break;
		}

		iReturnCode = registry_set_integer_value(hPortRegistryKey, "RM_FXSRXGAIN", analog_cfg->fxs_rxgain);
		if(iReturnCode){
			break;
		}

		iReturnCode = registry_set_integer_value(hPortRegistryKey, "RM_FXOTXGAIN", analog_cfg->fxo_txgain);
		if(iReturnCode){
			break;
		}

		iReturnCode = registry_set_integer_value(hPortRegistryKey, "RM_FXORXGAIN", analog_cfg->fxo_rxgain);
		if(iReturnCode){
			break;
		}
		break;

	case WAN_MEDIA_BRI:
		iReturnCode = registry_set_integer_value(hPortRegistryKey, "aft_bri_clock_mode", bri_cfg->clock_mode);
		if(iReturnCode){
			break;
		}
		break;

	case WAN_MEDIA_SERIAL:
		iReturnCode = registry_set_integer_value(hPortRegistryKey, "clock_source",			wandev_conf->clocking);
		if(iReturnCode){
			break;
		}

		iReturnCode = registry_set_integer_value(hPortRegistryKey, "BaudRate",				wandev_conf->bps);
		if(iReturnCode){
			break;
		}

		iReturnCode = registry_set_integer_value(hPortRegistryKey, "serial_connection_type",	wandev_conf->connection);
		if(iReturnCode){
			break;
		}

		iReturnCode = registry_set_integer_value(hPortRegistryKey, "serial_line_coding",		wandev_conf->line_coding);
		if(iReturnCode){
			break;
		}

		iReturnCode = registry_set_integer_value(hPortRegistryKey, "serial_line_idle",		wandev_conf->line_idle);
		if(iReturnCode){
			break;
		}
		break;

	default:
		DBG_ERR("Invalid Media Type %d!\n", sdla_fe_cfg->media);
		iReturnCode = 1;
	}

	return iReturnCode;
}

int registry_write_wan_tdmv_conf(HKEY hPortRegistryKey, port_cfg_t *port_cfg)
{
	wandev_conf_t	*wandev_conf	= &port_cfg->wandev_conf;
	sdla_fe_cfg_t	*sdla_fe_cfg	= &wandev_conf->fe_cfg;
	wan_tdmv_conf_t	*tdmv_conf		= &wandev_conf->tdmv_conf;
	int	iReturnCode = 1;

	/* set Media specific values. */
	switch(sdla_fe_cfg->media)
	{
	case WAN_MEDIA_T1:
	case WAN_MEDIA_J1: /* the same as T1 */
	case WAN_MEDIA_E1:
		/* write dchannel bitmap as a string (the same way as active channels) */
		iReturnCode = registry_set_string_value(hPortRegistryKey, "TDMV_DCHAN", timeslot_bitmap_to_string(tdmv_conf->dchan));
		break;

	case WAN_MEDIA_56K:
	case WAN_MEDIA_FXOFXS:
	case WAN_MEDIA_BRI:
	case WAN_MEDIA_SERIAL:
		/* do nothing */
		iReturnCode = 0;
		break;

	default:
		DBG_ERR("Invalid Media Type %d!\n", sdla_fe_cfg->media);
		iReturnCode = 2;
		break;
	}

	return iReturnCode;
}

int registry_write_channel_group_cfg(HKEY hPortRegistryKey, port_cfg_t *port_cfg, int interface_index, wanif_conf_t wanif_conf)
{
	char	szTemp[TMP_BUFFER_LEN];
	int		iReturnCode = 0;

	do{
		// Line mode
		_snprintf(szTemp, TMP_BUFFER_LEN, "aft_logic_channel_%d_line_mode", interface_index);
		iReturnCode = registry_set_string_value(hPortRegistryKey, szTemp, 
			(wanif_conf.hdlc_streaming == WANOPT_YES ? MODE_OPTION_HDLC : MODE_OPTION_BITSTRM));
		if(iReturnCode){
			break;
		}

		// MTU
		_snprintf(szTemp, TMP_BUFFER_LEN, "aft_logic_channel_%d_mtu", interface_index);
		iReturnCode = registry_set_integer_value(hPortRegistryKey, szTemp, wanif_conf.mtu);
		if(iReturnCode){
			break;
		}

		// Operational mode
		_snprintf(szTemp, TMP_BUFFER_LEN, "aft_logic_channel_%d_operational_mode", interface_index);
		iReturnCode = registry_set_string_value(hPortRegistryKey, szTemp, wanif_conf.usedby);
		if(iReturnCode){
			break;
		}

		// Active Timeslots for the Group
		_snprintf(szTemp, TMP_BUFFER_LEN, "aft_logic_channel_%d_active_ch", interface_index);
		iReturnCode = registry_set_string_value(hPortRegistryKey, szTemp, timeslot_bitmap_to_string(wanif_conf.active_ch));
		if(iReturnCode){
			break;
		}

		// Idle char.
		_snprintf(szTemp, TMP_BUFFER_LEN, "aft_logic_channel_%d_idle_char", interface_index);
		iReturnCode = registry_set_integer_value(hPortRegistryKey, szTemp, wanif_conf.u.aft.idle_flag);
		if(iReturnCode){
			break;
		}

	}while(0);

	return iReturnCode;
}

/*!
 * \brief Go through the list of ALL Sangoma PCI Adapters (Cards) on the system.
 * Enable or Disable each one of them (state change will be visible in the Device Manager).
 */
static sangoma_status_t sangoma_control_cards(DWORD StateChange)
{
	sangoma_status_t rc;
	int		i;
	SP_DEVINFO_DATA deid = {sizeof(SP_DEVINFO_DATA)};
	HDEVINFO	hdi;
	TCHAR	szInstanceId[MAX_COMP_INSTID];

	const TCHAR *szAftSearchSubStr = "PCI\\VEN_1923"; /* Sangoma PCI Vendor ID is 1923 */
	/*const TCHAR *szS518AdslSearchSubStr = "PCI\\VEN_14BC&DEV_D002";*/ /* Note: S518 is end-of-life */

	SP_PROPCHANGE_PARAMS	pcp;

	hdi = SetupDiGetClassDevs((struct _GUID *)&GUID_DEVCLASS_SANGOMA_ADAPTER, NULL, NULL, DIGCF_PRESENT);
	if(INVALID_HANDLE_VALUE == hdi){
		/* no sangoma cards installed on the system */
		return SANG_STATUS_INVALID_DEVICE;
	}

	/* search for all AFT Cards: */
	for (i = 0; SetupDiEnumDeviceInfo(hdi, i, &deid); i++){

		BOOL fSuccess = SetupDiGetDeviceInstanceId(hdi, &deid, (PSTR)szInstanceId, sizeof(szInstanceId), NULL);
		if (!fSuccess){
			rc = SANG_STATUS_REGISTRY_ERROR;
			break;
		}
		if(!strstr(szInstanceId, szAftSearchSubStr)){
			/* Found Sangoma Port, but we are interested only Cards. */
			continue;
		}

		memset(&pcp, 0x00, sizeof(pcp));
		//Set the PropChangeParams structure.
		pcp.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
		pcp.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
		pcp.Scope = DICS_FLAG_GLOBAL; /* DICS_FLAG_CONFIGSPECIFIC will enable ALL cards, including
									   * those explicetly DISABLED by the user in the Device Manager,
									   * and we don't want to force anything on the user, hence we
									   * use DICS_FLAG_GLOBAL. */
		pcp.StateChange = StateChange;

		if(!SetupDiSetClassInstallParams(hdi, &deid, (SP_CLASSINSTALL_HEADER *)&pcp, sizeof(pcp))){
			rc = SANG_STATUS_REGISTRY_ERROR;
			break;
		}

		if(!SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, hdi, &deid)){
			rc = SANG_STATUS_REGISTRY_ERROR;
			break;
		}

	}/* for() */

	SetupDiDestroyDeviceInfoList(hdi);

	return SANG_STATUS_SUCCESS;
}

sangoma_status_t _LIBSNG_CALL sangoma_unload_driver()
{
	return sangoma_control_cards(DICS_DISABLE);
}

sangoma_status_t _LIBSNG_CALL sangoma_load_driver()
{
	return sangoma_control_cards(DICS_ENABLE);
}

/*!
 * \brief Go through the list of ALL "Sangoma Hardware Abstraction Driver" ports installed on the system,
 * and assign sequential numbers to the Ports, starting from 1. This will override the Automatic Wanpipe
 * Span numbering.
 */
void _LIBSNG_CALL sangoma_reset_port_numbers()
{
	int				i, iRegistryReturnCode, iPortCounter = 0;
	SP_DEVINFO_DATA deid={sizeof(SP_DEVINFO_DATA)};
	HDEVINFO		hdi = SetupDiGetClassDevs((struct _GUID *)&GUID_DEVCLASS_SANGOMA_ADAPTER, NULL,NULL, DIGCF_PRESENT);
	HKEY			hKeyTmp = (struct HKEY__ *)INVALID_HANDLE_VALUE;

	char    szCompInstanceId[MAX_COMP_INSTID];
	TCHAR   szCompDescription[MAX_COMP_DESC];
	DWORD   dwRegType;
	/* Possible Sangoma Port Names (refer to sdladrv.inf):
	Sangoma Hardware Abstraction Driver (Port 1)
	Sangoma Hardware Abstraction Driver (Port 2)
	Sangoma Hardware Abstraction Driver (Port 3)
	Sangoma Hardware Abstraction Driver (Port 4)
	Sangoma Hardware Abstraction Driver (Port 5)
	Sangoma Hardware Abstraction Driver (Port 6)
	Sangoma Hardware Abstraction Driver (Port 7)
	Sangoma Hardware Abstraction Driver (Port 8)
	Sangoma Hardware Abstraction Driver (Analog)
	Sangoma Hardware Abstraction Driver (ISDN BRI)	*/
	const TCHAR	*search_SubStr = "Sangoma Hardware Abstraction Driver";

	/* search for all (AFT) ports: */
	for (i = 0; SetupDiEnumDeviceInfo(hdi, i, &deid); i++){

		BOOL fSuccess = SetupDiGetDeviceInstanceId(hdi, &deid, (PSTR)szCompInstanceId, MAX_COMP_INSTID, NULL);
		if (!fSuccess){
			continue;
		}

		fSuccess = SetupDiGetDeviceRegistryProperty(hdi, &deid, SPDRP_DEVICEDESC, &dwRegType, (BYTE*)szCompDescription, MAX_COMP_DESC, NULL);
		if (!fSuccess){
			continue;
		}

		if (!strstr(szCompDescription, search_SubStr)) {	/* Windows can add #2 etc - do NOT consider */
			/* This is a "Sangoma Card" device, we are interested only in "Sangoma Port" devices. */
			continue;
		}

		hKeyTmp = SetupDiOpenDevRegKey(hdi, &deid, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_READ | KEY_SET_VALUE );
		if(hKeyTmp == INVALID_HANDLE_VALUE){
			DBG_REGISTRY("Error: Failed to open Registry key!!\n");
			continue;
		}

		iPortCounter++;

		printf("* %s -> Setting Span/Port number: %d\n", szCompDescription, iPortCounter);

		/* TODO: 1. search for installed Sangoma Cards
				2. for each Card, search Ports (Bus/Slot must match)
				3. based on number of installed Ports, set SerialNumbersRange of the Card
				
				This way the WP_REGSTR_USER_SPECIFIED_WANPIPE_NUMBER can be set to zero and Span numbers will be
				sequencial automatically.
		*/
		iRegistryReturnCode = registry_set_integer_value(hKeyTmp, WP_REGSTR_USER_SPECIFIED_WANPIPE_NUMBER, iPortCounter);
		if(iRegistryReturnCode){
			continue;
		}

		RegCloseKey(hKeyTmp);

	}/* for() */

	SetupDiDestroyDeviceInfoList(hdi);

	return;
}


/*!
 * \brief Get version of driver from the Windows Registry, as it was written by .inf parser.
 *		Note that there is a possibility that currently loaded driver has a different
 *		version (if the original binary file was overwritten).
 */
sangoma_status_t _LIBSNG_CALL sangoma_get_driver_version_from_registry(char *out_buffer, int out_buffer_length)
{
	int				i, iRegistryReturnCode;
	SP_DEVINFO_DATA deid = {sizeof(SP_DEVINFO_DATA)};
	HDEVINFO		hdi = SetupDiGetClassDevs((struct _GUID *)&GUID_DEVCLASS_SANGOMA_ADAPTER, NULL, NULL, DIGCF_PRESENT);
	DWORD			dwTemp;
	HKEY			hKeyTmp = (struct HKEY__ *)INVALID_HANDLE_VALUE;

	TCHAR	szTmp[MAX_COMP_DESC];
	BOOL	fSuccess = FALSE;

	/* search for ANY Sangoma device of class GUID_DEVCLASS_SANGOMA_ADAPTER */
	for (i = 0; SetupDiEnumDeviceInfo(hdi, i, &deid); i++){

		fSuccess = SetupDiGetDeviceInstanceId(hdi, &deid, (PSTR)szTmp, sizeof(szTmp), NULL);
		if (!fSuccess){
			break;
		}
		DBG_REGISTRY("%s(): Device Instance Id: %s\n", __FUNCTION__, szTmp);
		/*	Device Instance Id: COMMSADAPTER\SANGOMAADAPTER_AFT_LINE8\5&32E1B75F&0&13
			Device Instance Id: PCI\VEN_1923&DEV_0100&SUBSYS_4113A114&REV_00\4&31B6CD7&0&10F0 	*/

		hKeyTmp = SetupDiOpenDevRegKey( hdi, &deid, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_READ | KEY_SET_VALUE );
		if(hKeyTmp == INVALID_HANDLE_VALUE){
			DBG_REGISTRY("Error: Failed to open Registry key!!\n");
			fSuccess = FALSE;
			break;
		}

		iRegistryReturnCode = registry_get_string_value(hKeyTmp, "DriverVersion", szTmp, &dwTemp);
		if(iRegistryReturnCode){
			fSuccess = FALSE;
			break;
		}

		wp_snprintf(out_buffer, out_buffer_length, szTmp);

		RegCloseKey(hKeyTmp);

		/* it is enough to read the version only once, so break here */
		break; /* breaking here will generate "warning C4702: unreachable code" - it is ok */
	}/* for() */

	SetupDiDestroyDeviceInfoList(hdi);

	if (!fSuccess){
		return SANG_STATUS_REGISTRY_ERROR;
	}else{
		return SANG_STATUS_SUCCESS;
	}
}

/*!
 * \brief Set the Driver Mode of all Hardware (not virtual) devices.
 *		The modes are:	Normal
 *						Firmware Update
 */
sangoma_status_t _LIBSNG_CALL sangoma_set_driver_mode_of_all_hw_devices(int driver_mode)
{
	sangoma_status_t rc;
	int		i, iRegistryReturnCode;
	SP_DEVINFO_DATA deid = {sizeof(SP_DEVINFO_DATA)};
	HDEVINFO	hdi;
	TCHAR	szInstanceId[MAX_COMP_INSTID];
	HKEY	hKeyTmp = (struct HKEY__ *)INVALID_HANDLE_VALUE;

	const TCHAR *szAftSearchSubStr = "PCI\\VEN_1923"; /* Sangoma PCI Vendor ID is 1923 */

	hdi = SetupDiGetClassDevs((struct _GUID *)&GUID_DEVCLASS_SANGOMA_ADAPTER, NULL, NULL, DIGCF_PRESENT);
	if(INVALID_HANDLE_VALUE == hdi){
		/* no sangoma cards installed on the system */
		return SANG_STATUS_INVALID_DEVICE;
	}

	/* search for all AFT Cards: */
	for (i = 0; SetupDiEnumDeviceInfo(hdi, i, &deid); i++){

		BOOL fSuccess = SetupDiGetDeviceInstanceId(hdi, &deid, (PSTR)szInstanceId, sizeof(szInstanceId), NULL);
		if (!fSuccess){
			rc = SANG_STATUS_REGISTRY_ERROR;
			break;
		}
		if(!strstr(szInstanceId, szAftSearchSubStr)){
			/* Found Sangoma Port, but we are interested only Cards. */
			continue;
		}

		hKeyTmp = SetupDiOpenDevRegKey(hdi, &deid, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_READ | KEY_SET_VALUE );
		if(hKeyTmp == INVALID_HANDLE_VALUE){
			rc = SANG_STATUS_REGISTRY_ERROR;
			break;
		}

		iRegistryReturnCode = registry_set_integer_value(hKeyTmp, "driver_mode", driver_mode);
		if(iRegistryReturnCode){
			rc = SANG_STATUS_REGISTRY_ERROR;
			break;
		}

		RegCloseKey(hKeyTmp);

	}/* for() */

	SetupDiDestroyDeviceInfoList(hdi);

	return SANG_STATUS_SUCCESS;
}


#endif	/* __WINDOWS__ */


/*********************************************************************//**
 * Common Linux & Windows Code
 *************************************************************************/

/*************************************************/
/* private functions for accessing wan_udp_hdr_t */
/*************************************************/
/* return POINTER to DATA at offset 'off' */
static unsigned char* sangoma_get_wan_udphdr_data_ptr(wan_udp_hdr_t *wan_udp_ptr, unsigned char off)
{
	unsigned char *p_data = &wan_udp_ptr->wan_udphdr_data[0];
	p_data += off;
	return p_data;
}

/* set a single byte of DATA at offset 'off' */
static unsigned char sangoma_set_wan_udphdr_data_byte(wan_udp_hdr_t *wan_udp_ptr, unsigned char off, unsigned char data)
{
	unsigned char *p_data = &wan_udp_ptr->wan_udphdr_data[0];
	p_data[off] = data;
	return 0;
}

/* return a single byte of DATA at offset 'off' */
static unsigned char sangoma_get_wan_udphdr_data_byte(wan_udp_hdr_t *wan_udp_ptr, unsigned char off)
{
	unsigned char *p_data = &wan_udp_ptr->wan_udphdr_data[0];
	return p_data[off];
}
/*************************************************/


/************************************************************//**
 * Device POLL Functions
 ***************************************************************/ 

/*!
  \fn sangoma_status_t sangoma_wait_obj_create(sangoma_wait_obj_t **sangoma_wait_object, sng_fd_t fd, sangoma_wait_obj_type_t object_type)
  \brief Create a wait object that will be used with sangoma_waitfor_many() API
  \param sangoma_wait_object pointer a single device object 
  \param fd device file descriptor
  \param object_type type of the wait object. see sangoma_wait_obj_type_t for types
  \see sangoma_wait_obj_type_t
  \return SANG_STATUS_SUCCESS: success, or error status
*/
sangoma_status_t _LIBSNG_CALL sangoma_wait_obj_create(sangoma_wait_obj_t **sangoma_wait_object, sng_fd_t fd, sangoma_wait_obj_type_t object_type)
{
	int err = 0;
	sangoma_wait_obj_t *sng_wait_obj = NULL;

	if (!sangoma_wait_object) { 
		return SANG_STATUS_INVALID_PARAMETER;
	}
	*sangoma_wait_object = NULL;
	sng_wait_obj = malloc(sizeof(**sangoma_wait_object));
	if (!sng_wait_obj) {
		return SANG_STATUS_FAILED_ALLOCATE_MEMORY;
	}

	memset(sng_wait_obj, 0x00, sizeof(*sng_wait_obj));
	/* it is a first initialization of the object */
	sng_wait_obj->init_flag = LIBSNG_MAGIC_NO;

	sng_wait_obj->fd			= fd;
	sng_wait_obj->object_type	= object_type;

#if defined(__WINDOWS__)
	if (!SANGOMA_OBJ_HAS_DEVICE(sng_wait_obj)) {
		sng_wait_obj->signal_object = CreateEvent(NULL, FALSE, FALSE, NULL);
		if(!sng_wait_obj->signal_object){
			err = SANG_STATUS_GENERAL_ERROR;
			goto failed;
		}
		err = SANG_STATUS_SUCCESS;
	} else {
		err = init_sangoma_event_object(sng_wait_obj, fd);
		if(SANG_STATUS_SUCCESS != err){
			goto failed;
		}
	}
#else
	int filedes[2];
	if (SANGOMA_OBJ_IS_SIGNALABLE(sng_wait_obj)) {
		sng_wait_obj->signal_read_fd = INVALID_HANDLE_VALUE;
		sng_wait_obj->signal_write_fd = INVALID_HANDLE_VALUE;
		/* if we want cross-process event notification we can use a named pipe with mkfifo() */
		if (pipe(filedes)) {
			err = SANG_STATUS_GENERAL_ERROR;
			goto failed;
		}
		sng_wait_obj->signal_read_fd = filedes[0];
		sng_wait_obj->signal_write_fd = filedes[1];
	}
#endif
	*sangoma_wait_object = sng_wait_obj;
	return err;

failed:
	if (sng_wait_obj) {
		sangoma_wait_obj_delete(&sng_wait_obj);
	}
	return err;
}

/*!
  \fn sangoma_status_t sangoma_wait_obj_delete(sangoma_wait_obj_t **sangoma_wait_object)
  \brief De-allocate all resources in a wait object
  \param sangoma_wait_object pointer to a pointer to a single device object
  \return SANG_STATUS_SUCCESS on success or some sangoma status error
*/
sangoma_status_t _LIBSNG_CALL sangoma_wait_obj_delete(sangoma_wait_obj_t **sangoma_wait_object)
{
	sangoma_wait_obj_t *sng_wait_obj = *sangoma_wait_object;

	if(sng_wait_obj->init_flag != LIBSNG_MAGIC_NO){
		/* error. object was not initialized by sangoma_wait_obj_init() */
		return SANG_STATUS_INVALID_DEVICE;
	}

#if defined(__WINDOWS__)
	if (sng_wait_obj->signal_object &&
		sng_wait_obj->signal_object != INVALID_HANDLE_VALUE) {
		sangoma_close(&sng_wait_obj->signal_object);
	}
#else
	if (SANGOMA_OBJ_IS_SIGNALABLE(sng_wait_obj)) {
		sangoma_close(&sng_wait_obj->signal_read_fd);
		sangoma_close(&sng_wait_obj->signal_write_fd);
	}
#endif
	sng_wait_obj->init_flag = 0;
	sng_wait_obj->object_type = UNKNOWN_WAIT_OBJ;
	free(sng_wait_obj);
	*sangoma_wait_object = NULL;
	return SANG_STATUS_SUCCESS;
}

/*!
  \fn void sangoma_wait_obj_signal(void *sangoma_wait_object)
  \brief Set wait object to a signaled state
  \param sangoma_wait_object pointer a single device object 
  \return 0 on success, non-zero on error
*/
int _LIBSNG_CALL sangoma_wait_obj_signal(sangoma_wait_obj_t *sng_wait_obj)
{
	if (!SANGOMA_OBJ_IS_SIGNALABLE(sng_wait_obj)) {
		/* even when Windows objects are always signalable for the sake of providing
		 * a consistent interface to the user we downgrade the capabilities of Windows
		 * objects unless the sangoma wait object is explicitly initialized as signalable */
		return SANG_STATUS_INVALID_DEVICE;
	}
#if defined(__WINDOWS__)
	if(sng_wait_obj->signal_object){
		if (!SetEvent(sng_wait_obj->signal_object)) {
			return SANG_STATUS_GENERAL_ERROR;
		}
	}
#else
	/* at this point we know is a signalable object and has a signal_write_fd */
	if (write(sng_wait_obj->signal_write_fd, "s", 1) < 1) {
		return SANG_STATUS_GENERAL_ERROR;
	}
#endif
	return SANG_STATUS_SUCCESS;
}

/*!
  \fn sng_fd_t sangoma_wait_obj_get_fd(void *sangoma_wait_object)
  \brief Retrieve fd device file descriptor which was the 'fd' parameter for sangoma_wait_obj_init()
  \param sangoma_wait_object pointer a single device object 
  \return fd - device file descriptor
*/
sng_fd_t _LIBSNG_CALL sangoma_wait_obj_get_fd(sangoma_wait_obj_t *sng_wait_obj)
{
	return sng_wait_obj->fd;
}

/*!
  \fn void sangoma_wait_obj_set_context(sangoma_wait_obj_t *sangoma_wait_object)
  \brief Store the given context into provided sangoma wait object.
  \brief This function is useful to associate a context with a sangoma wait object.
  \param sangoma_wait_object pointer a single device object 
  \param context void pointer to user context
  \return void
*/
void _LIBSNG_CALL sangoma_wait_obj_set_context(sangoma_wait_obj_t *sng_wait_obj, void *context)
{
	sng_wait_obj->context = context;
}

/*!
  \fn PVOID sangoma_wait_obj_get_context(sangoma_wait_obj_t *sangoma_wait_object)
  \brief Retrieve the user context (if any) that was set via sangoma_wait_obj_set_context.
  \param sangoma_wait_object pointer a single device object 
  \return void*
*/
PVOID _LIBSNG_CALL sangoma_wait_obj_get_context(sangoma_wait_obj_t *sng_wait_obj)
{
	return sng_wait_obj->context;
}

/*!
  \fn sangoma_status_t _LIBSNG_CALL sangoma_waitfor_many(sangoma_wait_obj_t *sangoma_wait_objects[], int32_t in_flags[], int32_t out_flags[] uint32_t number_of_sangoma_wait_objects, int32_t system_wait_timeout);
  \brief Wait for multiple sangoma waitable objects 
  \param sangoma_wait_objects pointer to array of wait objects previously created with sangoma_wait_obj_create
  \param in_flags array of flags corresponding to the flags the user is interested on for each waitable object
  \param out_flags array of flags corresponding to the flags that are ready in the waitable objects 
  \param number_of_sangoma_wait_objects size of the array of waitable objects and flags
  \param system_wait_timeout timeout in miliseconds in case of no event
  \return negative: SANG_STATUS_APIPOLL_TIMEOUT: timeout, SANG_STATUS_SUCCESS: event occured check in sangoma_wait_objects, any other return code is some error
*/
sangoma_status_t _LIBSNG_CALL sangoma_waitfor_many(sangoma_wait_obj_t *sng_wait_objects[], uint32_t in_flags[], uint32_t out_flags[],
		uint32_t number_of_sangoma_wait_objects, int32_t system_wait_timeout)
{
#if defined(__WINDOWS__)
	HANDLE hEvents[MAXIMUM_WAIT_OBJECTS];
	DWORD dwResult;
	int at_least_one_poll_set_flags_out, err;
	sangoma_wait_obj_t *sangoma_wait_object;
#else
	uint32_t j = 0;
#endif
	uint32_t i = 0;

	memset(out_flags, 0x00, number_of_sangoma_wait_objects * sizeof(out_flags[0]));
#if defined(__WINDOWS__)

	if(sangoma_check_number_of_wait_objects(number_of_sangoma_wait_objects)){
		/* error - most likely the user did not initialize sng_wait_objects[] */
		return SANG_STATUS_INVALID_PARAMETER;
	}

	for(i = 0; i < MAXIMUM_WAIT_OBJECTS; i++){
		hEvents[i] = INVALID_HANDLE_VALUE;
	}

	/* This loop will initialize 'hEvents[]' based on
	 * 'number_of_sangoma_wait_objects' and sng_wait_objects[].  */
	for(i = 0; i < number_of_sangoma_wait_objects; i++){

		sangoma_wait_object = sng_wait_objects[i];

		if(LIBSNG_MAGIC_NO != sangoma_wait_object->init_flag){
			return SANG_STATUS_DEV_INIT_INCOMPLETE;
		}

		if(sangoma_wait_object->signal_object){
			hEvents[i] = sangoma_wait_object->signal_object;
		}

	}/* for() */

	at_least_one_poll_set_flags_out = FALSE;

	/* It is important to get 'out flags' BEFORE the WaitForMultipleObjects()
	 * because it allows to keep API driver's TRANSMIT queue full. */
	err = get_out_flags(sng_wait_objects, INVALID_INDEX, in_flags, out_flags, number_of_sangoma_wait_objects, &at_least_one_poll_set_flags_out);
	if(SANG_ERROR(err)){
		return err;
	}

	if(TRUE == at_least_one_poll_set_flags_out){
		return SANG_STATUS_SUCCESS;
	}

	/* wait untill at least one of the events is signaled OR a 'system_wait_timeout' occured */
	dwResult = WaitForMultipleObjects(number_of_sangoma_wait_objects, &hEvents[0], FALSE, system_wait_timeout);
	if (WAIT_TIMEOUT == dwResult){
		return SANG_STATUS_APIPOLL_TIMEOUT;
	}

	if( dwResult >= (DWORD)number_of_sangoma_wait_objects ) {
		return SANG_STATUS_GENERAL_ERROR;
	}

	/* WaitForMultipleObjects() was waken by a Sangoma or by a non-Sangoma wait object. */
	err = get_out_flags(sng_wait_objects,
						dwResult, /* Array index of the signalled object with the smallest index
								   * value of all the signalled objects, from array. */ 
						in_flags, out_flags, number_of_sangoma_wait_objects, NULL);
	if(SANG_ERROR(err)){
		return err;
	}

	return SANG_STATUS_SUCCESS;
#else
	struct pollfd pfds[number_of_sangoma_wait_objects*2]; /* we need twice as many polls because of the sangoma signalable objects */
	char dummy_buf[1];
	int res;
	j = 0;

	memset(pfds, 0, sizeof(pfds));

	for(i = 0; i < number_of_sangoma_wait_objects; i++){

		if (SANGOMA_OBJ_HAS_DEVICE(sng_wait_objects[i])) {
			pfds[i].fd = sng_wait_objects[i]->fd;
			pfds[i].events = in_flags[i];
		}

		if (SANGOMA_OBJ_IS_SIGNALABLE(sng_wait_objects[i])) {
			pfds[number_of_sangoma_wait_objects+j].fd = sng_wait_objects[i]->signal_read_fd;
			pfds[number_of_sangoma_wait_objects+j].events = POLLIN;
			j++;
		}
	}

	poll_try_again:

	res = poll(pfds, (number_of_sangoma_wait_objects + j), system_wait_timeout);
	if (res > 0) {
		for(i = 0; i < number_of_sangoma_wait_objects; i++){
			out_flags[i] = pfds[i].revents;
      /* POLLIN, POLLOUT, POLLPRI have same values as SANG_WAIT_OBJ_ HAS_INPUT, CAN_OUTPUT and HAS_EVENTS, no need to set them */
		}
		for(i = 0; i < j; i++){
			if (pfds[number_of_sangoma_wait_objects+i].revents & POLLIN) {
				/* read and discard the signal byte  */
				read(pfds[number_of_sangoma_wait_objects+i].fd, &dummy_buf, 1);
				/* set the proper flag so users may know this object was signaled */
				out_flags[i] |= SANG_WAIT_OBJ_IS_SIGNALED;
			}
		}
	} else if (res < 0 && errno == EINTR) {
		/* TODO: decrement system_wait_timeout */
		goto poll_try_again;
	}
	if (res < 0) {
		return SANG_STATUS_GENERAL_ERROR;
	}
	if (res == 0) {
		return SANG_STATUS_APIPOLL_TIMEOUT;
	}
	return SANG_STATUS_SUCCESS;
#endif
}


/*!
  \fn sangoma_status_t _LIBSNG_CALL sangoma_waitfor(sangoma_wait_obj_t *sangoma_wait_obj, int32_t inflags, int32_t *outflags, int32_t timeout)
  \brief Wait for a single waitable object
  \param sangoma_wait_obj pointer to a wait object previously created with sangoma_wait_obj_create
  \param timeout timeout in miliseconds in case of no event
  \return SANG_STATUS_APIPOLL_TIMEOUT: timeout, SANG_STATUS_SUCCESS: event occured use sangoma_wait_obj_get_out_flags to check or error status
*/
sangoma_status_t _LIBSNG_CALL sangoma_waitfor(sangoma_wait_obj_t *sangoma_wait_obj, uint32_t inflags, uint32_t *outflags, int32_t timeout)
{
	return sangoma_waitfor_many(&sangoma_wait_obj, &inflags, outflags, 1, timeout);
}


/************************************************************//**
 * Device OPEN / CLOSE Functions
 ***************************************************************/


int _LIBSNG_CALL sangoma_span_chan_toif(int span, int chan, char *interface_name)
{
#if defined(__WINDOWS__)
	/* Form the Interface Name from span and chan number (i.e. wanpipe1_if1). */
	sprintf(interface_name, WP_INTERFACE_NAME_FORM, span, chan);
#else
 	sprintf(interface_name,"s%ic%i",span,chan);
#endif
	return 0;
}

int _LIBSNG_CALL sangoma_interface_toi(char *interface_name, int *span, int *chan)
{
	char *p=NULL, *sp = NULL, *ch = NULL;
	int ret = 0;
	char data[FNAME_LEN];

	strncpy(data, interface_name, FNAME_LEN);
	if ((data[0])) {
		for (p = data; *p; p++) {
			if (sp && *p == 'g') {
				*p = '\0';
				ch = (p + 1);
				break;
			} else if (*p == 'w') {
				sp = (p + 1);
			}
		}

		if(ch && sp) {
			*span = atoi(sp);
			*chan = atoi(ch);
			ret = 1;
		} else {
			*span = -1;
			*chan = -1;
		}
	}

	return ret;
}

int _LIBSNG_CALL sangoma_interface_wait_up(int span, int chan, int sectimeout)
{
#if defined(__WINDOWS__)
  /* Windows does not need to wait for interfaces to come up */
  return 0;
#else
	char interface_name[FNAME_LEN];
  struct stat statbuf;
  struct timeval endtime = {0,0};
  struct timeval curtime = {0,0};
  int counter;
  int rc;
  if (sectimeout >= 0 && gettimeofday(&endtime, NULL)) {
    return -1;
  }
  snprintf(interface_name, sizeof(interface_name), "/dev/" WP_INTERFACE_NAME_FORM, span, chan);
  endtime.tv_sec += sectimeout;
  do {
    counter = 0;
    while ((rc = stat(interface_name, &statbuf)) && errno == ENOENT && counter != 10) {
      poll(0, 0, 100); // test in 100ms increments
      counter++;
    }
    if (!rc || errno != ENOENT) break;
    if (gettimeofday(&curtime, NULL)) {
      return -1;
    }
  } while (sectimeout < 0 || timercmp(&endtime, &curtime,>));
  return rc;
#endif
}

int _LIBSNG_CALL sangoma_span_chan_fromif(char *interface_name, int *span, int *chan)
{
	char *p = NULL, *sp = NULL, *ch = NULL;
	int ret = 0;
	char data[FNAME_LEN];

	/* Windows: Accept WANPIPEx_IFy or wanpipex_ify
	 * where x is the span and y is the chan. */

	strncpy(data, interface_name, FNAME_LEN);
	if ((data[0])) {
		for (p = data; *p; p++) {
#if defined(__WINDOWS__)
			if (sp && (*p == 'F'||*p == 'f')) {
#else
			if (sp && *p == 'c') {
#endif
				*p = '\0';
				ch = (p + 1);
				break;
#if defined(__WINDOWS__)
			} else if (*p == 'E'||*p == 'e') {
#else
			} else if (*p == 's') {
#endif
				sp = (p + 1);
			}
		}

		if(ch && sp) {
			*span = atoi(sp);
			*chan = atoi(ch);
			ret = 1;
		} else {
			*span = -1;
			*chan = -1;
		}
	}

	return ret;
}

sng_fd_t _LIBSNG_CALL sangoma_open_api_span_chan(int span, int chan) 
{
	sng_fd_t fd = INVALID_HANDLE_VALUE;
	wanpipe_api_t tdm_api;
	int err;

	fd = __sangoma_open_api_span_chan(span, chan);

#if defined(__WINDOWS__)
	if(fd == INVALID_HANDLE_VALUE){
		return fd;
	}
#else
	if (fd < 0) {
		return fd;
	}
#endif

	memset(&tdm_api,0,sizeof(tdm_api));
	tdm_api.wp_cmd.cmd = WP_API_CMD_OPEN_CNT;
	err=sangoma_cmd_exec(fd,&tdm_api);
	if (err){
		sangoma_close(&fd);
		return fd;
	}

	if (tdm_api.wp_cmd.open_cnt > 1) {
		/* this is NOT the first open request for this span/chan */
		sangoma_close(&fd);
		fd = INVALID_HANDLE_VALUE;/* fd is NOT valid anymore */
	}

	return fd;
}            

sng_fd_t _LIBSNG_CALL sangoma_open_dev_by_name(const char *dev_name)
{
   	char fname[FNAME_LEN];

#if defined(__WINDOWS__)
	_snprintf(fname , FNAME_LEN, "\\\\.\\%s", dev_name);

	return CreateFile(	fname, 
						GENERIC_READ | GENERIC_WRITE, 
						FILE_SHARE_READ | FILE_SHARE_WRITE,
						(LPSECURITY_ATTRIBUTES)NULL, 
						OPEN_EXISTING,
						FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH,
						(HANDLE)NULL
						);
#else
	sprintf(fname,"/dev/%s", dev_name);

	return open(fname, O_RDWR);
#endif
}

/* no checks done for multiple open */
sng_fd_t _LIBSNG_CALL __sangoma_open_api_span_chan(int span, int chan)
{
   	char tmp_fname[FNAME_LEN];

	/* Form the Interface Name from span and chan number (i.e. wanpipe1_if1). */
	_snprintf(tmp_fname, DEV_NAME_LEN, WP_INTERFACE_NAME_FORM, span, chan);

	return sangoma_open_dev_by_name(tmp_fname);
}            

sng_fd_t _LIBSNG_CALL sangoma_open_api_ctrl(void)
{
	return sangoma_open_dev_by_name(WP_CTRL_DEV_NAME);
}

#ifdef WP_API_FEATURE_LOGGER
sng_fd_t _LIBSNG_CALL sangoma_logger_open(void)
{
	return sangoma_open_dev_by_name(WP_LOGGER_DEV_NAME);
}
#endif

int _LIBSNG_CALL sangoma_get_open_cnt(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_OPEN_CNT;

	err=sangoma_cmd_exec(fd,tdm_api);
	if (err){
		return -1;
	}

	return tdm_api->wp_cmd.open_cnt;
}

sng_fd_t _LIBSNG_CALL sangoma_create_socket_by_name(char *device, char *card) 
{
	int span,chan;
	sangoma_interface_toi(device,&span,&chan);
	
	return sangoma_open_api_span_chan(span,chan);
}

          
sng_fd_t _LIBSNG_CALL sangoma_open_api_span(int span) 
{
    int i=0;
	sng_fd_t fd = INVALID_HANDLE_VALUE;

	for(i = 1; i < 32; i++){

		fd = sangoma_open_api_span_chan(span, i);

#if defined(__WINDOWS__)
		if(fd != INVALID_HANDLE_VALUE){
#else
		if (fd >= 0) {
#endif

			/* found free chan */
			break;
		}

	}/*for()*/

    return fd;  
}


/*!
  \fn void sangoma_close(sng_fd_t *fd)
  \brief Close device file descriptor
  \param fd device file descriptor
  \return void
*/
void _LIBSNG_CALL sangoma_close(sng_fd_t *fd)
{
	if (!fd) {
		return;
	}
#if defined(__WINDOWS__)
	if (*fd != INVALID_HANDLE_VALUE){
		CloseHandle(*fd);
		*fd = INVALID_HANDLE_VALUE;
	}
#else
	if (*fd >= 0) {
		close(*fd);
		*fd = -1;
	}
#endif
}



/************************************************************//**
 * Device READ / WRITE Functions
 ***************************************************************/ 

int _LIBSNG_CALL sangoma_readmsg(sng_fd_t fd, void *hdrbuf, int hdrlen, void *databuf, int datalen, int flag)
{
	int rx_len=0;

#if defined(__WINDOWS__)
	wp_api_hdr_t	*rx_hdr = (wp_api_hdr_t*)hdrbuf;
	wp_api_element_t wp_api_element;

	if(hdrlen != sizeof(wp_api_hdr_t)){
		/*error*/
		DBG_ERR("hdrlen (%i) != sizeof(wp_api_hdr_t) (%i)\n", hdrlen, sizeof(wp_api_hdr_t));
		return -1;
	}

	wp_api_element.hdr.operation_status = SANG_STATUS_IO_ERROR;

	if(DoReadCommand(fd, &wp_api_element)){
		/*error*/
		DBG_ERR("DoReadCommand() failed! Check messages log.\n");
		return -4;
	}

	memcpy(rx_hdr, &wp_api_element.hdr, sizeof(wp_api_hdr_t));

	switch(rx_hdr->operation_status)
	{
	case SANG_STATUS_RX_DATA_AVAILABLE:
		/* ok */
		if(rx_hdr->data_length <= datalen){
			memcpy(databuf, wp_api_element.data, rx_hdr->data_length);
		}else{
			rx_hdr->operation_status = SANG_STATUS_BUFFER_TOO_SMALL;
		}
		break;
	case SANG_STATUS_NO_DATA_AVAILABLE:
		/* Note that SANG_STATUS_NO_DATA_AVAILABLE is NOT an error becase
		 * read() is non-blocking and can be called at any time (by some polling code)*/
		return 1; /* return positive value to indicate success, user must check 'rx_hdr->operation_status' */
	default:
		if(libsng_dbg_level)DBG_ERR("Operation Status: %s(%d)\n",
			SDLA_DECODE_SANG_STATUS(rx_hdr->operation_status), rx_hdr->operation_status);
		return -5;
	}

	rx_len = rx_hdr->data_length;
#else
	wan_msghdr_t msg;
	wan_iovec_t iov[2];

	memset(&msg,0,sizeof(msg));
	memset(&iov[0],0,sizeof(iov[0])*2);

	iov[0].iov_len=hdrlen;
	iov[0].iov_base=hdrbuf;

	iov[1].iov_len=datalen;
	iov[1].iov_base=databuf;

	msg.msg_iovlen=2;
	msg.msg_iov=iov;

	rx_len = read(fd,&msg,sizeof(msg));

	if (rx_len <= sizeof(wp_api_hdr_t)){
		return -EINVAL;
	}

	rx_len -= sizeof(wp_api_hdr_t);
#endif
    return rx_len;
}                    

int _LIBSNG_CALL sangoma_writemsg(sng_fd_t fd, void *hdrbuf, int hdrlen, void *databuf, unsigned short datalen, int flag)
{
	int bsent=-1;
	wp_api_hdr_t *wp_api_hdr = hdrbuf;

	if (hdrlen != sizeof(wp_api_hdr_t)) {
		/* error. Possible cause is a mismatch between versions of API header files. */
		DBG_ERR("hdrlen (%i) != sizeof(wp_api_hdr_t) (%i)\n", hdrlen, sizeof(wp_api_hdr_t));
		return -1;
	}

#if defined(__WINDOWS__)

	wp_api_hdr->data_length = datalen;

	/*queue data for transmission*/
	if(DoWriteCommand(fd, databuf, datalen, hdrbuf, hdrlen)){
		/*error*/
		DBG_ERR("DoWriteCommand() failed!! Check messages log.\n");
		return -1;
	}

	bsent=0;
	/*check that frame was transmitted*/
	switch(wp_api_hdr->operation_status)
	{
	case SANG_STATUS_SUCCESS:
		bsent = datalen;
		break;
	default:
		DBG_ERR("Operation Status: %s(%d)\n",
			SDLA_DECODE_SANG_STATUS(wp_api_hdr->operation_status), wp_api_hdr->operation_status);
		break;
	}/*switch()*/
#else
	wan_msghdr_t msg;
	wan_iovec_t iov[2];
	
	memset(&msg,0,sizeof(msg));
	memset(&iov[0],0,sizeof(iov[0])*2);

	iov[0].iov_len=hdrlen;
	iov[0].iov_base=hdrbuf;

	iov[1].iov_len=datalen;
	iov[1].iov_base=databuf;

	msg.msg_iovlen=2;
	msg.msg_iov=iov;

	bsent = write(fd,&msg,sizeof(msg));

	if (bsent == (datalen+hdrlen)){
		wp_api_hdr->wp_api_hdr_operation_status=SANG_STATUS_SUCCESS;
		bsent-=sizeof(wp_api_hdr_t);
	} else if (errno == EBUSY){
		wp_api_hdr->wp_api_hdr_operation_status=SANG_STATUS_DEVICE_BUSY;
	} else {
		wp_api_hdr->wp_api_hdr_operation_status=SANG_STATUS_IO_ERROR;
	}
	wp_api_hdr->wp_api_hdr_data_length=bsent;

#endif
	return bsent;
}


#ifdef WANPIPE_TDM_API


/************************************************************//**
 * Device API COMMAND Functions
 ***************************************************************/ 



/*========================================================
 * Execute TDM command
 *
 */
int _LIBSNG_CALL sangoma_cmd_exec(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	int err;

#if defined(__WINDOWS__)
	err = tdmv_api_ioctl(fd, &tdm_api->wp_cmd);
#else
	err = ioctl(fd,WANPIPE_IOCTL_API_CMD,&tdm_api->wp_cmd);
	if (err < 0){
		char tmp[50];
		sprintf(tmp,"TDM API: CMD: %i\n",tdm_api->wp_cmd.cmd);
		perror(tmp);
		return -1;
	}
#endif
	return err;
}

/*========================================================
 * Get Full TDM API configuration per channel
 *
 */
int _LIBSNG_CALL sangoma_get_full_cfg(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_GET_FULL_CFG;

	err=sangoma_cmd_exec(fd,tdm_api);
	if (err){
		return err;
	}

#if 0
	printf("TDM API CFG:\n");
	printf("\thw_tdm_coding:\t%d\n",tdm_api->wp_cmd.hw_tdm_coding);
	printf("\thw_mtu_mru:\t%d\n",tdm_api->wp_cmd.hw_mtu_mru);
	printf("\tusr_period:\t%d\n",tdm_api->wp_cmd.usr_period);
	printf("\ttdm_codec:\t%d\n",tdm_api->wp_cmd.tdm_codec);
	printf("\tpower_level:\t%d\n",tdm_api->wp_cmd.power_level);
	printf("\trx_disable:\t%d\n",tdm_api->wp_cmd.rx_disable);
	printf("\ttx_disable:\t%d\n",tdm_api->wp_cmd.tx_disable);
	printf("\tusr_mtu_mru:\t%d\n",tdm_api->wp_cmd.usr_mtu_mru);
	printf("\tidle flag:\t0x%02X\n",tdm_api->wp_cmd.idle_flag);

#ifdef WP_API_FEATURE_FE_ALARM
	printf("\tfe alarms:\t0x%02X\n",tdm_api->wp_cmd.fe_alarms);
#endif
	
	printf("\trx pkt\t%d\ttx pkt\t%d\n",tdm_api->wp_cmd.stats.rx_packets,
				tdm_api->wp_cmd.stats.tx_packets);
	printf("\trx err\t%d\ttx err\t%d\n",
				tdm_api->wp_cmd.stats.rx_errors,
				tdm_api->wp_cmd.stats.tx_errors);
#ifndef __WINDOWS__
	printf("\trx ovr\t%d\ttx idl\t%d\n",
				tdm_api->wp_cmd.stats.rx_fifo_errors,
				tdm_api->wp_cmd.stats.tx_carrier_errors);
#endif		
#endif		
	
	return 0;
}

/*========================================================
 * SET Codec on a particular Channel.
 * 
 * Available codecs are defined in 
 * /usr/src/linux/include/linux/wanpipe_cfg.h
 * 
 * enum wan_codec_format {
 *  	WP_NONE,
 *	WP_SLINEAR
 * }
 *
 */
int _LIBSNG_CALL sangoma_tdm_set_codec(sng_fd_t fd, wanpipe_api_t *tdm_api, int codec)
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_CODEC;
	tdm_api->wp_cmd.tdm_codec = codec;
	return sangoma_cmd_exec(fd,tdm_api);
}

/*========================================================
 * GET Codec from a particular Channel.
 * 
 * Available codecs are defined in 
 * /usr/src/linux/include/linux/wanpipe_cfg.h
 * 
 * enum wan_codec_format {
 *  	WP_NONE,
 *	WP_SLINEAR
 * }
 *
 */
int _LIBSNG_CALL sangoma_tdm_get_codec(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_GET_CODEC;

	err=sangoma_cmd_exec(fd,tdm_api);
	if (err){
		return err;
	}

	return tdm_api->wp_cmd.tdm_codec;	
}

/*========================================================
 * SET Rx/Tx Hardware Period in milliseconds.
 * 
 * Available options are:
 *  10,20,30,40,50 ms      
 *
 */
int _LIBSNG_CALL sangoma_tdm_set_usr_period(sng_fd_t fd, wanpipe_api_t *tdm_api, int period)
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_USR_PERIOD;
	tdm_api->wp_cmd.usr_period = period;
	return sangoma_cmd_exec(fd,tdm_api);
}

/*========================================================
 * GET Rx/Tx Hardware Period in milliseconds.
 * 
 * Available options are:
 *  10,20,30,40,50 ms      
 *
 */
int _LIBSNG_CALL sangoma_tdm_get_usr_period(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_GET_USR_PERIOD;

	err=sangoma_cmd_exec(fd,tdm_api);
	if (err){
		return err;
	}

	return tdm_api->wp_cmd.usr_period;
}

/*========================================================
 * GET Current User Hardware Coding Format
 *
 * Coding Format will be ULAW/ALAW based on T1/E1 
 */

int _LIBSNG_CALL sangoma_get_hw_coding(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_GET_HW_CODING;
	err=sangoma_cmd_exec(fd,tdm_api);
	if (err){
		return err;
	}
	return tdm_api->wp_cmd.hw_tdm_coding;
}

#ifdef WP_API_FEATURE_DTMF_EVENTS
/*========================================================
 * GET Current User Hardware DTMF Enabled/Disabled
 *
 * Will return true if HW DTMF is enabled on Octasic
 */

int _LIBSNG_CALL sangoma_tdm_get_hw_dtmf(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_GET_HW_DTMF;
	err=sangoma_cmd_exec(fd,tdm_api);
	if (err){
		return err;
	}
	return tdm_api->wp_cmd.hw_dtmf;
}

/*========================================================
 * GET status of echo canceler chip. 
 *
 * Will return true if HW EC is available 
 */

int _LIBSNG_CALL sangoma_tdm_get_hw_ec(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_GET_HW_EC;
	err=sangoma_cmd_exec(fd,tdm_api);
	if (err){
		return err;
	}
	return tdm_api->wp_cmd.hw_ec;
}
#endif


#ifdef WP_API_FEATURE_EC_CHAN_STAT
/*========================================================
 * GET status of the echo canceler for current channel.
 *
 * Will return true if HW EC is enabled 
 */

int _LIBSNG_CALL sangoma_tdm_get_hwec_chan_status(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_GET_HW_EC_CHAN;
	err=sangoma_cmd_exec(fd,tdm_api);
	if (err){
		return err;
	}
	return tdm_api->wp_cmd.hw_ec;
}

#endif

#ifdef WP_API_FEATURE_HWEC_PERSIST
/*========================================================
 * Check if hwec persist is enabled or disabled.
 * If persist is on: then hwec will always stay enabled for
 *                   all channes.
 * If persist is off: hwec will not be enabled by default.
 *
 * Will return true if HW EC is persist mode is on 
 */

int _LIBSNG_CALL sangoma_tdm_get_hwec_persist_status(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_GET_HW_EC_PERSIST;
	err=sangoma_cmd_exec(fd,tdm_api);
	if (err){
		return err;
	}
	return tdm_api->wp_cmd.hw_ec;
}     
#endif


/*========================================================
 * GET Current User MTU/MRU values in bytes.
 * 
 * The USER MTU/MRU values will change each time a PERIOD
 * or CODEC is adjusted.
 */
int _LIBSNG_CALL sangoma_tdm_get_usr_mtu_mru(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_GET_USR_MTU_MRU;

	err=sangoma_cmd_exec(fd,tdm_api);
	if (err){
		return err;
	}

	return tdm_api->wp_cmd.usr_mtu_mru;
}

/*========================================================
 * SET TDM Power Level
 * 
 * This option is not implemented yet
 *
 */
int _LIBSNG_CALL sangoma_tdm_set_power_level(sng_fd_t fd, wanpipe_api_t *tdm_api, int power)
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_POWER_LEVEL;
	tdm_api->wp_cmd.power_level = power;
	return sangoma_cmd_exec(fd,tdm_api);
}

/*========================================================
 * GET TDM Power Level
 * 
 * This option is not implemented yet
 *
 */
int _LIBSNG_CALL sangoma_tdm_get_power_level(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_GET_POWER_LEVEL;

	err=sangoma_cmd_exec(fd,tdm_api);
	if (err){
		return err;
	}

	return tdm_api->wp_cmd.power_level;
}

int _LIBSNG_CALL sangoma_flush_bufs(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_FLUSH_BUFFERS;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_flush_rx_bufs(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_FLUSH_RX_BUFFERS;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_flush_tx_bufs(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_FLUSH_TX_BUFFERS;
	return sangoma_cmd_exec(fd,tdm_api);
} 

int _LIBSNG_CALL sangoma_flush_event_bufs(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_FLUSH_EVENT_BUFFERS;
	return sangoma_cmd_exec(fd,tdm_api);
}  

int _LIBSNG_CALL sangoma_tdm_enable_rbs_events(sng_fd_t fd, wanpipe_api_t *tdm_api, int poll_in_sec) 
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_ENABLE_RBS_EVENTS;
	tdm_api->wp_cmd.rbs_poll = poll_in_sec;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_tdm_disable_rbs_events(sng_fd_t fd, wanpipe_api_t *tdm_api) {

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_DISABLE_RBS_EVENTS;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_tdm_write_rbs(sng_fd_t fd, wanpipe_api_t *tdm_api, int channel, unsigned char rbs)
{
	WANPIPE_API_INIT_CHAN(tdm_api, channel);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_WRITE_RBS_BITS;
	tdm_api->wp_cmd.rbs_tx_bits=rbs;
	return sangoma_cmd_exec(fd,tdm_api);
}        

int _LIBSNG_CALL sangoma_tdm_read_rbs(sng_fd_t fd, wanpipe_api_t *tdm_api, int channel, unsigned char *rbs)
{
	int err;
	WANPIPE_API_INIT_CHAN(tdm_api, channel);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_READ_RBS_BITS;
	tdm_api->wp_cmd.rbs_tx_bits=0;
	
	err=sangoma_cmd_exec(fd,tdm_api);
	if (err){
		return err;
	}

	*rbs=(unsigned char)tdm_api->wp_cmd.rbs_rx_bits;
	return 0;
}

#ifdef WP_API_FEATURE_BUFFER_MULT

int _LIBSNG_CALL sangoma_tdm_set_buffer_multiplier(sng_fd_t fd, wanpipe_api_t *tdm_api, unsigned int multiplier)
{
	
	int err;
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_BUFFER_MULTIPLIER;
	*((unsigned int*)&tdm_api->wp_cmd.data[0]) = multiplier;
	
	err=sangoma_cmd_exec(fd,tdm_api);
	if (err){
		return err;
	}

	return 0;
}

#endif


int _LIBSNG_CALL sangoma_read_event(sng_fd_t fd, wanpipe_api_t *tdm_api)
{

#ifdef WP_API_FEATURE_EVENTS
	wp_api_event_t *rx_event;
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_READ_EVENT;
	
	err=sangoma_cmd_exec(fd,tdm_api);
	if (err){
		return err;
	}

	rx_event = &tdm_api->wp_cmd.event;

#ifdef WP_API_DEPRECATED_FEATURE_READ_CALLBACK_FUNCTIONS
   	/*
	 The use of callbacks here is purely optional and is left
     here for backward compatibility purposes.  By default user
 	 should handle events outside this funciton. This function
	 should only be used to read the event
    */

	switch (rx_event->wp_api_event_type){
	
	case WP_API_EVENT_RBS:
		if (tdm_api->wp_callback.wp_rbs_event) {
			tdm_api->wp_callback.wp_rbs_event(fd,rx_event->wp_api_event_rbs_bits);
		}
		break;
	
#ifdef WP_API_FEATURE_DTMF_EVENTS
	case WP_API_EVENT_DTMF:
		if (tdm_api->wp_callback.wp_dtmf_event) {
			tdm_api->wp_callback.wp_dtmf_event(fd,
						rx_event->wp_api_event_dtmf_digit,
						rx_event->wp_api_event_dtmf_type,
						rx_event->wp_api_event_dtmf_port);
		}
		break;
#endif
		
	case WP_API_EVENT_RXHOOK:
		if (tdm_api->wp_callback.wp_rxhook_event) {
			tdm_api->wp_callback.wp_rxhook_event(fd,
						rx_event->wp_api_event_hook_state);
		}
		break;

	case WP_API_EVENT_RING_DETECT:
		if (tdm_api->wp_callback.wp_ring_detect_event) {
			tdm_api->wp_callback.wp_ring_detect_event(fd,
						rx_event->wp_api_event_ring_state);
		}
		break;

	case WP_API_EVENT_RING_TRIP_DETECT:
		if (tdm_api->wp_callback.wp_ring_trip_detect_event) {
			tdm_api->wp_callback.wp_ring_trip_detect_event(fd,
						rx_event->wp_api_event_ring_state);
		}
		break;

#ifdef WP_API_FEATURE_FE_ALARM
	case WP_API_EVENT_ALARM:
		if (tdm_api->wp_callback.wp_fe_alarm_event) {
			tdm_api->wp_callback.wp_fe_alarm_event(fd,
						rx_event->wp_api_event_alarm);
		}   
		break; 
#endif

#ifdef WP_API_FEATURE_LINK_STATUS
	/* Link Status */	
	case WP_API_EVENT_LINK_STATUS:
		if(tdm_api->wp_callback.wp_link_status_event){
			tdm_api->wp_callback.wp_link_status_event(fd,
						rx_event->wp_api_event_link_status);
		}
		
		break;
#endif

#ifdef WP_API_FEATURE_POL_REV
    case WP_API_EVENT_POLARITY_REVERSE:
        break;
#endif	
	default:
#ifdef __WINDOWS__
		if(0)printf("Warning: libsangoma: %s fd=0x%p: Unknown TDM event!", __FUNCTION__,fd);
#else
		if(0)printf("Warning: libsangoma: %s fd=%d: Unknown TDM event!", __FUNCTION__, fd);
#endif
		break;
	}

#endif

	
	return 0;
#else
	printf("Error: Read Event not supported!\n");
	return -1;
#endif
}  


#ifdef WP_API_FEATURE_LOGGER
/*========================================================
 * Execute Wanpipe Logger command
 */
sangoma_status_t _LIBSNG_CALL sangoma_logger_cmd_exec(sng_fd_t fd, wp_logger_cmd_t *logger_cmd)
{
#if defined(__WINDOWS__)
	return logger_api_ioctl(fd, logger_cmd);
#else
	int err = ioctl(fd,WANPIPE_IOCTL_LOGGER_CMD,logger_cmd);
	if (err < 0){
		char tmp[50];
		sprintf(tmp,"Logger CMD: %i\n",logger_cmd->cmd);
		perror(tmp);
		return -1;
	}
	return err;
#endif
}

sangoma_status_t _LIBSNG_CALL sangoma_logger_read_event(sng_fd_t fd, wp_logger_cmd_t *logger_cmd)
{
	logger_cmd->cmd = WP_API_LOGGER_CMD_READ_EVENT;
	return sangoma_logger_cmd_exec(fd, logger_cmd);
}

sangoma_status_t _LIBSNG_CALL sangoma_logger_flush_buffers(sng_fd_t fd, wp_logger_cmd_t *logger_cmd)
{
	logger_cmd->cmd = WP_API_LOGGER_CMD_FLUSH_BUFFERS;
	return sangoma_logger_cmd_exec(fd, logger_cmd);
}

sangoma_status_t _LIBSNG_CALL sangoma_logger_get_statistics(sng_fd_t fd, wp_logger_cmd_t *logger_cmd)
{
	logger_cmd->cmd = WP_API_LOGGER_CMD_GET_STATS;
	return sangoma_logger_cmd_exec(fd, logger_cmd);
}

sangoma_status_t _LIBSNG_CALL sangoma_logger_reset_statistics(sng_fd_t fd, wp_logger_cmd_t *logger_cmd)
{
	logger_cmd->cmd = WP_API_LOGGER_CMD_RESET_STATS;
	return sangoma_logger_cmd_exec(fd, logger_cmd);
}

sangoma_status_t _LIBSNG_CALL sangoma_logger_get_open_handle_counter(sng_fd_t fd, wp_logger_cmd_t *logger_cmd)
{
	logger_cmd->cmd = WP_API_LOGGER_CMD_OPEN_CNT;
	return sangoma_logger_cmd_exec(fd, logger_cmd);
}

sangoma_status_t _LIBSNG_CALL sangoma_logger_get_logger_level(sng_fd_t fd, wp_logger_cmd_t *logger_cmd)
{
	logger_cmd->cmd = WP_API_LOGGER_CMD_GET_LOGGER_LEVEL;
	return sangoma_logger_cmd_exec(fd, logger_cmd);
}

sangoma_status_t _LIBSNG_CALL sangoma_logger_set_logger_level(sng_fd_t fd, wp_logger_cmd_t *logger_cmd)
{
	logger_cmd->cmd = WP_API_LOGGER_CMD_SET_LOGGER_LEVEL;
	return sangoma_logger_cmd_exec(fd, logger_cmd);
}

#endif /* WP_API_FEATURE_LOGGER */

#ifdef WP_API_FEATURE_FAX_EVENTS
int _LIBSNG_CALL sangoma_tdm_enable_fax_events(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);	
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_FAX_DETECT;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_ENABLE;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_tdm_disable_fax_events(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_FAX_DETECT;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_DISABLE;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_tdm_get_hw_fax(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	int err;
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_GET_HW_FAX_DETECT;
	err=sangoma_cmd_exec(fd,tdm_api);
	if (err){
		return err;
	}
	return tdm_api->wp_cmd.hw_fax;
}
#endif

#ifdef WP_API_FEATURE_DTMF_EVENTS
int _LIBSNG_CALL sangoma_tdm_enable_dtmf_events(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);	
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_DTMF;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_ENABLE;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_tdm_disable_dtmf_events(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_DTMF;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_DISABLE;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_tdm_enable_rm_dtmf_events(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_RM_DTMF;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_ENABLE;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_tdm_disable_rm_dtmf_events(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_RM_DTMF;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_DISABLE;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_tdm_enable_rxhook_events(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_RXHOOK;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_ENABLE;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_tdm_disable_rxhook_events(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_RXHOOK;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_DISABLE;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_tdm_enable_ring_events(sng_fd_t fd, wanpipe_api_t *tdm_api) 
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_RING;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_ENABLE;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_tdm_disable_ring_events(sng_fd_t fd, wanpipe_api_t *tdm_api) 
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_RING;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_DISABLE;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_tdm_enable_ring_detect_events(sng_fd_t fd, wanpipe_api_t *tdm_api) 
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);	
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_RING_DETECT;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_ENABLE;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_tdm_disable_ring_detect_events(sng_fd_t fd, wanpipe_api_t *tdm_api) 
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_RING_DETECT;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_DISABLE;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_tdm_enable_ring_trip_detect_events(sng_fd_t fd, wanpipe_api_t *tdm_api) 
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_RING_TRIP_DETECT;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_ENABLE;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_tdm_disable_ring_trip_detect_events(sng_fd_t fd, wanpipe_api_t *tdm_api) 
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_RING_TRIP_DETECT;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_DISABLE;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_tdm_txsig_kewl(sng_fd_t fd, wanpipe_api_t *tdm_api) 
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_TXSIG_KEWL;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_ENABLE;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_tdm_txsig_start(sng_fd_t fd, wanpipe_api_t *tdm_api) 
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);	
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_TXSIG_START;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_ENABLE;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_tdm_txsig_onhook(sng_fd_t fd, wanpipe_api_t *tdm_api) 
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_TXSIG_ONHOOK;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_ENABLE;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_tdm_txsig_offhook(sng_fd_t fd, wanpipe_api_t *tdm_api) 
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);	
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_TXSIG_OFFHOOK;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_ENABLE;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_tdm_enable_tone_events(sng_fd_t fd, wanpipe_api_t *tdm_api, uint16_t tone_id) 
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_TONE;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_ENABLE;
	tdm_api->wp_cmd.event.wp_api_event_tone_type = tone_id;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_tdm_disable_tone_events(sng_fd_t fd, wanpipe_api_t *tdm_api) 
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_TONE;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_DISABLE;
	tdm_api->wp_cmd.event.wp_api_event_tone_type = 0x00;
	return sangoma_cmd_exec(fd,tdm_api);
}
#endif

int _LIBSNG_CALL sangoma_tdm_enable_hwec(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	/* intentionally NOT initializing chan - caller must do it */
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_ENABLE_HWEC;
	return sangoma_cmd_exec(fd,tdm_api);
}

int _LIBSNG_CALL sangoma_tdm_disable_hwec(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	/* intentionally NOT initializing chan - caller must do it */
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_DISABLE_HWEC;
	return sangoma_cmd_exec(fd,tdm_api);
}


/*========================================================
 * GET Front End Alarms
 * 
 */                  
#ifdef WP_API_FEATURE_FE_ALARM
int _LIBSNG_CALL sangoma_tdm_get_fe_alarms(sng_fd_t fd, wanpipe_api_t *tdm_api, unsigned int *alarms)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_GET_FE_ALARMS;

	err=sangoma_cmd_exec(fd,tdm_api);
	if (err){
		return err;
	}

	*alarms=tdm_api->wp_cmd.fe_alarms;

	return 0;
}         

/* get current Line Connection state - Connected/Disconnected */
int _LIBSNG_CALL sangoma_get_fe_status(sng_fd_t fd, wanpipe_api_t *tdm_api, unsigned char *current_status)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_GET_FE_STATUS;
	err = sangoma_cmd_exec(fd, tdm_api);
	*current_status = tdm_api->wp_cmd.fe_status;

	return err;
}
#endif

/* get current Line Connection state - Connected/Disconnected */
#ifdef WP_API_FEATURE_LINK_STATUS
int _LIBSNG_CALL sangoma_get_link_status(sng_fd_t fd, wanpipe_api_t *tdm_api, unsigned char *current_status)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_GET_FE_STATUS;
	err = sangoma_cmd_exec(fd, tdm_api);
	*current_status = tdm_api->wp_cmd.fe_status;

	return err;
}

/* set current Line Connection state - Connected/Disconnected. valid only for ISDN BRI */
int _LIBSNG_CALL sangoma_set_fe_status(sng_fd_t fd, wanpipe_api_t *tdm_api, unsigned char new_status)
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_FE_STATUS;
	tdm_api->wp_cmd.fe_status = new_status;
	return sangoma_cmd_exec(fd, tdm_api);
}
#endif

int _LIBSNG_CALL sangoma_disable_bri_bchan_loopback(sng_fd_t fd, wanpipe_api_t *tdm_api, int channel)
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.channel	= (unsigned char)channel;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_BRI_CHAN_LOOPBACK;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_DISABLE;
	return sangoma_cmd_exec(fd, tdm_api);
}

int _LIBSNG_CALL sangoma_enable_bri_bchan_loopback(sng_fd_t fd, wanpipe_api_t *tdm_api, int channel)
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.channel	= (unsigned char)channel;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_BRI_CHAN_LOOPBACK;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_ENABLE;
	return sangoma_cmd_exec(fd, tdm_api);
}

int _LIBSNG_CALL sangoma_get_tx_queue_sz(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_GET_TX_Q_SIZE;
	tdm_api->wp_cmd.tx_queue_sz = 0;
	
	err=sangoma_cmd_exec(fd, tdm_api);
	if (err < 0) {
		return err;
	}

	return tdm_api->wp_cmd.tx_queue_sz;
}

int _LIBSNG_CALL sangoma_set_tx_queue_sz(sng_fd_t fd, wanpipe_api_t *tdm_api, int size)
{
	if (size < 0) {
		return -1;
	}
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_TX_Q_SIZE;
	tdm_api->wp_cmd.tx_queue_sz = size;
	return sangoma_cmd_exec(fd, tdm_api);
}

int _LIBSNG_CALL sangoma_get_rx_queue_sz(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_GET_RX_Q_SIZE;
	tdm_api->wp_cmd.rx_queue_sz = 0;
	
	err=sangoma_cmd_exec(fd, tdm_api);
	if (err < 0) {
		return err;
	}

	return tdm_api->wp_cmd.rx_queue_sz;

}

int _LIBSNG_CALL sangoma_set_rx_queue_sz(sng_fd_t fd, wanpipe_api_t *tdm_api, int size)
{
	if (size < 0) {
		return -1;
	}

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_RX_Q_SIZE;
	tdm_api->wp_cmd.rx_queue_sz = size;
	return sangoma_cmd_exec(fd, tdm_api);
}

int _LIBSNG_CALL sangoma_get_driver_version(sng_fd_t fd, wanpipe_api_t *tdm_api, wan_driver_version_t *drv_ver)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_DRIVER_VERSION;

	err = sangoma_cmd_exec(fd, tdm_api);
	if (err == 0) {
		if (tdm_api->wp_cmd.data_len == sizeof(wan_driver_version_t)) {
			if (drv_ver) {
				memcpy(drv_ver,&tdm_api->wp_cmd.version,sizeof(wan_driver_version_t));
			}
		} else {
			return -1;
		}
	}

	return err;
}

int _LIBSNG_CALL sangoma_get_firmware_version(sng_fd_t fd, wanpipe_api_t *tdm_api, unsigned char *ver)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_FIRMWARE_VERSION;

	err = sangoma_cmd_exec(fd, tdm_api);
	if (err == 0) {
		if (tdm_api->wp_cmd.data_len == sizeof(unsigned char)) {
			*ver = tdm_api->wp_cmd.data[0];
		} else {
			return -1;
		}
	}

	return err;
}

int _LIBSNG_CALL sangoma_get_cpld_version(sng_fd_t fd, wanpipe_api_t *tdm_api, unsigned char *ver)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_CPLD_VERSION;

	err = sangoma_cmd_exec(fd, tdm_api);
	if (err == 0) {
		if (tdm_api->wp_cmd.data_len == sizeof(unsigned char)) {
			*ver = tdm_api->wp_cmd.data[0];
		} else {
			return -1;
		}
	}

	return err;
}

int _LIBSNG_CALL sangoma_get_aft_customer_id(sng_fd_t fd, unsigned char *out_customer_id)
{
	wan_udp_hdr_t wan_udp;

	memset(&wan_udp, 0x00, sizeof(wan_udp));

	wan_udp.wan_udphdr_command = WANPIPEMON_AFT_CUSTOMER_ID;
	wan_udp.wan_udphdr_return_code = SANG_STATUS_UNSUPPORTED_FUNCTION;
	wan_udp.wan_udphdr_data_len = 0;

	if (sangoma_mgmt_cmd(fd, &wan_udp)) {
		return SANG_STATUS_IO_ERROR;
	}

	if (wan_udp.wan_udphdr_return_code) {
		return SANG_STATUS_UNSUPPORTED_FUNCTION;
	}

	*out_customer_id = sangoma_get_wan_udphdr_data_byte(&wan_udp, 0);

	return 0;
}

#ifdef  WP_API_FEATURE_FE_RW
int _LIBSNG_CALL sangoma_fe_reg_write(sng_fd_t fd, uint32_t offset, uint8_t data)
{
	int chan=0;
	wan_udp_hdr_t wan_udp;
	sdla_fe_debug_t *fe_debug; 
	memset(&wan_udp, 0x00, sizeof(wan_udp));
	
	{
		int err;
		wanpipe_api_t tdm_api;
		memset(&tdm_api,0,sizeof(tdm_api));
		err=sangoma_get_full_cfg(fd, &tdm_api);
		if (err) {
			return err;
		}
		chan=tdm_api.wp_cmd.chan;
		if (chan) 
			chan--;
	}

	wan_udp.wan_udphdr_command	= WAN_FE_SET_DEBUG_MODE;
	wan_udp.wan_udphdr_data_len	= sizeof(sdla_fe_debug_t);
	wan_udp.wan_udphdr_return_code	= 0xaa;
	fe_debug = (sdla_fe_debug_t*)wan_udp.wan_udphdr_data;

	fe_debug->type = WAN_FE_DEBUG_REG;
	fe_debug->mod_no = chan;
	fe_debug->fe_debug_reg.reg  = offset;
	fe_debug->fe_debug_reg.value  = data;
	fe_debug->fe_debug_reg.read = 0;
	
	if (sangoma_mgmt_cmd(fd, &wan_udp)) {
		return SANG_STATUS_IO_ERROR;
	}

	if (wan_udp.wan_udphdr_return_code) {
		return SANG_STATUS_UNSUPPORTED_FUNCTION;
	}

	return 0;
}

int _LIBSNG_CALL sangoma_fe_reg_read(sng_fd_t fd, uint32_t offset, uint8_t *data)
{
	int chan=0;
	wan_udp_hdr_t wan_udp;
	sdla_fe_debug_t *fe_debug;
	memset(&wan_udp, 0x00, sizeof(wan_udp));

	{
		int err;
		wanpipe_api_t tdm_api;
		memset(&tdm_api,0,sizeof(tdm_api));
		err=sangoma_get_full_cfg(fd, &tdm_api);
		if (err) {
			return err;
		}
		chan=tdm_api.wp_cmd.chan;
		if (chan) 
			chan--;
	}

	wan_udp.wan_udphdr_command	= WAN_FE_SET_DEBUG_MODE;
	wan_udp.wan_udphdr_data_len	= sizeof(sdla_fe_debug_t);
	wan_udp.wan_udphdr_return_code	= 0xaa;
	fe_debug = (sdla_fe_debug_t*)wan_udp.wan_udphdr_data;

	fe_debug->type = WAN_FE_DEBUG_REG;
	fe_debug->mod_no = chan;
	fe_debug->fe_debug_reg.reg  = offset;
	fe_debug->fe_debug_reg.read = 1;
	
	if (sangoma_mgmt_cmd(fd, &wan_udp)) {
		return SANG_STATUS_IO_ERROR;
	}

	if (wan_udp.wan_udphdr_return_code) {
		return SANG_STATUS_UNSUPPORTED_FUNCTION;
	}

	*data = fe_debug->fe_debug_reg.value;
	
	return 0;
}
#endif

int _LIBSNG_CALL sangoma_get_stats(sng_fd_t fd, wanpipe_api_t *tdm_api, wanpipe_chan_stats_t *stats)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_GET_STATS;

	err = sangoma_cmd_exec(fd, tdm_api);
	if (err == 0) {
		if (stats) {
			memcpy(stats, &tdm_api->wp_cmd.stats, sizeof(wanpipe_chan_stats_t));
		}
	}

	return err;
}

int _LIBSNG_CALL sangoma_flush_stats(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_RESET_STATS;
	return sangoma_cmd_exec(fd, tdm_api);
}

int _LIBSNG_CALL sangoma_set_rm_rxflashtime(sng_fd_t fd, wanpipe_api_t *tdm_api, int rxflashtime)
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_RM_RXFLASHTIME;
	tdm_api->wp_cmd.rxflashtime=rxflashtime;
	return sangoma_cmd_exec(fd, tdm_api);
}

#ifdef WP_API_FEATURE_RM_GAIN
int _LIBSNG_CALL sangoma_set_rm_tx_gain(sng_fd_t fd, wanpipe_api_t *tdm_api, int value)
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_SET_RM_TX_GAIN;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_ENABLE;
	tdm_api->wp_cmd.event.wp_api_event_gain_value = value;
	return sangoma_cmd_exec(fd, tdm_api);
}

int _LIBSNG_CALL sangoma_set_rm_rx_gain(sng_fd_t fd, wanpipe_api_t *tdm_api, int value)
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_SET_RM_RX_GAIN;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_ENABLE;
	tdm_api->wp_cmd.event.wp_api_event_gain_value = value;
	return sangoma_cmd_exec(fd, tdm_api);
}
#endif /* WP_API_FEATURE_RM_GAIN */

int _LIBSNG_CALL sangoma_tdm_set_polarity(sng_fd_t fd, wanpipe_api_t *tdm_api, int polarity)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_SETPOLARITY;
	tdm_api->wp_cmd.event.wp_api_event_polarity = polarity;
	err = sangoma_cmd_exec(fd, tdm_api);
	return err;

}


int _LIBSNG_CALL sangoma_tdm_txsig_onhooktransfer(sng_fd_t fd, wanpipe_api_t *tdm_api) 
{
	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_SET_EVENT;
	tdm_api->wp_cmd.event.wp_api_event_type = WP_API_EVENT_ONHOOKTRANSFER;
	tdm_api->wp_cmd.event.wp_api_event_mode = WP_API_EVENT_ENABLE;
	return sangoma_cmd_exec(fd,tdm_api);
}

#ifdef WP_API_FEATURE_LOOP

int _LIBSNG_CALL sangoma_tdm_enable_loop(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_ENABLE_LOOP;
	err = sangoma_cmd_exec(fd, tdm_api);
	return err;
}      

int _LIBSNG_CALL sangoma_tdm_disable_loop(sng_fd_t fd, wanpipe_api_t *tdm_api)
{
	int err;

	WANPIPE_API_INIT_CHAN(tdm_api, 0);
	SANGOMA_INIT_TDM_API_CMD_RESULT(*tdm_api);
	tdm_api->wp_cmd.cmd = WP_API_CMD_DISABLE_LOOP;
	err = sangoma_cmd_exec(fd, tdm_api);
	return err;
}      

#endif

#ifdef WP_API_FEATURE_HWEC_PERSIST




#endif



#endif /* WANPIPE_TDM_API */
