/*
 * universal multiplayer library level 0 implementation file
 * transport layer: IPX datagrams PC-windows 95
 */
 
/*
 *      Author: Christophe Roguet after Benoit GERMAIN
 *
 *      Creation Date:      03/05/96
 *
 *  this file is extremely similar with the UDP version: only the parts that are bound to the address structure change
 *  and parts where socket options that work with UDP don't with IPX (MSG_PEEK, FIONREAD ...)
 */
 
/* with a non blocking socket */
 
#include "warnings.h"
 
#ifdef _MSC_VER
	#pragma warning(disable : 4306)
#endif
 
#include <stdlib.h>
#include "Privl0ipx.h"
#include "PrivNetDef.h"
#include "l0GlDef.h"
#include "NetMemCo.h"
#include "NetSock.h"
#include "NetEnd.h"
#include <NET\Netdebug.h>
 
/* array of descriptors to access a physical channel. */
static tdstL0PCWin95IPXChannel gs_a_stL0PCWin95IPXChannels[C_ucL0MaxNumberOfIPXChannels];
 
/* the IPX adress of the computer the application is running on, in network order */
 
static SOCKADDR_IPX gs_stMyAddress;
 
/* the global socket descriptor used for all net operations */
 
static int gs_iSD;
 
static char gs_bL0PCWin95IPXInitState;
 
static tdstWinSockFunc *gs_p_stIPXWinSocDesc;
static char gs_cIsWinsocDescInit = 0;
 
#define DLL_FD_ISSET(fd, set) ((gs_p_stIPXWinSocDesc->m_pfn_i_WSAFDIsSet)((SOCKET)(fd), (fd_set *)(set)))
 
/* Flot control */
static int gs_iNbSendByNetEngine;
 
/*****************************************************************************
 * 
 * Description: uwL0PCWin95IPXGetChannel
 *
 * look up the channel associated to a particular remote address
 *
 *****************************************************************************
 *
 * Input:   inet address to look up
 *
 * Output:  identification of the channel
 *
 *****************************************************************************
 * Creation Date: May 3, 1996         Author: Christophe ROGUET
 *****************************************************************************
 *
 * Modification log:
 *
 * Date:                                Author:
 *
 ****************************************************************************/
tduwNetChannel uwL0PCWin95IPXGetChannel(SOCKADDR_IPX *p_stKey)
{
        tduwNetChannel uwCurrentChannel;
 
        for
  (
  uwCurrentChannel=0;
  uwCurrentChannel<C_ucL0MaxNumberOfIPXChannels;
  uwCurrentChannel++
  )
                if(
     gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].ubf1IsSlotInUse==1
                        /*&&!memcmp(gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].stRemoteAddr.sa_netnum,
                                           p_stKey->sa_netnum, 
                                           sizeof gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].stRemoteAddr.sa_netnum)
                        */&&!memcmp(gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].stRemoteAddr.sa_nodenum,
                                           p_stKey->sa_nodenum, 
                                           sizeof gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].stRemoteAddr.sa_nodenum))
                        return uwCurrentChannel;
 
        return C_uwNetInvalidChannel;   /* Error invalid channel ?*/
}
 
 /*****************************************************************************
 * 
 * Description: uwL0PCWin95IPXOpenChannel
 *
 * returns the identification number of an unused slot in the channel table
 *
 *****************************************************************************
 *
 * Input:   inet address to bind to the channel
 *
 * Output:  identification of the channel
 *
 *****************************************************************************
 * Creation Date: May 3, 1996         Author: Christophe ROGUET
 *****************************************************************************
 *
 * Modification log:
 *
 * Date:                                Author:
 *
 ****************************************************************************/
tduwNetChannel uwL0PCWin95IPXOpenChannel(SOCKADDR_IPX *p_stRemoteAddr, unsigned short uwPortNumber)
{
 tduwNetChannel uwCurrentChannel;
 
        /* scan the channel table until we find an unused slot */
 
        for
  (
  uwCurrentChannel=0; 
                uwCurrentChannel<C_ucL0MaxNumberOfIPXChannels; 
                uwCurrentChannel++
  )
                
                if(gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].ubf1IsSlotInUse == 0)
  {
                        /* set the use mark */
                        gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].ubf1IsSlotInUse =1;
 
                        /* set the remote address */
                        /*memcpy(gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].stRemoteAddr.sa_netnum,
                                   p_stRemoteAddr->sa_netnum,
                                   sizeof gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].stRemoteAddr.sa_netnum);*/
                        memset(gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].stRemoteAddr.sa_netnum,
                               0,
                               sizeof gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].stRemoteAddr.sa_netnum);
                        memcpy(gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].stRemoteAddr.sa_nodenum,
                                   p_stRemoteAddr->sa_nodenum, 
                                   sizeof gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].stRemoteAddr.sa_nodenum);
                                   /* remark: the socket number in RemoteAddr might be a local, system allocated socket number */
                                   /* (I am not sure of this), so we don't copy the whole SOCKADDR_IPX structure, but only     */
                                   /* the previous two fields. The socket number has been set to the right value                           */
                                   /* for all channels in the call to vLevel0InitWin95PCIPX() (and the address family as well).*/
   gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].stRemoteAddr.sa_socket=uwPortNumber;
 
                        /* set the ring to empty */
                        gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].ucHead=
                        gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].ucTail=0;
 
                        return uwCurrentChannel;
                }
 
        /* there are no free channels */
 
        return C_uwNetInvalidChannel;   /* Error invalid channel ?*/
 
}
 
/*****************************************************************************
 * 
 * Description: eL0PCWin95IPXCloseAndResetChannel
 *
 * Close properluy the corresponding channel
 *
 *****************************************************************************
 *
 * Input:     identifier of the channel
 *
 * Output:        none
 *
 *****************************************************************************
 * Creation Date: May 3, 1996         Author: Christophe ROGUET
 *****************************************************************************
 *
 * Modification log:
 *
 * Date:                                Author:
 *
 ****************************************************************************/
NetLib_tdeErrorStatus eL0PCWin95IPXCloseAndResetChannel(tduwNetChannel uwChannel)
{
 tdpPointer p_Msg;
 
        if(uwChannel>=C_ucL0MaxNumberOfIPXChannels)
                return NetLib_E_es_InvalidChannel; /* the channel does not exist in the table */
 
        if(!gs_a_stL0PCWin95IPXChannels[uwChannel].ubf1IsSlotInUse)
                return NetLib_E_es_NoError;   /* the slot is not initialized for use */
 
        /* clear the use mark */
 while(eL0PCWin95IPXRemoveMessage(uwChannel,&p_Msg)==NetLib_E_es_NoError)
 {
  vFree(p_Msg);
 }
 
        gs_a_stL0PCWin95IPXChannels[uwChannel].ubf1IsSlotInUse=0;
 
        /* set the destination adress to none (let's be cautious) */
        memset(gs_a_stL0PCWin95IPXChannels[uwChannel].stRemoteAddr.sa_netnum, 0, 
                    sizeof gs_a_stL0PCWin95IPXChannels[uwChannel].stRemoteAddr.sa_netnum);
        memset(gs_a_stL0PCWin95IPXChannels[uwChannel].stRemoteAddr.sa_nodenum, 0, 
                    sizeof gs_a_stL0PCWin95IPXChannels[uwChannel].stRemoteAddr.sa_nodenum);
 gs_a_stL0PCWin95IPXChannels[uwChannel].stRemoteAddr.sa_socket =0;
 
 /* the channel is working properly */
 gs_a_stL0PCWin95IPXChannels[uwChannel].eChannelStatus = E_ts_Invalid;
 
 return NetLib_E_es_NoError;
}
 
/*****************************************************************************
 * 
 * Description: vL0PCWin95IPXCloseChannel
 *
 * frees a slot in the channel table
 *
 *****************************************************************************
 *
 * Input:     identifier of the channel
 *
 * Output:        none
 *
 *****************************************************************************
 * Creation Date: May 3, 1996         Author: Christophe ROGUET
 *****************************************************************************
 *
 * Modification log:
 *
 * Date:                                Author:
 *
 ****************************************************************************/
void vL0PCWin95IPXCloseChannel(tduwNetChannel uwChannel)
{
        /* broadcast channels should never be closed */
 if(gs_a_stL0PCWin95IPXChannels[uwChannel].ubf1IsBroadcast)
  return;
 
 eL0PCWin95IPXCloseAndResetChannel(uwChannel);
}
 
 /*****************************************************************************
 * 
 * Description: eL0PCWin95IPXInsertMessage
 *
 * insert a message at the end of a channel's queue
 *
 *****************************************************************************
 *
 * Input:   channel identifier, pointer to a message
 *
 * Output:  error status
 *
 *****************************************************************************
 * Creation Date: May 3, 1996         Author: Christophe ROGUET
 *****************************************************************************
 *
 * Modification log:
 *
 * Date:                                Author:
 *
 ****************************************************************************/
NetLib_tdeErrorStatus eL0PCWin95IPXInsertMessage(tduwNetChannel uwChannel, tdpPointer pData)
{
        /* are we out of space ? */
        if((1+gs_a_stL0PCWin95IPXChannels[uwChannel].ucHead)%C_ucL0MaxNumberOfMsgPerChannel
            == gs_a_stL0PCWin95IPXChannels[uwChannel].ucTail)
        return NetLib_E_es_FIFOIsFull; /* error: the ring is full */
 
        /* no, store the message at the head of the ring */
 
        gs_a_stL0PCWin95IPXChannels[uwChannel].apMsgQueue[gs_a_stL0PCWin95IPXChannels[uwChannel].ucHead]=pData;
        gs_a_stL0PCWin95IPXChannels[uwChannel].ucHead=(++gs_a_stL0PCWin95IPXChannels[uwChannel].ucHead)%C_ucL0MaxNumberOfMsgPerChannel;
 
        return NetLib_E_es_NoError; /* no error */
        
}
 
 /*****************************************************************************
 * 
 * Description: eL0PCWin95IPXRemoveMessage
 *
 * remove the first message of a channel's queue
 *
 *****************************************************************************
 *
 * Input:   channel identifier, address of a pointer to a message
 *
 * Output:  error status
 *
 *****************************************************************************
 * Creation Date: May 3, 1996         Author: Christophe ROGUET
 *****************************************************************************
 *
 * Modification log:
 *
 * Date:                                Author:
 *
 ****************************************************************************/
NetLib_tdeErrorStatus eL0PCWin95IPXRemoveMessage(tduwNetChannel uwChannel, tdpPointer *ppData)
{
        /* is there anything in the ring ? */
        if(gs_a_stL0PCWin95IPXChannels[uwChannel].ucTail==gs_a_stL0PCWin95IPXChannels[uwChannel].ucHead)
        return NetLib_E_es_FIFOIsEmpty; /* error: the ring is empty */
 
        /* yes, return message at the tail of the ring */
        *ppData=gs_a_stL0PCWin95IPXChannels[uwChannel].apMsgQueue[gs_a_stL0PCWin95IPXChannels[uwChannel].ucTail];
        gs_a_stL0PCWin95IPXChannels[uwChannel].ucTail=
          (++gs_a_stL0PCWin95IPXChannels[uwChannel].ucTail)%C_ucL0MaxNumberOfMsgPerChannel;
 
        return NetLib_E_es_NoError;
        
}
 
/*****************************************************************************
 * 
 * Description: eL0PCWin95IPXMessageAvailable
 *
 * test the availability of a message on the socket
 *
 *****************************************************************************
 *
 * Input:     none
 *
 * Output:        
 *
 *****************************************************************************
 * Creation Date: May 3, 1996         Author: Christophe ROGUET
 *****************************************************************************
 *
 * Modification log:
 *
 * Date:                                Author:
 *
 ****************************************************************************/
NetLib_tdeErrorStatus eL0PCWin95IPXMessageAvailable(void)
{
        static fd_set readfds;
        static struct timeval timeout = { 0, 0 };
        int ok;
 
        FD_ZERO(&readfds);
        FD_SET(gs_iSD, &readfds);
 
        if((ok=gs_p_stIPXWinSocDesc->m_pfn_i_select(0, &readfds, NULL, NULL, &timeout))==SOCKET_ERROR) {
                return NetLib_E_es_SelectSocketFailure;
        }
 
        /* return TRUE iff there's something waiting to be read from our socket*/
 
        if(ok!=0 && DLL_FD_ISSET(gs_iSD, &readfds))
                return NetLib_E_es_True;
        else
                return NetLib_E_es_False;
}
 
/*****************************************************************************
 *
 * Description: uwL0PCWin95IPXGetMessage
 *
 * Get one message from the socket and insert it in a channel
 *
 * remark: Due to a bug (?) in wsock32.dll for win95, we can't perform a recvfrom
 * in MSG_PEEK mode, as we do with UDP.
 *****************************************************************************
 *
 * Input:     none
 *
 * Output:        identifier of the channel associated to the remote end from which the message originates
 *
 *****************************************************************************
 * Creation Date: May 3, 1996         Author: Christophe ROGUET
 *****************************************************************************
 *
 * Modification log:
 *
 * Date:                                Author:
 *
 ****************************************************************************/
tduwNetChannel uwL0PCWin95IPXGetMessage(void)
{
 SOCKADDR_IPX s_stExpAddr;               /* address of the expeditor of the received message */
 int iNameLength;                                /* length of the above structure */
 unsigned long ulMsgSize;                /* length of the incoming message (header included) */
 long lReceivedBytes;
 tduwNetChannel uwChannel;                       /* channel in which to insert the incoming message */
 tduwNetChannel uwReturnChannel; /* channel number to return */
 tdpPointer pData;                               /* address of the block where the incoming message gets copied */
 unsigned char ucDiscardMessage;                        /* the incoming message comes from ourself, or it is a wrong one */
 tdstNetMessage *p_stMsgReceived;
 
 pData=NULL;
 
 iNameLength=sizeof s_stExpAddr;
 
 do
 {
   /* is there anything to read from the socket ? */
 
   if(eL0PCWin95IPXMessageAvailable()!=NetLib_E_es_True)
           return C_uwNetInvalidChannel; /* error: no message available */
 
   /* read the size of datagram waiting to be read */
   if(gs_p_stIPXWinSocDesc->m_pfn_i_ioctlsocket(gs_iSD, FIONREAD, &ulMsgSize)==SOCKET_ERROR)
           return C_uwNetInvalidChannel;
 
   /* allocate a block of the size of the message */
   pData=pMalloc(ulMsgSize);
 
   if(pData==C_pNull)
    return C_uwNetInvalidChannel; /* no memory left */
 
 /* read the message into this block */
 if((lReceivedBytes=gs_p_stIPXWinSocDesc->m_pfn_i_recvfrom(gs_iSD, pData, ulMsgSize, 0,
                         (struct sockaddr *)&s_stExpAddr, &iNameLength))==SOCKET_ERROR) {
         vFree(pData);
         return C_uwNetInvalidChannel; /* the socket can't be read ? */
 }
 
 /*  if the received datagram is smaller than a header then this can't be one of our messages */
 /* and , if the message originates from the host, discard it */
 ucDiscardMessage = lReceivedBytes < sizeof(tdstNetMessage)
         || (/*!memcmp(s_stExpAddr.sa_netnum, gs_stMyAddress.sa_netnum, sizeof s_stExpAddr.sa_netnum)
             && */!memcmp(s_stExpAddr.sa_nodenum, gs_stMyAddress.sa_nodenum, sizeof s_stExpAddr.sa_nodenum));
 
 if(!ucDiscardMessage) {
    /* at least we have a block of data that can be a header : test this */
 
    p_stMsgReceived = (tdstNetMessage*)pData;
 /* if big/little endian differs, swap the header before anything else*/
    if (p_stMsgReceived->uxHeadBigEndian!=NetLib_ucGetLittleBigEndian())
      NetLib_eSwapLittleBigEndianMsgHeader(p_stMsgReceived);
 
    ucDiscardMessage =
    (M_uwCheckSumSlot(p_stMsgReceived)!=M_uwProcessCheckSum(p_stMsgReceived))||
    (p_stMsgReceived->eMessageType <=E_Net_mt_InvalidMessage)||
    (p_stMsgReceived->eMessageType >=E_Net_mt_LastSysMsg)||
    /* verify that the data block has the size indicated in the header */
    (p_stMsgReceived->uwMessageSizeInBytes + sizeof(tdstNetMessage) != (unsigned long)lReceivedBytes);
 }
 
  if(ucDiscardMessage)
  {
   /* Either an incorrect msg was read or the message came from this machine*/
   /* free the allocated block */
#if defined(NET_USE_DEBUG)
   vDebugSI(Net_C_Debug_IPX,"GetMessage(): msg discarded",lReceivedBytes);
#endif /* NET_USE_DEBUG */
   vFree(pData);
  } else {
 
    /* reallocate a block of the right size (FIONREAD should return the size of the next datagram, */
    /* but instead it returns the total size of all datagrams waiting to be read, so the actual    */
    /* datagram is eventually smaller than what FIONREAD gave) */
 
    p_stMsgReceived=(tdstNetMessage *)pData;
    if(ulMsgSize!=sizeof(tdstNetMessage) + p_stMsgReceived->uwMessageSizeInBytes) {
#if defined(NET_USE_DEBUG)
    vDebugSISI(Net_C_Debug_IPX,"GetMessage(): real size",
            sizeof(tdstNetMessage) + p_stMsgReceived->uwMessageSizeInBytes,
            "not",ulMsgSize);
#endif /* NET_USE_DEBUG */
        ulMsgSize=sizeof(tdstNetMessage) + p_stMsgReceived->uwMessageSizeInBytes;
        pData=pMalloc(ulMsgSize);
        if(pData==C_pNull) {
            vFree((tdpPointer)p_stMsgReceived);
            return  C_uwNetInvalidChannel; /* no memory left */
            /* remark: we could continue with p_stMsgReceived */
        }
        memcpy(pData, p_stMsgReceived, ulMsgSize);
        vFree((tdpPointer)p_stMsgReceived);
    }
  }
 
         /* loop until we read a 'real' message */
 } while(ucDiscardMessage);
 
 /* does the message come from an already registered channel ? */
 
 uwChannel=uwL0PCWin95IPXGetChannel(&s_stExpAddr);
 
 if(uwChannel==C_uwNetInvalidChannel)
 {
  /* no, allocate a new channel */
  uwChannel=uwL0PCWin95IPXOpenChannel(&s_stExpAddr,gs_a_stL0PCWin95IPXChannels[0].stRemoteAddr.sa_socket);
  if(uwChannel==C_uwNetInvalidChannel) { /* can't allocate a new channel */
      vFree(pData);   /* the message gets lost ... */
      return C_uwNetInvalidChannel;
  }
 
  /* remember that this channel is new: insert its number into the broadcast channel's queue */
  uwReturnChannel=0; /* zero is the broadcast channel's number */
 
  if(eL0PCWin95IPXInsertMessage(uwReturnChannel, (tdpPointer)uwChannel)!=NetLib_E_es_NoError) {
      /* there is no room left to store the channel number in the broadcast channel */
#if defined(NET_USE_DEBUG)
        vDebugS(Net_C_Debug_IPX,"GetMessage(): msg lost: broadcast table full");
#endif /* NET_USE_DEBUG */
 
      vFree(pData);   /* the message gets lost ... */
      return C_uwNetInvalidChannel;
  }
 
  /* mark the new channel as not readable */
  gs_a_stL0PCWin95IPXChannels[uwChannel].ubf1IsReadable=0;
 }
 else
  uwReturnChannel=uwChannel;
 
 /* insert the message into the associated channel's queue */
 if(eL0PCWin95IPXInsertMessage(uwChannel, pData)!=NetLib_E_es_NoError) {
     /* there is no room left to store the message in the channel */
#if defined(NET_USE_DEBUG)
     vDebugSIS(Net_C_Debug_IPX,"GetMessage(): msg lost: channel",uwChannel,"table full");
#endif /* NET_USE_DEBUG */
     vFree(pData); /* the message gets lost */
 }
 
 /* return the channel number associated to the incoming message */
 /* i.e. : - if the remote address wasn't already registered, return the broadcast channel's number   */
 /*                - if it was, return the channel number of the queue in which the message has been inserted */
 
 return uwReturnChannel;
}
 
/*****************************************************************************
* 
* Description: uwL0PCWin95IPXStartChannelScan
*
* returns the identification number of the first channel for the protocol
*
*****************************************************************************
*
* Input:   none
*
* Output:  identification of the channel
*
*****************************************************************************
* Creation Date: May 3, 1996         Author: Christophe ROGUET
*****************************************************************************
*
* Modification log:
*
* Date:                                Author:
*
****************************************************************************/
tduwNetChannel uwL0PCWin95IPXStartChannelScan(void)
{
 tduwNetChannel uwCurrentChannel;
 
 /* scan the array of channel definitions */
 for (uwCurrentChannel = 0; uwCurrentChannel < C_ucL0MaxNumberOfIPXChannels; uwCurrentChannel ++)
 /* if the slot is used, and can transmit data to another individual player */ 
 if 
 (
     gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].ubf1IsSlotInUse
  && gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].ubf1IsReadable
 )
  /* return its index value */
  return uwCurrentChannel;
 
 /* else no slot is in use */
 return C_uwNetInvalidChannel;
}
 
/*****************************************************************************
 * 
 * Description: uwL0PCWin95IPXNextChannel
 *
 * returns the identification number of the channel following the last
 *
 *****************************************************************************
 *
 * Input:   uwLastScannedChannel, index returned by the previous call
 *
 * Output:  identification of the channel following the specified one
 *
 *****************************************************************************
 * Creation Date: May 3, 1996         Author: Christophe ROGUET
 *****************************************************************************
 *
 * Modification log:
 *
 * Date:                                Author:
 *
 ****************************************************************************/
tduwNetChannel uwL0PCWin95IPXNextChannel(tduwNetChannel uwLastScannedChannel)
{
 tduwNetChannel uwCurrentChannel;
 
 /* 
  * scan the array of channel definitions, starting with the channel following
  * the last channel returned
  */
 for 
 (
  uwCurrentChannel = (tduwNetChannel)(uwLastScannedChannel + 1); 
  uwCurrentChannel < C_ucL0MaxNumberOfIPXChannels; 
  uwCurrentChannel ++
 )
 /* if the slot is used, and can transmit data to another individual player */ 
 if 
 (
     gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].ubf1IsSlotInUse 
  && gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].ubf1IsReadable
 )
  /* return its index value */
  return uwCurrentChannel;
 
 /* else no slot is in use */
 return C_uwNetInvalidChannel;
}
 
/*****************************************************************************
 * 
 * Description: uwL0PCWin95IPXStartBroadcastChannelScan
 *
 * returns the identification number of the first broadcast channel
 * for the protocol
 *
 *****************************************************************************
 *
 * Input:   none
 *
 * Output:  identification of the channel
 *
 *****************************************************************************
 * Creation Date: May 3, 1996         Author: Christophe ROGUET
 *****************************************************************************
 *
 * Modification log:
 *
 * Date:                                Author:
 *
 ****************************************************************************/
tduwNetChannel uwL0PCWin95IPXStartBroadcastChannelScan(void)
{
 tduwNetChannel uwCurrentChannel;
 
 /* scan the array of channel definitions */
 for (uwCurrentChannel = 0; uwCurrentChannel < C_ucL0MaxNumberOfIPXChannels; uwCurrentChannel ++)
 /* if the slot is used, and the channel can send broadcast data */ 
 if 
 (
     gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].ubf1IsSlotInUse 
  && gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].ubf1IsBroadcast
  && gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].ubf1IsReadable
 )
  /* return its index value */
  return uwCurrentChannel;
 
 /* else no slot is in use */
 return C_uwNetInvalidChannel;
}
 
/*****************************************************************************
 * 
 * Description: uwL0PCWin95IPXNextBroadcastChannel
 *
 * returns the identification number of the broadcast channel following
 * the specified one
 *
 *****************************************************************************
 *
 * Input:   uwLastScannedChannel, index returned by the previous call
 *
 * Output:  identification of the channel following the specified one
 *
 *****************************************************************************
 * Creation Date: May 3, 1996         Author: Christophe ROGUET
 *****************************************************************************
 *
 * Modification log:
 *
 * Date:                                Author:
 *
 ****************************************************************************/
tduwNetChannel uwL0PCWin95IPXNextBroadcastChannel(tduwNetChannel uwLastScannedChannel)
{
 tduwNetChannel uwCurrentChannel;
 
 /* 
  * scan the array of channel definitions, starting with the channel following
  * the last channel returned
  */
 for 
 (
  uwCurrentChannel = (tduwNetChannel)(uwLastScannedChannel + 1);
  uwCurrentChannel < C_ucL0MaxNumberOfIPXChannels; 
  uwCurrentChannel ++
 )
 /* if the slot is used, and can transmit broadcast data */ 
 if 
 (
     gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].ubf1IsSlotInUse 
  && gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].ubf1IsBroadcast
  && gs_a_stL0PCWin95IPXChannels[uwCurrentChannel].ubf1IsReadable
 )
  /* return its index value */
  return uwCurrentChannel;
 
 /* else no slot is in use */
 return C_uwNetInvalidChannel;
}
 
 
/*****************************************************************************
 * 
 * Description: eL0PCWin95IPXReadData
 *
 *
 *****************************************************************************
 *
 * Input:   p_uwChannel, pointer on a field to store the identification of
 *          a new channel in case of access to a newly created broacast channel
 *          (irrelevant for the serial layer)
 *          ppData, address of the pointer to retrieve a full message if
 *          necessary.
 *
 * Output:  an error condition.
 *
 *****************************************************************************
 * Creation Date: May 3, 1996         Author: Christophe ROGUET
 *****************************************************************************
 *
 * Modification log:
 *
 * Date:                                Author:
 *
 ****************************************************************************/
NetLib_tdeErrorStatus eL0PCWin95IPXReadData(tduwNetChannel *p_uwChannel, tdpPointer *ppData)
{
 tduwNetChannel uwChannel;
 tdpPointer pToBeCast;
 
 /* verify the validity of the Channel */
 
 if(*p_uwChannel>=C_ucL0MaxNumberOfIPXChannels)
  return NetLib_E_es_InvalidChannel; /* the channel does not exist in the table */
 
 if(!gs_a_stL0PCWin95IPXChannels[*p_uwChannel].ubf1IsSlotInUse)
  return NetLib_E_es_ChannelUninitialized;   /* the slot is not initialized for use */
 
 if(!gs_a_stL0PCWin95IPXChannels[*p_uwChannel].ubf1IsReadable)
  /* is it worth a new error code ? */
  return NetLib_E_es_ChannelUninitialized;   /* the slot should not be accessed yet */
 
 /* differenciate broadcast channel and normal channels */
 
 if(gs_a_stL0PCWin95IPXChannels[*p_uwChannel].ubf1IsBroadcast)
 {
  /* is there already a message in the broadcast queue ? */
  if(eL0PCWin95IPXRemoveMessage(*p_uwChannel, &pToBeCast)==NetLib_E_es_FIFOIsEmpty)
  {
   /* no : try to get a message from the socket */
   do
   {
    /* read the first message available from the socket */
    uwChannel=uwL0PCWin95IPXGetMessage();
    /* until we get a message for the broadcast channel */
   } while(uwChannel != *p_uwChannel && uwChannel != C_uwNetInvalidChannel);
 
   if(uwChannel==C_uwNetInvalidChannel)
           return NetLib_E_es_FIFOIsEmpty; /* error: no message available */
 
   if(eL0PCWin95IPXRemoveMessage(uwChannel, &pToBeCast)!=NetLib_E_es_NoError) { /* normally this call produces no error */
#if defined(NET_USE_DEBUG)
  vDebugS(Net_C_Debug_IPX,"ReadData(): RemoveMessage() should not reach 1");
#endif /* NET_USE_DEBUG */
   }
  }
 
  uwChannel=(tduwNetChannel)pToBeCast;
  /* we retrieved a channel number, now mark the channel as readable */
 
  gs_a_stL0PCWin95IPXChannels[uwChannel].ubf1IsReadable=1;
 
  /*  and retrieve the actual message from this channel*/
 
  if(eL0PCWin95IPXRemoveMessage(uwChannel, ppData)==NetLib_E_es_FIFOIsEmpty) {
#if defined(NET_USE_DEBUG)
  vDebugS(Net_C_Debug_IPX,"ReadData(): RemoveMessage() should not reach 2");
#endif /* NET_USE_DEBUG */
          return NetLib_E_es_ShouldNotReach; /* paranoid: there should be at least one message in the channel */
   }
 
  /* tell the caller which channel this message comes from */
 
  *p_uwChannel=uwChannel;
 
#if defined(NET_USE_DEBUG)
  vDebugSISISI(Net_C_Debug_IPX,"ReadData1 channel",uwChannel,
          "msg type",((tdstNetMessage*)(*ppData))->eMessageType,
          "size",((tdstNetMessage*)(*ppData))->uwMessageSizeInBytes);
#endif /* NET_USE_DEBUG */
 
  return NetLib_E_es_NoError; /* no error */
 }
 
 /* the channel is not a broadcast channel */
 
 /* is there already a message in the incoming queue ? */
 
 if(eL0PCWin95IPXRemoveMessage(*p_uwChannel, ppData)==NetLib_E_es_NoError) {
#if defined(NET_USE_DEBUG)
  vDebugSISISI(Net_C_Debug_IPX,"ReadData2 channel",*p_uwChannel,
          "msg type",((tdstNetMessage*)(*ppData))->eMessageType,
          "size",((tdstNetMessage*)(*ppData))->uwMessageSizeInBytes);
#endif /* NET_USE_DEBUG */
         return NetLib_E_es_NoError; /* no error */
 }
 /* no : try to get a message from the socket */
 
 do
 {
  /* read the first message available from the socket */
  uwChannel=uwL0PCWin95IPXGetMessage();
  /* until we get a message for the requested channel */
 } while(uwChannel != *p_uwChannel && uwChannel != C_uwNetInvalidChannel);
 
 if(uwChannel == C_uwNetInvalidChannel)
  return NetLib_E_es_FIFOIsEmpty; /* error: no message available */
        
 if(eL0PCWin95IPXRemoveMessage(uwChannel, ppData)==NetLib_E_es_FIFOIsEmpty) {
#if defined(NET_USE_DEBUG)
  vDebugS(Net_C_Debug_IPX,"ReadData(): RemoveMessage() should not reach 3");
#endif /* NET_USE_DEBUG */
  return NetLib_E_es_ShouldNotReach; /* paranoid: there should be at least one message in the channel */
 }
#if defined(NET_USE_DEBUG)
  vDebugSISISI(Net_C_Debug_IPX,"ReadData3 channel",uwChannel,
          "msg type",((tdstNetMessage*)(*ppData))->eMessageType,
          "size",((tdstNetMessage*)(*ppData))->uwMessageSizeInBytes);
#endif /* NET_USE_DEBUG */
 
 return NetLib_E_es_NoError; /* no error */
}
 
 
/*****************************************************************************
 * 
 * Description: eL0PCWin95IPXSendData
 *
 *
 *****************************************************************************
 *
 * Input:   uwChannel, channel to send the message into.
 *          pData, pointer on the block to send
 *
 * Output:  an error condition.
 *
 *****************************************************************************
 * Creation Date: May 3, 1996         Author: Christophe ROGUET
 *****************************************************************************
 *
 * Modification log: Flot control.
 *
 * Date: 07/04/97                     Author: David FOURNIER
 *
 *****************************************************************************/
NetLib_tdeErrorStatus eL0PCWin95IPXSendData(tduwNetChannel uwChannel, tdpPointer pData)
{
 unsigned long ulBytestoSend;
 unsigned long ulBytesSent;
 
 /* verify the validity of the Channel */
 
 if(uwChannel>=C_ucL0MaxNumberOfIPXChannels)
  return NetLib_E_es_InvalidChannel; /* the channel does not exist in the table */
 
 if(!gs_a_stL0PCWin95IPXChannels[uwChannel].ubf1IsSlotInUse)
  return NetLib_E_es_ChannelUninitialized;   /* the slot is not initialized for use */
 
 if(!gs_a_stL0PCWin95IPXChannels[uwChannel].ubf1IsReadable)
  /* is it worth a new error code ? */
  return NetLib_E_es_ChannelUninitialized;   /* the slot should not be accessed yet */
 
 if (gs_iNbSendByNetEngine++>=5) return NetLib_E_es_BufferIsFull; /* Flot control. */
 
#if defined(NET_USE_DEBUG)
  vDebugSISISI(Net_C_Debug_IPX,"SendData channel",uwChannel,
          "msg type",((tdstNetMessage*)pData)->eMessageType,
          "size",((tdstNetMessage*)pData)->uwMessageSizeInBytes);
#endif /* NET_USE_DEBUG */
 
 ulBytestoSend=((tdstNetMessage *) pData)->uwMessageSizeInBytes + sizeof(tdstNetMessage);
 
 /* send the message */
 
 if((ulBytesSent=gs_p_stIPXWinSocDesc->m_pfn_i_sendto(gs_iSD, pData, ulBytestoSend, 0,
 (struct sockaddr *)&gs_a_stL0PCWin95IPXChannels[uwChannel].stRemoteAddr,
 sizeof gs_a_stL0PCWin95IPXChannels[uwChannel].stRemoteAddr))==SOCKET_ERROR)
 {
  ulBytesSent=gs_p_stIPXWinSocDesc->m_pfn_i_WSAGetLastError();
 
  if(ulBytesSent==WSAEWOULDBLOCK) {
#if defined(NET_USE_DEBUG)
      vDebugS(Net_C_Debug_IPX,"SendData(): sendto(): would block");
#endif /* NET_USE_DEBUG */
 
    return NetLib_E_es_EmissionInProgress;
  }
#if defined(NET_USE_DEBUG)
  vDebugSI(Net_C_Debug_IPX,"SendData(): sendto(): error",ulBytesSent);
#endif /* NET_USE_DEBUG */
  
  if(ulBytesSent==WSAENETDOWN)
   return NetLib_E_es_SendSocketFailure;
  return NetLib_E_es_UnknownError;
 }
 
 if(ulBytesSent!=ulBytestoSend) {
#if defined(NET_USE_DEBUG)
  vDebugSISI(Net_C_Debug_IPX,"SendData(): msg partly sent. Bytes Sent", ulBytesSent,"BytestoSend",ulBytestoSend);
#endif /* NET_USE_DEBUG */
  
  return NetLib_E_es_MessagePartlySent;
 }
 /* free the message buffer */
  vFree(pData);
 
#if defined(NET_USE_DEBUG)
  vDebugSIS(Net_C_Debug_IPX,"SendData()", uwChannel," msg correctly sent");
#endif /* NET_USE_DEBUG */
 return NetLib_E_es_NoError;
}
 
/*****************************************************************************
 * 
 * Description: eL0PCWin95IPXQueryChannelStatus
 *
 * returns the current status of the channel
 *
 *****************************************************************************
 *
 * Input:   uwChannel, channel to query
 *
 * Output:  an error condition.
 *
 *****************************************************************************
 * Creation Date: May 3, 1996         Author: Christophe ROGUET
 *****************************************************************************
 *
 * Modification log:
 *
 * Date:                                Author:
 *
 ****************************************************************************/
NetLib_tdeErrorStatus eL0PCWin95IPXQueryChannelStatus(tduwNetChannel uwChannel)
{
        /* verify the validity of the Channel */
 
        if(uwChannel>=C_ucL0MaxNumberOfIPXChannels)
                return NetLib_E_es_InvalidChannel; /* the channel does not exist in the table */
 
        if(!gs_a_stL0PCWin95IPXChannels[uwChannel].ubf1IsSlotInUse)
                return NetLib_E_es_ChannelUninitialized;   /* the slot is not initialized for use */
 
        if(!gs_a_stL0PCWin95IPXChannels[uwChannel].ubf1IsReadable)
                /* is it worth a new error code ? */
                return NetLib_E_es_ChannelUninitialized;   /* the slot should not be accessed yet */
 
        /* what else to report ???? */
        return NetLib_E_es_NoError;
}
/*****************************************************************************
 * 
 * Description: eL0PCWin95IPXInit
 *
 * initializes the global socket descriptor used for all channels
 *
 *****************************************************************************
 *
 * Input:   port number to use
 *              
 * Output:  none
 *
 * Remark: Due to a bug in wsock32.dll for win95, we can't enumerate the IPX 
 * addresses of the host, so we bind the socket to our port and then read the 
 * address at which the system has bound our socket. This might not be reliable.
 *
 *****************************************************************************
 * Creation Date: May 3, 1996         Author: Christophe ROGUET
 *****************************************************************************
 *
 * Modification log: separating initialisation of socket library
 * from initialisation of global socket.  Use a temporary socket
 * in order to obtain the adress of the machine
 *
 * Date: May,7 1996                   Author: Albert Pais
 *
 ****************************************************************************/
 
 NetLib_tdeErrorStatus eL0PCWin95IPXInit(unsigned short uwPortNumber)
 {
	 SOCKADDR_IPX my_name;          /* the address to bind the socket to */
	 int iAddrLength;                        /* length of the address structure */
	 int iTempSock;
 
	 /* open an IPX datagram socket */
	 iTempSock = gs_p_stIPXWinSocDesc->m_pfn_ui_socket(AF_IPX, SOCK_DGRAM, NSPROTO_IPX);
 
	 if(iTempSock==INVALID_SOCKET)
		 return NetLib_E_es_InitialisationSocketError;
        
	 /* bind our socket to our specific port */
	 my_name.sa_family = AF_IPX;
	 my_name.sa_socket = gs_p_stIPXWinSocDesc->m_pfn_uw_htons(uwPortNumber);  /* htons() is intended for IP socket numbers, but should work with IPX socket numbers as well */
 
	 /* clear the address fields */
	 memset(my_name.sa_netnum, 0, sizeof my_name.sa_netnum);
	 memset(my_name.sa_nodenum, 0, sizeof my_name.sa_nodenum);
 
	 if(gs_p_stIPXWinSocDesc->m_pfn_i_bind(iTempSock, (struct sockaddr *)&my_name, sizeof my_name)<0)
		 return NetLib_E_es_InitialisationSocketError;
	 /* now that the socket is bound, read our address and store it in our global variable */
	 memset(&gs_stMyAddress, 0, sizeof (SOCKADDR_IPX));
	 iAddrLength=sizeof (SOCKADDR_IPX);
	 if(gs_p_stIPXWinSocDesc->m_pfn_i_getsockname(iTempSock, (struct sockaddr *)&gs_stMyAddress, &iAddrLength)==SOCKET_ERROR)
		 return NetLib_E_es_InitialisationSocketError;
	 if(gs_p_stIPXWinSocDesc->m_pfn_i_closesocket(iTempSock)==SOCKET_ERROR)
		 return NetLib_E_es_InitialisationSocketError;
 
	 gs_bL0PCWin95IPXInitState = gs_bL0PCWin95IPXInitState |(char)0x02;
	 return NetLib_E_es_NoError;
}
 
/*****************************************************************************
 * 
 * Description: eL0PCWin95IPXAddPort
 *
 * Creates a new port
 *
 *****************************************************************************
 *
 * Input:   New port number
 *              
 * Output:  a NetLib_tdeErrorStatus
 *
 *****************************************************************************
 * Creation Date: May 3, 1996         Author: Albert PAIS
 * Comment : code from old function eLevel0InitWin95PCIPX developped
 * by Christophe ROGUET
 ****************************************************************************/
NetLib_tdeErrorStatus _NET_CALLING_CONV_ eL0PCWin95IPXAddPort(unsigned short uwPortNumber)
{
 SOCKADDR_IPX my_name;          /* the address to bind the socket to */
 int iOpt;                      /* parameter used for socket option setting */
 unsigned long ulOpt;           /* parameter used for socket option setting */
 int iErrorCode;
 
 /* is the system properly initialized ? */
 
 if(!(gs_bL0PCWin95IPXInitState&0x01))
  return NetLib_E_es_ProtocolNotInitialized;
 
 if(!(gs_bL0PCWin95IPXInitState&0x02))
  return NetLib_E_es_InitialisationSocketError;
 
 /* if a previous socket is opened, clear it :*/
 if(gs_iSD!=INVALID_SOCKET)
 {
  vL0PCWin95IPXClosePort(gs_a_stL0PCWin95IPXChannels[0].stRemoteAddr.sa_socket);
 }
        
 /* open an IPX datagram socket */
 gs_iSD=gs_p_stIPXWinSocDesc->m_pfn_ui_socket(AF_IPX, SOCK_DGRAM, NSPROTO_IPX);
 
 if(gs_iSD==INVALID_SOCKET)
        return NetLib_E_es_InitialisationSocketError;
        
 /* bind our socket to our specific port */
 my_name.sa_family = AF_IPX;
 my_name.sa_socket = gs_p_stIPXWinSocDesc->m_pfn_uw_htons(uwPortNumber);  /* htons() is intended for IP socket numbers, but should work with IPX socket numbers as well */
 
 /* clear the address fields */
 memset(my_name.sa_netnum, 0, sizeof my_name.sa_netnum);
        memset(my_name.sa_nodenum, 0, sizeof my_name.sa_nodenum);
 
 if(gs_p_stIPXWinSocDesc->m_pfn_i_bind(gs_iSD, (struct sockaddr *)&my_name, sizeof my_name)<0)
 {
   long lerrorcode;
   lerrorcode=gs_p_stIPXWinSocDesc->m_pfn_i_WSAGetLastError();
  gs_p_stIPXWinSocDesc->m_pfn_i_closesocket(gs_iSD);
  gs_iSD = INVALID_SOCKET;
  return NetLib_E_es_InitialisationSocketError;
 }
 
 /* enable broadcast sends */
 
 iOpt=~0;
 if(gs_p_stIPXWinSocDesc->m_pfn_i_setsockopt(gs_iSD, SOL_SOCKET, SO_BROADCAST, (char *)&iOpt, sizeof(iOpt))<0) 
 {
  gs_p_stIPXWinSocDesc->m_pfn_i_closesocket(gs_iSD);
  gs_iSD = INVALID_SOCKET;
  return NetLib_E_es_InitialisationSocketError;
 }
 
 /* set non blocking mode on this socket */
 
 ulOpt=(unsigned long)~0L;
 if(gs_p_stIPXWinSocDesc->m_pfn_i_ioctlsocket(gs_iSD, FIONBIO, &ulOpt)==SOCKET_ERROR) {
  iErrorCode=gs_p_stIPXWinSocDesc->m_pfn_i_WSAGetLastError();
  return NetLib_E_es_InitialisationSocketError;
 }
 
 /* set the size of send and receive buffers */
 
 iOpt=1500;
 if(gs_p_stIPXWinSocDesc->m_pfn_i_setsockopt(gs_iSD, SOL_SOCKET, SO_SNDBUF, (char *)&iOpt, sizeof(iOpt))<0) 
 {
  gs_p_stIPXWinSocDesc->m_pfn_i_closesocket(gs_iSD);
  gs_iSD = INVALID_SOCKET;
  return NetLib_E_es_InitialisationSocketError;
 }
 
 iOpt=1500;
 if(gs_p_stIPXWinSocDesc->m_pfn_i_setsockopt(gs_iSD, SOL_SOCKET, SO_RCVBUF, (char *)&iOpt, sizeof(iOpt))<0) 
 {
  gs_p_stIPXWinSocDesc->m_pfn_i_closesocket(gs_iSD);
  gs_iSD = INVALID_SOCKET;
  return NetLib_E_es_InitialisationSocketError;
 }
 
 /* initialize channel 0 as broadcast channel  */
 /* the broadcast channel is always in use */
 gs_a_stL0PCWin95IPXChannels[0].ubf1IsSlotInUse = 1;
 /* this is a channel usable to send only broadcast messages */
 gs_a_stL0PCWin95IPXChannels[0].ubf1IsBroadcast = 1;
 gs_a_stL0PCWin95IPXChannels[0].ubf1IsReadable = 1;
 
 /* set the destination adress : broadcast address */
 gs_a_stL0PCWin95IPXChannels[0].stRemoteAddr.sa_family = AF_IPX;
 memset(gs_a_stL0PCWin95IPXChannels[0].stRemoteAddr.sa_netnum, 0x00, 
             sizeof gs_a_stL0PCWin95IPXChannels[0].stRemoteAddr.sa_netnum);
 memset(gs_a_stL0PCWin95IPXChannels[0].stRemoteAddr.sa_nodenum, 0xFF, 
             sizeof gs_a_stL0PCWin95IPXChannels[0].stRemoteAddr.sa_nodenum);
 gs_a_stL0PCWin95IPXChannels[0].stRemoteAddr.sa_socket = gs_p_stIPXWinSocDesc->m_pfn_uw_htons(uwPortNumber);
 
 /**/
 gs_a_stL0PCWin95IPXChannels[0].ucHead = gs_a_stL0PCWin95IPXChannels[0].ucTail = 0;
 
 /* the channel is working properly */
 gs_a_stL0PCWin95IPXChannels[0].eChannelStatus = E_ts_OK;
 
 gs_bL0PCWin95IPXInitState = gs_bL0PCWin95IPXInitState | (char)0x04;
 return NetLib_E_es_NoError;
}
 
/*
////////////////////////////////////////////////////////////////////////////////
Description : vL0PCWin95IPXCloseProtocol(void)
ipx-Level 0 closing function
//////////////////////////////////////////////////////////////////////////////// 
Input :
none
////////////////////////////////////////////////////////////////////////////////
Output :
none
////////////////////////////////////////////////////////////////////////////////
Creation date : May 6,96
Author : Albert Pais
////////////////////////////////////////////////////////////////////////////////
*/
void vL0PCWin95IPXCloseProtocol(void)
{
 if(gs_bL0PCWin95IPXInitState!=0) {
	 gs_p_stIPXWinSocDesc->m_pfn_i_closesocket(gs_iSD);
	 gs_iSD = INVALID_SOCKET;
	 gs_bL0PCWin95IPXInitState = 0x00;
 
	 eNetRestoreWinSock();
	 gs_cIsWinsocDescInit=0;
 
	 vLevel1RemoveProtocol(E_Net_pr_Windows95IPXProtocol);
 }
}
 
/*****************************************************************************
 * Description: eL0PCWin95IPXEngine
 *****************************************************************************
 * Input:   none
 * Output:  none
 *****************************************************************************
 * Creation Date: May 3, 1996         Author: David FOURNIER
 ****************************************************************************/
void vL0PCWin95IPXEngine(void)
{
  gs_iNbSendByNetEngine=0;
}
 
/*****************************************************************************
 * 
 * Description: vL0PCWin95IPXOpenProtocol
 *
 * initialize the specified interface structure with the correct function
 * pointers to enable a protocol-independent data transmission process.
 *
 *****************************************************************************
 *
 * Input:   p_stProtocolInterface, pointer to the interface structure
 *                      uwPortNumber, port number to bind the socket to
 *                      
 * Output:  none
 *
 *****************************************************************************
 * Creation Date: May 3, 1996         Author: Christophe ROGUET
 *****************************************************************************
 *
 * Modification log:
 *
 * Date:                                Author:
 *
 ****************************************************************************/
void _NET_CALLING_CONV_ vL0PCWin95IPXOpenProtocol(void)
{
 tduwNetChannel uwChannelToInit;
 tdstNetProtocolInterface *p_stProtocolInterface;
 
 
#if defined(NET_USE_DEBUG)
  vDebugClear(Net_C_Debug_IPX);
  vDebugS(Net_C_Debug_IPX,"Netlib - Level 0 - IPX WIN95 : Log file");
#endif /* NET_USE_DEBUG */
 
 gs_bL0PCWin95IPXInitState = 0x00;
 
 if(!gs_cIsWinsocDescInit) {
	 if(eNetGetWinSockDesc(&gs_p_stIPXWinSocDesc)!=NetLib_E_es_NoError)
		 return ;
	 gs_cIsWinsocDescInit=~0;
 }
 
 /* initialize socket library and own adress*/
 if(eL0PCWin95IPXInit(1500)!=NetLib_E_es_NoError)
  return;
 
 p_stProtocolInterface=pstLevel1AddProtocol();
 
 /* logical identification of the protocol */
 p_stProtocolInterface->eProtocol = E_Net_pr_Windows95IPXProtocol;
 
 /* setup the function pointers */
 p_stProtocolInterface->fn_uwStartChannelScan = uwL0PCWin95IPXStartChannelScan;
 p_stProtocolInterface->fn_uwStartBroadcastChannelScan= uwL0PCWin95IPXStartBroadcastChannelScan;
 p_stProtocolInterface->fn_uwNextChannel= uwL0PCWin95IPXNextChannel;
 p_stProtocolInterface->fn_uwNextBroadcastChannel= uwL0PCWin95IPXNextBroadcastChannel;
 p_stProtocolInterface->fn_eReadData= eL0PCWin95IPXReadData;
 p_stProtocolInterface->fn_eSendData= eL0PCWin95IPXSendData;
 p_stProtocolInterface->fn_eQueryChannelStatus= eL0PCWin95IPXQueryChannelStatus;
 p_stProtocolInterface->fn_vLevel0NetEngine = vL0PCWin95IPXEngine;
 p_stProtocolInterface->fn_vCloseChannel = vL0PCWin95IPXCloseChannel;
 p_stProtocolInterface->fn_vLevel0CloseProtocol = vL0PCWin95IPXCloseProtocol;
 p_stProtocolInterface->eIsInternet = 0;
 
 /* initialize all other slots as unused channels */
 
 for (uwChannelToInit = 0; uwChannelToInit < C_ucL0MaxNumberOfIPXChannels; uwChannelToInit ++)
 {
  /* the channel is not used yet  */
  gs_a_stL0PCWin95IPXChannels[uwChannelToInit].ubf1IsSlotInUse = 0;
  /* this is a channel usable to send private messages */
  gs_a_stL0PCWin95IPXChannels[uwChannelToInit].ubf1IsBroadcast = 0;
  gs_a_stL0PCWin95IPXChannels[uwChannelToInit].ubf1IsReadable = 0;
 
  /* initialize the destination address: */
  gs_a_stL0PCWin95IPXChannels[uwChannelToInit].stRemoteAddr.sa_family=AF_IPX;
  memset(gs_a_stL0PCWin95IPXChannels[uwChannelToInit].stRemoteAddr.sa_nodenum, 0x00, 
              sizeof gs_a_stL0PCWin95IPXChannels[uwChannelToInit].stRemoteAddr.sa_nodenum);
  memset(gs_a_stL0PCWin95IPXChannels[uwChannelToInit].stRemoteAddr.sa_netnum, 0x00, 
              sizeof gs_a_stL0PCWin95IPXChannels[uwChannelToInit].stRemoteAddr.sa_netnum);
  gs_a_stL0PCWin95IPXChannels[uwChannelToInit].stRemoteAddr.sa_socket=0;
 
  /* the channel is working properly */
  gs_a_stL0PCWin95IPXChannels[uwChannelToInit].eChannelStatus = E_ts_OK;
 }
 
 gs_iSD = INVALID_SOCKET;
 gs_bL0PCWin95IPXInitState = gs_bL0PCWin95IPXInitState | (char)0x01;
}
 
/*
////////////////////////////////////////////////////////////////////////////////
Description : eL0PCWin95IPXIsProtocolSet
Check if the protocol is correctly initialize or not
//////////////////////////////////////////////////////////////////////////////// 
Input :
none
////////////////////////////////////////////////////////////////////////////////
Output :
NetLib_E_es_ProtocolNotInitialized : the vLevel0SetupWin95PCIPXInterface has not
been called yet or it has failed, the application should call the level 2
eInitializeGlobalData function.
NetLib_E_es_InitialisationSocketError : the eLevel0InitWin95PCIPX function has not been
called yet or it has failed, the application should call the level 2 
eInitializeGlobalData function
NetLib_E_es_NoPortSelected : the eL0PCWin95IPXAddPort has not been called yet. The 
protocol has been correctly initialised, it can be used, but it won't give any
results since no port is avaible.  The application should call it.
////////////////////////////////////////////////////////////////////////////////
Creation date : June 4,96
Author : Albert Pais
////////////////////////////////////////////////////////////////////////////////
*/
NetLib_tdeErrorStatus _NET_CALLING_CONV_ eL0PCWin95IPXIsProtocolSet(void)
{
 if(!(gs_bL0PCWin95IPXInitState&0x01))
  return NetLib_E_es_ProtocolNotInitialized;
 if(!(gs_bL0PCWin95IPXInitState&0x02))
  return NetLib_E_es_InitialisationSocketError;
 if(!(gs_bL0PCWin95IPXInitState&0x04))
  return NetLib_E_es_NoPortSelected;
 return NetLib_E_es_True;
}
 
/*
////////////////////////////////////////////////////////////////////////////////
Description : vL0PCWin95IPXClosePort
Close the socket bound with the specified port number
//////////////////////////////////////////////////////////////////////////////// 
Input :
The port number to close
////////////////////////////////////////////////////////////////////////////////
Output :
None
////////////////////////////////////////////////////////////////////////////////
Creation date : June 24,96
Author : Albert Pais
////////////////////////////////////////////////////////////////////////////////
*/
 
void _NET_CALLING_CONV_ vL0PCWin95IPXClosePort(unsigned short uwOldPort)
{
 unsigned short uwCurrentChannel;
 if
  (
  (gs_bL0PCWin95IPXInitState&0x04)&&
  (uwOldPort == gs_p_stIPXWinSocDesc->m_pfn_uw_ntohs(gs_a_stL0PCWin95IPXChannels[0].stRemoteAddr.sa_socket))
  )
 {
  gs_p_stIPXWinSocDesc->m_pfn_i_closesocket(gs_iSD);
  gs_iSD = INVALID_SOCKET;
  /* Closes the channel :*/
  for(uwCurrentChannel = 0;uwCurrentChannel<C_ucL0MaxNumberOfIPXChannels;uwCurrentChannel++)
  {
   eL0PCWin95IPXCloseAndResetChannel(uwCurrentChannel);
  }
 
  gs_bL0PCWin95IPXInitState = gs_bL0PCWin95IPXInitState & (char)~0x04;
 }
}
 
/*
////////////////////////////////////////////////////////////////////////////////
Description : eL0PCWin95IPXIsPortAvailable
Check if the port is avaible for use or not
//////////////////////////////////////////////////////////////////////////////// 
Input :
A port number to test
////////////////////////////////////////////////////////////////////////////////
Output :
NetLib_E_es_True if the port is avaible
NetLib_E_es_PortAlreadyUsed if the port is already in use
An error code otherwise
////////////////////////////////////////////////////////////////////////////////
Creation date : June 24,96
Author : Albert Pais
////////////////////////////////////////////////////////////////////////////////
*/
NetLib_tdeErrorStatus _NET_CALLING_CONV_ eL0PCWin95IPXIsPortAvailable(unsigned short uwPortNumber)
{
 SOCKADDR_IPX my_name;          /* the address to bind the socket to */
 int iOpt;                      /* parameter used for socket option setting */
 int iTempSock;					/*temporary socket*/
 NetLib_tdeErrorStatus eErrorReturned;
        
 /* bind to the DLL if necessary */
 if(!gs_cIsWinsocDescInit) {
	 if((eErrorReturned=eNetGetWinSockDesc(&gs_p_stIPXWinSocDesc))!=NetLib_E_es_NoError)
		 return NetLib_E_es_InitialisationSocketError;
	 gs_cIsWinsocDescInit=~0;
 }
 
 /* open an IPX datagram socket */
 iTempSock=gs_p_stIPXWinSocDesc->m_pfn_ui_socket(AF_IPX, SOCK_DGRAM, NSPROTO_IPX);
 
  if(iTempSock==INVALID_SOCKET)
	  return NetLib_E_es_False;
        
 /* bind our socket to our specific port */
 my_name.sa_family = AF_IPX;
 my_name.sa_socket = gs_p_stIPXWinSocDesc->m_pfn_uw_htons(uwPortNumber);  /* htons() is intended for IP socket numbers, but should work with IPX socket numbers as well */
 
 /* clear the address fields */
 memset(my_name.sa_netnum, 0, sizeof my_name.sa_netnum);
 memset(my_name.sa_nodenum, 0, sizeof my_name.sa_nodenum);
 
 if(gs_p_stIPXWinSocDesc->m_pfn_i_bind(iTempSock, (struct sockaddr *)&my_name, sizeof my_name)<0)
 {
  if(gs_p_stIPXWinSocDesc->m_pfn_i_WSAGetLastError()==WSAEADDRINUSE)
   eErrorReturned = NetLib_E_es_PortAlreadyUsed;
  else
   eErrorReturned = NetLib_E_es_False;
  gs_p_stIPXWinSocDesc->m_pfn_i_closesocket(iTempSock);
  return eErrorReturned;
 }
 
 iOpt=~0;
 if(gs_p_stIPXWinSocDesc->m_pfn_i_setsockopt(iTempSock, SOL_SOCKET, SO_BROADCAST, (char *)&iOpt, sizeof(iOpt))<0) 
 {
  gs_p_stIPXWinSocDesc->m_pfn_i_closesocket(iTempSock);
  return NetLib_E_es_False;
 }
 /* if we came here, it means that there is no problem with this port number*/
 gs_p_stIPXWinSocDesc->m_pfn_i_closesocket(iTempSock);
 return NetLib_E_es_True;
}
 
/*
////////////////////////////////////////////////////////////////////////////////
Description : eL0PCWin95IPXIsProtocolAvailable
Check if the protocol is avaible for use or not
//////////////////////////////////////////////////////////////////////////////// 
Input :
A port number to test
////////////////////////////////////////////////////////////////////////////////
Output :
NetLib_E_es_True if the port is avaible
An error code otherwise
////////////////////////////////////////////////////////////////////////////////
Creation date : June 24,96
Author : Albert Pais
////////////////////////////////////////////////////////////////////////////////
*/
NetLib_tdeErrorStatus _NET_CALLING_CONV_ eL0PCWin95IPXIsProtocolAvailable(void)
{
 unsigned short uwPortNumberTest;
 NetLib_tdeErrorStatus eErrorReturned;
 
 /* bind to the DLL if necessary */
 if(!gs_cIsWinsocDescInit) {
	 if((eErrorReturned=eNetGetWinSockDesc(&gs_p_stIPXWinSocDesc))!=NetLib_E_es_NoError)
		 return NetLib_E_es_InitialisationSocketError;
	 gs_cIsWinsocDescInit=~0;
 }
 
 uwPortNumberTest = 1000;
 while
  (
  ((eErrorReturned = eL0PCWin95IPXIsPortAvailable(uwPortNumberTest))==NetLib_E_es_PortAlreadyUsed)&&
  (uwPortNumberTest<1100)
  )
  uwPortNumberTest++;
 return eErrorReturned;
}
 
/*
////////////////////////////////////////////////////////////////////////////////
Description : eL0PCWin95IPXIsPortOpened
Returns wether the port is opened or not
//////////////////////////////////////////////////////////////////////////////// 
Input :
A port
////////////////////////////////////////////////////////////////////////////////
Output :
an tdeErrorStatus
////////////////////////////////////////////////////////////////////////////////
Creation date : June 27,96
Author : Albert Pais
////////////////////////////////////////////////////////////////////////////////
*/
NetLib_tdeErrorStatus _NET_CALLING_CONV_ eL0PCWin95IPXIsPortOpened(unsigned short uwPortNumber)
{
 if(!(gs_bL0PCWin95IPXInitState&0x04))
  return NetLib_E_es_NoPortSelected;
 
 if(!(uwPortNumber == gs_p_stIPXWinSocDesc->m_pfn_uw_ntohs(gs_a_stL0PCWin95IPXChannels[0].stRemoteAddr.sa_socket)))
  return NetLib_E_es_False;
 
 return NetLib_E_es_True;
}
