/*
* co_sdonetwork.c - contains sdo network routines for server
*
* Copyright (c) 2013-2023 emotas embedded communication GmbH
*-------------------------------------------------------------------
* $Id: co_sdonetwork.c 49616 2023-09-26 14:22:08Z boe $
*
*
*-------------------------------------------------------------------
*
*
*/

/********************************************************************/
/**
* \brief sdo network routines
*
* \file co_sdonetwork.c
* contains sdo network transfer routines for server
*
* Functions are only available in Classical CAN mode
*
* Each SDO network request is handled as SDO server.
* Then the application is indicated to get an unused SDO channel
* for the client transfer.
* After the transfer is finished, the application is informed
* about not more used given SDO channel.
* So the application can decide, how many SDO client channel should be defined
* and how many can be used by the gateway.
* If no indication was registered, SDO channel 1 is used.
*/


/* header of standard C - libraries
---------------------------------------------------------------------------*/
#include <string.h>

/* header of project specific types
---------------------------------------------------------------------------*/
#include <gen_define.h>

#ifdef CO_SDO_NETWORKING
#include <co_datatype.h>
#include <co_memcpy.h>
#include <co_drv.h>
#include <co_timer.h>
#include <co_odaccess.h>
#include <co_nmt.h>
#include <co_network.h>
#include <co_sdo.h>
#include "ico_cobhandler.h"
#include "ico_queue.h"
#include "ico_event.h"
#include "ico_indication.h"
#include "ico_sdoserver.h"
#include "ico_sdoclient.h"
#include "ico_sdo.h"
#include "ico_network.h"

/* constant definitions
---------------------------------------------------------------------------*/

/* local defined data types
---------------------------------------------------------------------------*/

/* list of external used functions, if not in headers
---------------------------------------------------------------------------*/

/* list of global defined functions
---------------------------------------------------------------------------*/

/* list of local defined functions
---------------------------------------------------------------------------*/
static UNSIGNED8 getUserSdoNr(CO_LINE_DECL, UNSIGNED16 network, UNSIGNED8 node,
		UNSIGNED32 *pCobClSrv, UNSIGNED32 *pCobSrvCl);

/* external variables
---------------------------------------------------------------------------*/

/* global variables
---------------------------------------------------------------------------*/

/* local defined variables
---------------------------------------------------------------------------*/
#ifdef CO_EVENT_DYNAMIC_NETWORK_SDOCLIENT_USER
static CO_EVENT_GW_SDOCLIENT_FCT_T usrSdoNrFct = NULL;
#endif /* CO_EVENT_DYNAMIC_NETWORK_SDOCLIENT_USER */


#ifdef CO_SDO_CLIENT_CNT
/***************************************************************************/
/**
*
* \brief coSdoNetworkRead - read network value by SDO
*
* This function is only available in classical CAN mode.
*
* This function starts a sdo read transfer over a network
* to the given network/node and SDO parameters.
*
* As first, the network connection to the router is etablished
* and than the normal SDO transfer ist started.
*
* The result is given by the standard SDO client indication functions.
*
* The data are stored at the given pointer pData
* with a maximal length of dataLen.
* <br>
* The timeout value given in msec is started with each message transmission.
* <br>
* The numeric flag is only valid for big-endian transfers.
* If this parameter is set, the data are changed to little endian format.
*
* Before an SDO can be started, it has to be initialized.
* Initialization is done by setup the COB-Ids of this SDO
* at index 0x128x:1 and 0x128x:2
*
* \return RET_T
*
*/
RET_T coSdoNetworkRead(
		CO_LINE_TYPE	CO_LINE,	/**< can line */
		UNSIGNED8		sdoNr,		/**< sdo number */
		UNSIGNED16		network,	/**< network number */
		UNSIGNED8		node,		/**< node number */
		UNSIGNED16		index,		/**< index at server OD */
		UNSIGNED8		subIndex,	/**< index at server OD */
		UNSIGNED8		*pData,		/**< pointer to transfer data */
		UNSIGNED32		dataLen,	/**< data len for transfer */
		UNSIGNED16		numeric,	/**< numeric flag (only for big endian) */
		UNSIGNED32		timeout		/**< timeout in msec */
	)
{
CO_NMT_STATE_T nmtState;
CO_SDO_CLIENT_T	*pSdo;
UNSIGNED8	trData[CO_CAN_MAX_DATA_LEN];	/* data */
RET_T		retVal;
UNSIGNED8	nwLine;
UNSIGNED8	routerNode;

	nmtState = coNmtGetState(CO_LINE);

	/* PRE_OPERATIONAL ? */
	if ((nmtState != CO_NMT_STATE_PREOP)
	 && (nmtState != CO_NMT_STATE_OPERATIONAL))  {
		return(RET_INVALID_NMT_STATE);
	}

	pSdo = icoSdoClientPtr(CO_LINE, sdoNr);
	if (pSdo == NULL)  {
		return(RET_SDO_INVALID_VALUE);
	}

	if (pSdo->state != CO_SDO_CLIENT_STATE_FREE)  {
		return(RET_SERVICE_BUSY);
	}

	/* setup cob-ids */
	retVal = coNetworkGet(CO_LINE, network, &nwLine, &routerNode);
	if (retVal != RET_OK)  {
		return(RET_NETWORK_ID_UNKNOWN);
	}

	/* setup cobs */
	if (pSdo->trCobId != (0x600ul + routerNode))  {
		retVal = coOdSetCobid(CO_LINE, (0x1280u + sdoNr) - 1u, 1u,
				0x600ul + routerNode);
		if (retVal != RET_OK)  {
			return(retVal);
		}
	}
	if (pSdo->recCobId != (0x580ul + routerNode))  {
		retVal = coOdSetCobid(CO_LINE, (0x1280u + sdoNr) - 1u, 2u,
				0x580ul + routerNode);
		if (retVal != RET_OK)  {
			return(retVal);
		}
	}

	/* requests for other network ? */
	if (icoNetworkLocalId(CO_LINE) == network)  {
		/* local network - start sdo transfer directly */
		retVal = coSdoRead(CO_LINE, sdoNr, index, subIndex, pData, dataLen,
				numeric, timeout);
	} else {
		/* remote network, start first network request */
		pSdo->para.index = index;
		pSdo->para.subIndex = subIndex;
		pSdo->pData = pData;
		pSdo->para.size = dataLen;
		pSdo->numeric = numeric;
		pSdo->timeOutVal = timeout;
		pSdo->networkNr = network;
		pSdo->networkNode = node;

		trData[0] = CO_SDO_CCS_NET_IND;
		trData[1] = (UNSIGNED8)(network & 0xffu);
		trData[2] = (UNSIGNED8)(network >> 8u);
		trData[3] = node;

		MEMSET(&trData[4], 0, 4u);
		pSdo->state = CO_SDO_CLIENT_STATE_NETWORK_READ_REQ;

		/* transmit request */
		retVal = icoTransmitMessage(CO_LINE, pSdo->trCob, &trData[0], 0u);
		if (retVal != RET_OK)  {
			pSdo->state = CO_SDO_CLIENT_STATE_FREE;
			return(retVal);
		}

		/* start timer */
		(void)coTimerStart(CO_LINE, &pSdo->timer, timeout * 1000ul,
			icoSdoClientTimeOut, pSdo, CO_TIMER_ATTR_ROUNDUP); /*lint !e960 */
		/* Derogation MisraC2004 R.16.9 function identifier used without '&'
		 * or parenthesized parameter */
	}

	return(retVal);
}


/***************************************************************************/
/**
* \brief coSdoNetworkWrite - Write value by SDO
*
* This function is only available in classical CAN mode.
*
* This function starts a sdo read transfer over a network
* to the given network/node and SDO parameters.
*
* As first, the network connection to the router is etablished
* and than the normal SDO transfer ist started.
*
* The result is given by the standard SDO client indication functions.
*
* The data are written from the given pointer pData
* and with a length of dataLen.
* <br>
* The timeout value given in msec is started with each message transmission.
* <br>
* The numeric flag is only valid for big-endian transfers.
* If this parameter is set, the data are changed to little endian format.
*
* Before an SDO can be started, it has to be initialized.
* This is done by setup the COB-Ids of this SDO at index 0x128x:1 and 0x128x:2
*
* If SDO block transfer is enabled,
* it will be used automatically if dataLen is larger
* than CO_SDO_BLOCK_MIN_SIZE.
* If the server doesn't support block transfer,
* segmented transfer will be used instead.
*
* \return RET_T
*
*/
RET_T coSdoNetworkWrite(
		CO_LINE_TYPE	CO_LINE,	/**< can line */
		UNSIGNED8		sdoNr,		/**< sdo number */
		UNSIGNED16		network,	/**< network number */
		UNSIGNED8		node,		/**< node number */
		UNSIGNED16		index,		/**< index at server OD */
		UNSIGNED8		subIndex,	/**< index at server OD */
		UNSIGNED8		*pData,		/**< pointer to transfer data */
		UNSIGNED32		dataLen,	/**< data len for transfer */
		UNSIGNED16		numeric,	/**< numeric flag (only for big endian) */
		UNSIGNED32		timeout		/**< timeout in msec */
	)
{
CO_NMT_STATE_T nmtState;
CO_SDO_CLIENT_T	*pSdo;
RET_T		retVal;
UNSIGNED8	trData[CO_CAN_MAX_DATA_LEN];	/* data */
UNSIGNED8	nwLine;
UNSIGNED8	routerNode;

	nmtState = coNmtGetState(CO_LINE);

	/* PRE_OPERATIONAL ? */
	if ((nmtState != CO_NMT_STATE_PREOP)
	 && (nmtState != CO_NMT_STATE_OPERATIONAL))  {
		return(RET_INVALID_NMT_STATE);
	}

	pSdo = icoSdoClientPtr(CO_LINE, sdoNr);
	if (pSdo == NULL)  {
		return(RET_SDO_INVALID_VALUE);
	}

	if (pSdo->state != CO_SDO_CLIENT_STATE_FREE)  {
		return(RET_SERVICE_BUSY);
	}

	/* setup cob-ids */
	retVal = coNetworkGet(CO_LINE, network, &nwLine, &routerNode);
	if (retVal != RET_OK)  {
		return(RET_NETWORK_ID_UNKNOWN);
	}

	/* setup cobs */
	if (pSdo->trCobId != (0x600ul + routerNode))  {
		retVal = coOdSetCobid(CO_LINE, (0x1280u + sdoNr) - 1u, 1u,
				0x600ul + routerNode);
		if (retVal != RET_OK)  {
			return(retVal);
		}
	}
	if (pSdo->recCobId != (0x580ul + routerNode))  {
		retVal = coOdSetCobid(CO_LINE, (0x1280u + sdoNr) - 1u, 2u,
				0x580ul + routerNode);
		if (retVal != RET_OK)  {
			return(retVal);
		}
	}

	/* requests for other network ? */
	if (icoNetworkLocalId(CO_LINE) == network)  {
		/* local network - start sdo transfer directly */
		retVal = coSdoWrite(CO_LINE, sdoNr, index, subIndex, pData, dataLen,
				numeric, timeout);
	} else {
		/* remote network, start first network request */
		pSdo->para.index = index;
		pSdo->para.subIndex = subIndex;
		pSdo->pData = pData;
		pSdo->para.size = dataLen;
		pSdo->numeric = numeric;
		pSdo->timeOutVal = timeout;

		trData[0] = CO_SDO_CCS_NET_IND;
		trData[1] = (UNSIGNED8)(network & 0xffu);
		trData[2] = (UNSIGNED8)(network >> 8u);
		trData[3] = node;

		MEMSET(&trData[4], 0, 4u);
		pSdo->state = CO_SDO_CLIENT_STATE_NETWORK_WRITE_REQ;

		/* transmit request */
		retVal = icoTransmitMessage(CO_LINE, pSdo->trCob, &trData[0], 0u);
		if (retVal != RET_OK)  {
			pSdo->state = CO_SDO_CLIENT_STATE_FREE;
			return(retVal);
		}

		/* start timer */
		(void)coTimerStart(CO_LINE, &pSdo->timer, timeout * 1000ul,
			icoSdoClientTimeOut, pSdo, CO_TIMER_ATTR_ROUNDUP); /*lint !e960 */
		/* Derogation MisraC2004 R.16.9 function identifier used without '&'
		 * or parenthesized parameter */
	}

	return(retVal);
}


/***************************************************************************/
/**
* \internal
*
* \brief icoSdoClientNetworkCon - first answer to network request
*
* \return RET_T
*
*/
void icoSdoClientNetworkCon(
		CO_LINE_TYPE	CO_LINE,		/* can line */
		CO_SDO_CLIENT_T	*pSdo,			/* pointer to sdo */
		const CO_CAN_REC_MSG_T	*pRecData	/* pointer to received data */
	)
{
RET_T	retVal;

	/* read request ? */
	if (pSdo->state == CO_SDO_CLIENT_STATE_NETWORK_READ_REQ)  {
		pSdo->state = CO_SDO_CLIENT_STATE_FREE;

		retVal = coSdoRead(CO_LINE, pSdo->para.sdoNr,
			pSdo->para.index, pSdo->para.subIndex,
			pSdo->pData, pSdo->para.size, pSdo->numeric, pSdo->timeOutVal);
	} else {
		/* write request */
		pSdo->state = CO_SDO_CLIENT_STATE_FREE;

		retVal = coSdoWrite(CO_LINE, pSdo->para.sdoNr,
			pSdo->para.index, pSdo->para.subIndex,
			pSdo->pData, pSdo->para.size, pSdo->numeric, pSdo->timeOutVal);
	}

	/* if error, abort transfer */
	if (retVal != RET_OK)  {
		icoSdoClientAbort(CO_LINE, pSdo, retVal);
	}
}
#endif /* CO_SDO_CLIENT_CNT */


/***************************************************************************/
/**
* \internal
*
* \brief icoSdoServerNetworkReq - request network sdo transfer
*
* Called from SDO server with a network request
*
* \return RET_T
*
*/
RET_T icoSdoServerNetworkReq(
		CO_LINE_TYPE	CO_LINE,		/* can line */
		CO_SDO_SERVER_T	*pSdo,			/* pointer to sdo */
		const CO_CAN_REC_MSG_T	*pRecData	/* pointer to received data */
	)
{
RET_T	retVal;
UNSIGNED8	trData[CO_CAN_MAX_DATA_LEN];	/* data */
UNSIGNED8	ifNr;
UNSIGNED8	sdoNr;
CO_SDO_CLIENT_T	*pSdoClient;
UNSIGNED8	routerNode;
UNSIGNED32	cobClSrv = 0;
UNSIGNED32	cobSrvCl = 0;

	/* transfer already actice ? */
	if (pSdo->state != CO_SDO_STATE_FREE)  {
		return(RET_NW_SDO_CHANNEL_IN_USE);
	}

	/* network/node id - save as index and subindex */
	icoSdoDeCodeMultiplexer(&pRecData->data[1], pSdo);

	/* check for valid routing path */

	/* get interface number (=canline + 1) and routernode from network-id */
	retVal = coNetworkGet(CO_LINE, pSdo->para.index, &ifNr, &routerNode);
	if (retVal != RET_OK)  {
		/* not found */
		return(RET_NETWORK_ID_UNKNOWN);
	}

	/* check requested node id */
	if ((pSdo->para.subIndex < 1u) || (pSdo->para.subIndex > 127u))  {
		return(RET_NW_NODE_ID_UNKNOWN);
	}

	/* save this information */
	pSdo->networkCanLine = ifNr - 1u;

	/* access to local data from other line ? (node id from other line) */
	if (pSdo->para.subIndex == coNmtGetNodeId(ifNr - 1))  {
		/* yes, only local access to other line */
		sdoNr = 0;
		pSdo->clientSdoNr = sdoNr;
		pSdo->canLine = CO_LINE;
		pSdoClient = NULL; /* avoid uninitialized warning */
	} else {
		/* call user indication go get unused SDO client channel */
		/* provide infos about calling sdo server as cob-ids */
		cobClSrv = pSdo->recCobId;
		cobSrvCl = pSdo->trCobId;

		sdoNr = getUserSdoNr(CO_LINE, pSdo->para.index, pSdo->para.subIndex,
			&cobClSrv, &cobSrvCl);

		/* (if no indication is registered, try SDO client 1) */

		/* check this client sdo for free */
		pSdoClient = icoSdoClientPtr(ifNr - 1, sdoNr);
		if (pSdoClient == NULL)  {
			return(RET_NW_SDO_CHANNEL_IN_USE);
		}
		if (pSdoClient->state != CO_SDO_CLIENT_STATE_FREE)  {
			return(RET_NW_SDO_CHANNEL_IN_USE);
		}

		/* now we can communication to this network to the requested node */
		pSdo->clientSdoNr = sdoNr;

		/* direct access over this interface, or further routing ? */
		if (routerNode != 0)  {
			/* further routing, send client request to next router */

			/* has user defined the cob-ids ? */
			if ((cobClSrv == 0) || (cobSrvCl == 0))  {
				cobClSrv = 0x600 + routerNode;
				cobSrvCl = 0x580 + routerNode;
			}

			/* setup this client sdo */
			retVal = coOdSetCobid(ifNr - 1, 0x1280 + sdoNr - 1, 1, cobClSrv);
			if (retVal != RET_OK)  {
				return(RET_NW_SDO_CHANNEL_IN_USE);
			}
			retVal = coOdSetCobid(ifNr - 1, 0x1280 + sdoNr - 1, 2, cobSrvCl);
			if (retVal != RET_OK)  {
				return(RET_NW_SDO_CHANNEL_IN_USE);
			}

			/* pass trough data */
			MEMCPY(&trData[0], &pRecData->data[0], 8u);
			retVal = icoTransmitMessage(pSdo->networkCanLine, pSdoClient->trCob, &trData[0], 0u);
			if (retVal != RET_OK)  {
				/* error transmit data - abort transfer */
				return(RET_NW_SDO_CHANNEL_IN_USE);
			}

			/* set sdo client state */
			pSdoClient->state = CO_SDO_CLIENT_STATE_NETWORK_IND;
			pSdoClient->serverSdoNr = pSdo->para.sdoNr;
			pSdoClient->networkCanLine = CO_LINE;

			pSdo->state = CO_SDO_STATE_NETWORK_IND;

			return(RET_OK);

		} else {
			/* direct access to requested node */

			/* has user defined the cob-ids ? */
			if ((cobClSrv == 0) || (cobSrvCl == 0))  {
				cobClSrv = 0x600 + pSdo->para.subIndex;
				cobSrvCl = 0x580 + pSdo->para.subIndex;
			}
			/* setup this client sdo */
			retVal = coOdSetCobid(ifNr - 1, 0x1280 + sdoNr - 1, 1, cobClSrv);
			if (retVal != RET_OK)  {
				return(RET_NW_SDO_CHANNEL_IN_USE);
			}
			retVal = coOdSetCobid(ifNr - 1, 0x1280 + sdoNr - 1, 2, cobSrvCl);
			if (retVal != RET_OK)  {
				return(RET_NW_SDO_CHANNEL_IN_USE);
			}
		}
	}

	/* answer */
	trData[0] = CO_SDO_SCS_NET_IND;
	trData[1] = (UNSIGNED8)(pSdo->para.index & 0xffu);
	trData[2] = (UNSIGNED8)(pSdo->para.index >> 8);
	trData[3] = pSdo->para.subIndex;

	MEMSET(&trData[4], 0u, 4u);

	/* transmit answer */
	retVal = icoTransmitMessage(CO_LINE, pSdo->trCob, &trData[0], 0u);
	if (retVal == RET_OK)  {
		/* set server state */
		pSdo->state = CO_SDO_STATE_NETWORK_IND;
		pSdoClient->cmdByte = 0u;

		/* external access - set client state to reserve this SDO */
		if (sdoNr != 0u)  {
			pSdoClient->state = CO_SDO_CLIENT_STATE_NETWORK_IND;
			pSdoClient->serverSdoNr = pSdo->para.sdoNr;
			pSdoClient->networkCanLine = CO_LINE;
		}
	}

	return(retVal);
}


/***************************************************************************/
/**
* \internal
*
* \brief icoSdoServerNetwork - Standard SDO Message from client
*
* Called from client with normal SDO request
*
* \return RET_T
*
*/
void icoSdoServerNetwork(
		CO_LINE_TYPE	CO_LINE,		/* can line */
		CO_SDO_SERVER_T	*pSdo,			/* pointer to sdo */
		const CO_CAN_REC_MSG_T	*pRecData	/* pointer to received data */
	)
{
RET_T	retVal;
UNSIGNED8	trData[CO_CAN_MAX_DATA_LEN];	/* data */
CO_REC_DATA_T	srvData;
CO_SDO_CLIENT_T	*pSdoClient;
CO_SDO_SERVER_T	*pSdoServer;

	/* local access ? */
	if (pSdo->clientSdoNr == 0u)  {

		/* simulate SDO transfer for this line */
		/* use default sdo */
		srvData.spec = 0u;
		srvData.msg.len = 8u;
		MEMCPY(&srvData.msg.data[0], &pRecData->data[0], 8u);
		icoSdoServerHandler(pSdo->networkCanLine, &srvData, pSdo);

		/* check for end transfer */
		/* if state of sdo server for other line = free, transfer is finished*/
		pSdoServer = icoSdoServerPtr(pSdo->networkCanLine, pSdo->para.sdoNr);
		if (pSdoServer != NULL)  {
			if (pSdoServer->state == CO_SDO_STATE_FREE)  {
				pSdo->state = CO_SDO_STATE_FREE;
			}
		}

		return;

	} else {
		/* get sdo client data structure */
		pSdoClient = icoSdoClientPtr(pSdo->networkCanLine, pSdo->clientSdoNr);
		if (pSdoClient == NULL)  {
			return;
		}

		/* network abort not handled yet... */

		/* pass trough data */
		MEMCPY(&trData[0], &pRecData->data[0], 8u);
		retVal = icoTransmitMessage(pSdo->networkCanLine, pSdoClient->trCob, &trData[0], 0u);
		if (retVal != RET_OK)  {
			/* error transmit data - abort transfer */
			icoSdoServerAbort(CO_LINE, pSdo, retVal, CO_SDO_SERVER_ABORT_READ);

			pSdo->state = CO_SDO_STATE_FREE;
			pSdoClient->state = CO_SDO_CLIENT_STATE_FREE;
			return;
		}

		/* last transfer ? */
		if ((pRecData->data[0] &
					(CO_SDO_CCS_MASK | CO_SDO_SCS_BLOCK_SS_DL_MASK))
				== (CO_SDO_CCS_BLOCK_UPLOAD | CO_SDO_CCS_BLOCK_SC_UL_END))  {
			pSdo->state = CO_SDO_STATE_FREE;
			pSdoClient->state = CO_SDO_CLIENT_STATE_FREE;
		}
	}

	/* save command byte - necessary for end of transfer check */
	if (pSdoClient->cmdByte == 0u)  {
		pSdoClient->cmdByte = pRecData->data[0];
	}
	pSdoClient->lastCmdByte = pRecData->data[0];

	/* abort message ? */
	if (pRecData->data[0] == CO_SDO_CS_ABORT)  {
		pSdo->state = CO_SDO_STATE_FREE;
		pSdoClient->state = CO_SDO_CLIENT_STATE_FREE;
	}
}


/***************************************************************************/
/**
* \internal
*
* \brief icoSdoserverLocalResp - response local sdo transfer
*
* \return RET_T
*
*/
RET_T icoSdoServerLocalResp(
		CO_LINE_TYPE	CO_LINE,		/* can line */
		CO_SDO_SERVER_T	*pSdo,			/* pointer to sdo */
		const UNSIGNED8	*pTrData
	)
{
CO_SDO_SERVER_T	*pSdoServer = pSdo->pLocalReqSdo;
RET_T	retVal;

	retVal = icoTransmitMessage(pSdoServer->canLine, pSdoServer->trCob, pTrData, 0u);
	if (retVal != RET_OK)  {
		/* abort transfer */
		icoSdoServerAbort(pSdoServer->canLine, pSdoServer, retVal, CO_FALSE);

		pSdo->state = CO_SDO_STATE_FREE;
		pSdoServer->state = CO_SDO_STATE_FREE;
	}

	/* abort message or last transfer? */
	if (pSdo->state == CO_SDO_STATE_FREE)  {
		pSdoServer->state = CO_SDO_STATE_FREE;
	}

	return(RET_OK);
}


/***************************************************************************/
/**
* \internal
*
* \brief icoSdoClientNetworkResp - response network sdo transfer
*
* \return RET_T
*
*/
void icoSdoClientNetworkResp(
		CO_LINE_TYPE	CO_LINE,		/* can line */
		CO_SDO_CLIENT_T	*pSdo,			/* pointer to sdo */
		const CO_CAN_REC_MSG_T	*pRecData	/* pointer to received data */
	)
{
RET_T	retVal;
UNSIGNED8	trData[CO_CAN_MAX_DATA_LEN];	/* data */
CO_SDO_SERVER_T	*pSdoServer;
UNSIGNED8	cs;
BOOL_T		finish = CO_FALSE;

	/* get sdo server data structure */
	pSdoServer = icoSdoServerPtr(pSdo->networkCanLine, pSdo->serverSdoNr);
	if (pSdoServer == NULL)  {
		return;
	}

	/* pass trough data */
	MEMCPY(&trData[0], &pRecData->data[0], 8u);
	retVal = icoTransmitMessage(pSdo->networkCanLine, pSdoServer->trCob, &trData[0], 0u);
	if (retVal != RET_OK)  {
		/* abort transfer */
		icoSdoClientAbort(CO_LINE, pSdo, retVal);

		pSdo->state = CO_SDO_CLIENT_STATE_FREE;
		pSdoServer->state = CO_SDO_STATE_FREE;

		return;
	}

	/* abort message or last transfer? */
	/* abort or block transfer */
	if (pRecData->data[0] == CO_SDO_CS_ABORT)  { 	/* abort */
		finish = CO_TRUE;
	} else {
		/* block transfer ? */
		cs = pSdo->cmdByte & CO_SDO_CCS_MASK;

		if ((cs == CO_SDO_CCS_BLOCK_DOWNLOAD)
		 || (cs == CO_SDO_CCS_BLOCK_UPLOAD))  {
			/* block transfer ? */

			/* end block download */
			if ((cs == CO_SDO_CCS_BLOCK_DOWNLOAD)
			 && ((pRecData->data[0] &
					(CO_SDO_CCS_MASK | CO_SDO_SCS_BLOCK_SS_DL_MASK))
				== (CO_SDO_SCS_BLOCK_DOWNLOAD | CO_SDO_SCS_BLOCK_SS_DL_END)))  {
				finish = CO_TRUE;
			}
		} else {
			/* expedited or segmented transfer */

			/* upload expedited */
			if (((pRecData->data[0] & (CO_SDO_CCS_MASK | CO_SDO_SCS_EXPEDITED))
					== (CO_SDO_SCS_UPLOAD_INIT | CO_SDO_SCS_EXPEDITED))
			/* last segment upload */
			 || ((pRecData->data[0] & (CO_SDO_CCS_MASK | CO_SDO_SCS_CONT_FLAG))
					== (CO_SDO_SCS_UPLOAD_SEGMENT | CO_SDO_SCS_CONT_FLAG))

			/* download expedited */
			 || ((pSdo->cmdByte & (CO_SDO_CCS_MASK | CO_SDO_SCS_EXPEDITED))
				== (CO_SDO_CCS_DOWNLOAD_INIT | CO_SDO_SCS_EXPEDITED))
			/* last segment download */
			 || ((pSdo->lastCmdByte & (CO_SDO_CCS_MASK | CO_SDO_SCS_CONT_FLAG))
				== (CO_SDO_CCS_DOWNLOAD_SEGMENT | CO_SDO_SCS_CONT_FLAG)))  {

				finish = CO_TRUE;
			}
		}
	}

	if (finish == CO_TRUE)  {
		/* transfer finished */
		pSdo->state = CO_SDO_CLIENT_STATE_FREE;
		pSdoServer->state = CO_SDO_STATE_FREE;
	}
}


/***************************************************************************/
/**
* \internal
*
* \brief getUserSdoNr - get user client SDO channel
*
* \return RET_T
*
*/
static UNSIGNED8 getUserSdoNr(
		CO_LINE_TYPE	CO_LINE,		/* can line */
		UNSIGNED16		network,
		UNSIGNED8		node,
		UNSIGNED32		*pCobClSrv,
		UNSIGNED32		*pCobSrvCl
	)
{
UNSIGNED8	sdoNr = 1;

#ifdef CO_EVENT_DYNAMIC_NETWORK_SDOCLIENT_USER
	if (usrSdoNrFct != NULL)  {
		sdoNr = usrSdoNrFct(CO_LINE, network, node, pCobClSrv, pCobSrvCl);
	}
	else
#endif /* CO_EVENT_NETWORKING_SDO_CLIENT */
	{
		*pCobClSrv = 0ul;
		*pCobSrvCl = 0ul;
	}

	return(sdoNr);
}


# ifdef CO_EVENT_DYNAMIC_NETWORK_SDOCLIENT_USER
/***************************************************************************/
/**
*
* \brief coEventRegister_GW_SDOCLIENT - register SDO client number function
*
* This function is only available in classical CAN mode.
*
* Register a function to provide user specific SDO client channel
* for network accesses.
* If no function is registered, always SDO channel 1 is used
* for network SDO accesses.
*
* The registered function is called for every SDO request
* for network gateway and should provide an unsused SDO client channel.
*
* \return RET_T
*
*/
RET_T coEventRegister_GW_SDOCLIENT(
		CO_EVENT_GW_SDOCLIENT_FCT_T	pFct	/** function pointer */
	)
{
	if (usrSdoNrFct != NULL)  {
		return(RET_EVENT_NO_RESSOURCE);
	}

	usrSdoNrFct = pFct;

	return(RET_OK);
}
# endif /* CO_EVENT_NETWORKING_SDO_CLIENT */

#endif /* CO_SDO_NETWORKING */
