/****************************************************************************** * Product: # # # # # ### ###### * # # # # # # # # * # # # # # # # # * # # # # # ###### * # # # # # # # # * # # # # # # # # * ##### # # ####### ####### ### ###### * * File: ux_comms.c * Description: Generic communications routines. * * Version: %I% * Dated: %D% * Copyright: P.D. Smart, 1994-2019 * * History: 1.0 - Initial Release. * ****************************************************************************** * This source file is free software: you can redistribute it and#or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This source file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . ******************************************************************************/ /* Bring in system header files. */ #include #include #include #include #include #include #include #if defined(_WIN32) #include #include #include #include #include #endif #if defined(SOLARIS) || defined(SUNOS) || defined(LINUX) || defined(ZPU) #include #include #include #include #include #include #include #endif #if defined(SOLARIS) #include #endif #include #include /* Indicate that we are a C module for any header specifics. */ #define UX_COMMS_C /* Bring in specific header files. */ #include "ux.h" /* Local module variables. */ static SL_GLOBALS Sl; /* 16 bit CRC lookup table. */ SL_CRCTAB tCRCTable[] = { { 0, 0 }, { 193, 192 }, { 129, 193 }, { 64, 1 }, { 1, 195 }, { 192, 3 }, { 128, 2 }, { 65, 194 }, { 1, 198 }, { 192, 6 }, { 128, 7 }, { 65, 199 }, { 0, 5 }, { 193, 197 }, { 129, 196 }, { 64, 4 }, { 1, 204 }, { 192, 12 }, { 128, 13 }, { 65, 205 }, { 0, 15 }, { 193, 207 }, { 129, 206 }, { 64, 14 }, { 0, 10 }, { 193, 202 }, { 129, 203 }, { 64, 11 }, { 1, 201 }, { 192, 9 }, { 128, 8 }, { 65, 200 }, { 1, 216 }, { 192, 24 }, { 128, 25 }, { 65, 217 }, { 0, 27 }, { 193, 219 }, { 129, 218 }, { 64, 26 }, { 0, 30 }, { 193, 222 }, { 129, 223 }, { 64, 31 }, { 1, 221 }, { 192, 29 }, { 128, 28 }, { 65, 220 }, { 0, 20 }, { 193, 212 }, { 129, 213 }, { 64, 21 }, { 1, 215 }, { 192, 23 }, { 128, 22 }, { 65, 214 }, { 1, 210 }, { 192, 18 }, { 128, 19 }, { 65, 211 }, { 0, 17 }, { 193, 209 }, { 129, 208 }, { 64, 16 }, { 1, 240 }, { 192, 48 }, { 128, 49 }, { 65, 241 }, { 0, 51 }, { 193, 243 }, { 129, 242 }, { 64, 50 }, { 0, 54 }, { 193, 246 }, { 129, 247 }, { 64, 55 }, { 1, 245 }, { 192, 53 }, { 128, 52 }, { 65, 244 }, { 0, 60 }, { 193, 252 }, { 129, 253 }, { 64, 61 }, { 1, 255 }, { 192, 63 }, { 128, 62 }, { 65, 254 }, { 1, 250 }, { 192, 58 }, { 128, 59 }, { 65, 251 }, { 0, 57 }, { 193, 249 }, { 129, 248 }, { 64, 56 }, { 0, 40 }, { 193, 232 }, { 129, 233 }, { 64, 41 }, { 1, 235 }, { 192, 43 }, { 128, 42 }, { 65, 234 }, { 1, 238 }, { 192, 46 }, { 128, 47 }, { 65, 239 }, { 0, 45 }, { 193, 237 }, { 129, 236 }, { 64, 44 }, { 1, 228 }, { 192, 36 }, { 128, 37 }, { 65, 229 }, { 0, 39 }, { 193, 231 }, { 129, 230 }, { 64, 38 }, { 0, 34 }, { 193, 226 }, { 129, 227 }, { 64, 35 }, { 1, 225 }, { 192, 33 }, { 128, 32 }, { 65, 224 }, { 1, 160 }, { 192, 96 }, { 128, 97 }, { 65, 161 }, { 0, 99 }, { 193, 163 }, { 129, 162 }, { 64, 98 }, { 0, 102 }, { 193, 166 }, { 129, 167 }, { 64, 103 }, { 1, 165 }, { 192, 101 }, { 128, 100 }, { 65, 164 }, { 0, 108 }, { 193, 172 }, { 129, 173 }, { 64, 109 }, { 1, 175 }, { 192, 111 }, { 128, 110 }, { 65, 174 }, { 1, 170 }, { 192, 106 }, { 128, 107 }, { 65, 171 }, { 0, 105 }, { 193, 169 }, { 129, 168 }, { 64, 104 }, { 0, 120 }, { 193, 184 }, { 129, 185 }, { 64, 121 }, { 1, 187 }, { 192, 123 }, { 128, 122 }, { 65, 186 }, { 1, 190 }, { 192, 126 }, { 128, 127 }, { 65, 191 }, { 0, 125 }, { 193, 189 }, { 129, 188 }, { 64, 124 }, { 1, 180 }, { 192, 116 }, { 128, 117 }, { 65, 181 }, { 0, 119 }, { 193, 183 }, { 129, 182 }, { 64, 118 }, { 0, 114 }, { 193, 178 }, { 129, 179 }, { 64, 115 }, { 1, 177 }, { 192, 113 }, { 128, 112 }, { 65, 176 }, { 0, 80 }, { 193, 144 }, { 129, 145 }, { 64, 81 }, { 1, 147 }, { 192, 83 }, { 128, 82 }, { 65, 146 }, { 1, 150 }, { 192, 86 }, { 128, 87 }, { 65, 151 }, { 0, 85 }, { 193, 149 }, { 129, 148 }, { 64, 84 }, { 1, 156 }, { 192, 92 }, { 128, 93 }, { 65, 157 }, { 0, 95 }, { 193, 159 }, { 129, 158 }, { 64, 94 }, { 0, 90 }, { 193, 154 }, { 129, 155 }, { 64, 91 }, { 1, 153 }, { 192, 89 }, { 128, 88 }, { 65, 152 }, { 1, 136 }, { 192, 72 }, { 128, 73 }, { 65, 137 }, { 0, 75 }, { 193, 139 }, { 129, 138 }, { 64, 74 }, { 0, 78 }, { 193, 142 }, { 129, 143 }, { 64, 79 }, { 1, 141 }, { 192, 77 }, { 128, 76 }, { 65, 140 }, { 0, 68 }, { 193, 132 }, { 129, 133 }, { 64, 69 }, { 1, 135 }, { 192, 71 }, { 128, 70 }, { 65, 134 }, { 1, 130 }, { 192, 66 }, { 128, 67 }, { 65, 131 }, { 0, 65 }, { 193, 129 }, { 129, 128 }, { 64, 64 }}; /****************************************************************************** * Function: _SL_CalcCRC * Description: Calculate the CRC on a buffer. * Thread Safe: Yes * Returns: 16bit CRC ******************************************************************************/ UINT _SL_CalcCRC( UCHAR *szBuf, /* I: Data buffer to perform CRC on */ UINT nBufLen ) /* I: Length of data buffer */ { /* Local variables. */ UINT nCRC = 0; UINT nNdx; UINT nTabNdx; UCHAR cHiCRC = 0; UCHAR cLoCRC = 0; /* Loop through each byte, using the CRC table to update the CRC value. */ for(nNdx=0; nNdx < nBufLen; nNdx++) { nTabNdx = cHiCRC ^ (UCHAR)szBuf[nNdx]; cHiCRC = cLoCRC ^ tCRCTable[nTabNdx].cHiCRC; cLoCRC = tCRCTable[nTabNdx].cLoCRC; } /* Place CRC lower and upper bytes into one integer for caller. */ nCRC = cLoCRC | (cHiCRC << 8); /* Return calculated CRC to caller. */ return(nCRC); } /****************************************************************************** * Function: _SL_CheckCRC * Description: Validate the CRC on a buffer with the one given. * Thread Safe: Yes * Returns: R_OK - CRC match. * R_FAIL - CRC failure. * ******************************************************************************/ UINT _SL_CheckCRC( UCHAR *szBuf, /* I: Data buffer to calc CRC on */ UINT nBufLen ) /* I: Length of data in buffer */ { /* Local variables. */ UINT nBufferCRC; UINT nPacketCRC; /* Get packet CRC from buffer. */ nPacketCRC = szBuf[nBufLen] + (szBuf[nBufLen-1] << 8); /* Calculate comparison CRC based on data in buffer. */ nBufferCRC = _SL_CalcCRC( szBuf, nBufLen-2) ; /* Debugging code. */ #if defined(UX_DEBUG) if(nPacketCRC != nBufferCRC) { printf("CRC failed (%u, %u)\n", nPacketCRC, nBufferCRC); fflush(stdout); } #endif /* Passed or failed? */ return(nPacketCRC == nBufferCRC ? R_OK : R_FAIL); } /****************************************************************************** * Function: _SL_FdBlocking * Description: Set a file descriptors blocking mode. * Thread Safe: Yes * Returns: R_OK - Mode set. * R_FAIL - Couldnt set mode. ******************************************************************************/ int _SL_FdBlocking( int nFd, /* File descr to perform action on*/ int nBlock ) /* Block (1) or non-blocking (0) */ { /* Local variables. */ int nReturn = R_FAIL; #if defined(_WIN32) ULNG lBlock; #endif #if defined(SOLARIS) || defined(SUNOS) || defined(LINUX) || defined(ZPU) /* Set the mode accordingly. */ if(fcntl(nFd, F_SETFL, nBlock == 1 ? 0 : FNDELAY) >= 0) nReturn = R_OK; #endif #if defined(_WIN32) /* Set up blocking flag. */ lBlock = (nBlock == 1 ? 0L : 1L); /* Set the mode accordingly. */ if(ioctlsocket(nFd, FIONBIO, &lBlock) == 0) nReturn = R_OK; #endif /* Finished, get out!! */ return( nReturn ); } /****************************************************************************** * Function: _SL_AcceptClient * Description: Accept an incoming request from a client. Builds a duplicate * table entry for the client (if one doesnt already exist) and * allocates a unique Channel Id to it. * Thread Safe: No, ensures only SL library thread may enter. * Returns: R_OK - Comms functionality initialised. * R_FAIL - Initialisation failed, see Errno. * E_NOMEM - Memory exhaustion. ******************************************************************************/ int _SL_AcceptClient( UINT nServerSd, /* I: Server Socket Id */ SL_NETCONS *spServer, /* I: Server descr record */ SL_NETCONS **spNewClnt ) /* O: New client */ { /* Local variables. */ struct linger sLinger; struct sockaddr_in sPeer; int nReturn = R_FAIL; UINT nChanId = DEF_CHANID; UINT nResult = sizeof(sPeer); int nTmpSd; char *szFunc = "_SL_AcceptClient"; LINKLIST *spNext; SL_NETCONS *spNetCon; SL_THREAD_ONLY; /* Accept the connection to yield client information. */ if( (nTmpSd=accept(nServerSd, (struct sockaddr *)&sPeer, &nResult)) < 0 ) { Errno = E_BADACCEPT; return(nReturn); } /* Need to find the next channel ID. */ for(spNetCon=(SL_NETCONS *)StartItem(Sl.spHead, &spNext); spNetCon != NULL; spNetCon=(SL_NETCONS *)NextItem(&spNext)) { /* Find the greatest Channel Id. */ if(spNetCon->nChanId > nChanId) nChanId = spNetCon->nChanId; } /* New entry, need to duplicate masters record. */ if((spNetCon=(SL_NETCONS *)malloc(sizeof(SL_NETCONS))) == NULL) { Lgr(LOG_DEBUG, szFunc, "Couldnt malloc (%d) bytes", sizeof(SL_NETCONS)); Errno = E_NOMEM; } else { /* Duplicate!!! Watch for dynamic copyrights! */ memcpy((UCHAR *)spNetCon, (UCHAR *)spServer, sizeof(SL_NETCONS)); /* Try and allocate an initial receive buffer. */ if((spNetCon->spRecvBuf=(UCHAR *)malloc(DEF_INITRECVBUF))==NULL) { Lgr(LOG_DEBUG, szFunc, "Couldnt malloc (%d) bytes", DEF_INITRECVBUF); Errno = E_NOMEM; free(spNetCon); } else { /* Add in new specific information. */ spNetCon->nSd = nTmpSd; spNetCon->nServerPortNo = ntohs(sPeer.sin_port); spNetCon->lServerIPaddr = ntohl(sPeer.sin_addr.s_addr); spNetCon->nChanId = nChanId + 1; spNetCon->nStatus = SSL_UP; spNetCon->spXmitBuf = NULL; spNetCon->nXmitLen = 0; spNetCon->nXmitPos = 0; /* Set up KEEPALIVE, so the underlying keeps an eye on the net/ * processes going up/down. */ if( setsockopt(spNetCon->nSd, SOL_SOCKET, SO_KEEPALIVE, (UCHAR *)&Sl.nSockKeepAlive, sizeof(Sl.nSockKeepAlive)) < 0 ) { Lgr(LOG_WARNING, szFunc, "Couldnt set KEEPALIVE on socket (%d)", spNetCon->nSd); } /* Set up LINGER to be disabled, if we die unexpectedly, the socket * /ports should be freed up. We lose data, but nothing can be done. */ sLinger.l_onoff = 0; sLinger.l_linger = 0; if( setsockopt(spNetCon->nSd, SOL_SOCKET, SO_LINGER, (UCHAR *)&sLinger, sizeof(struct linger)) < 0 ) { Lgr(LOG_WARNING, szFunc, "Couldnt disable LINGER on socket (%d)", spNetCon->nSd); } /* Set the socket to non-blocking mode, not prepared for anything * to bring us to a halt! */ _SL_FdBlocking(spNetCon->nSd, 0); /* Place in NetCon list. */ if(AddItem(&Sl.spHead, &Sl.spTail, SORT_NONE, &spNetCon->nChanId, &spNetCon->lServerIPaddr, spNetCon->szServerName, spNetCon) == R_OK) { /* Finally, call the users control callback to let him * know about the new connection. */ spNetCon->nCntrlCallback(SLC_NEWSERVICE, spNetCon->nChanId, _SL_GetPortNo(spNetCon), spNetCon->lServerIPaddr, spNetCon->nOurPortNo); nReturn = R_OK; } else { free(spNetCon->spRecvBuf); free(spNetCon); } /* If the caller requires a pointer to the new clients control * record then set it up for passback. */ if(spNewClnt != NULL) { *spNewClnt = spNetCon; } } } /* Finished, get out!! */ return( nReturn ); } /****************************************************************************** * Function: _SL_GetPortNo * Description: Get valid port number from structures. * Thread Safe: Yes * Returns: Port Number. ******************************************************************************/ UINT _SL_GetPortNo( SL_NETCONS *spNetCon ) /* I: Connection description */ { /* Local variables. */ UINT nReturn; /* Work out correct callback port number. */ nReturn = (spNetCon->nServerPortNo == 0 ? spNetCon->nOurPortNo : spNetCon->nServerPortNo); /* Return valid port number. */ return(nReturn); } /****************************************************************************** * Function: _SL_Close * Description: Close a client or server connection. * Thread Safe: No, forces SL thread entry only. * Returns: R_OK - Connection closed successfully. * R_FAIL - Failed to close connection, see Errno. * ******************************************************************************/ int _SL_Close( SL_NETCONS *spNetCon, /* I: Connection to sever */ UINT nDoCallback ) /* I: Perform closure callback? */ { /* Local variables. */ int nReturn = R_OK; UCHAR *szFunc = "_SL_Close"; SL_THREAD_ONLY; /* Close the port, no longer needed. */ SocketClose(spNetCon->nSd); /* OK, send a close/fail callback to user code if required. */ if(nDoCallback == TRUE) { spNetCon->nCntrlCallback(SLC_LINKFAIL, spNetCon->nChanId, _SL_GetPortNo(spNetCon), spNetCon->lServerIPaddr, spNetCon->nOurPortNo); } /* Free up receive buffer, not needed. */ if(spNetCon->spRecvBuf != NULL) { free(spNetCon->spRecvBuf); spNetCon->spRecvBuf = NULL; } /* Free up transmit buffer, not needed. */ if(spNetCon->spXmitBuf != NULL) { free(spNetCon->spXmitBuf); spNetCon->spXmitBuf = NULL; } /* Free up control record, no longer needed. */ if(DelItem(&Sl.spHead,&Sl.spTail,spNetCon,NULL,NULL,NULL) == R_FAIL) { Lgr(LOG_WARNING, szFunc, "Couldnt delete server entry (%d, %d, %s)", spNetCon->nChanId, spNetCon->nOurPortNo, SL_HostIPtoString(spNetCon->lServerIPaddr)); /* Set return value to indicate failure. */ nReturn=R_FAIL; } else { /* Free up the control memory. */ free(spNetCon); } /* Return result code to caller. */ return(nReturn); } /****************************************************************************** * Function: _SL_ConnectToServer * Description: Attempt to make a connection with a remote server. * Thread Safe: No, forces SL thread entry only. * Returns: R_OK - * R_FAIL - * E_NOMEM - Memory exhaustion. * E_NOSOCKET - Couldnt allocate a socket for connection. ******************************************************************************/ int _SL_ConnectToServer( SL_NETCONS *spNetCon ) { /* Local variables. */ #if defined(_WIN32) int nWinErr; #endif char *szFunc = "_SL_ConnectToServer"; struct linger sLinger; struct sockaddr_in sServer; SL_THREAD_ONLY; /* Build up info of the Server we intend to connect to. */ memset((UCHAR *)&sServer, '\0', sizeof(struct sockaddr)); sServer.sin_family = AF_INET; sServer.sin_addr.s_addr = htonl(spNetCon->lServerIPaddr); sServer.sin_port = htons((USHRT)spNetCon->nServerPortNo); /* If required (nearly always), create an end point (socket) for our * side of the communications link. */ if(spNetCon->nSd == -1) { if((spNetCon->nSd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { Errno = E_NOSOCKET; return(R_FAIL); } else { /* Set the socket to non-blocking mode, not prepared for anything * to bring us to a halt! */ _SL_FdBlocking(spNetCon->nSd, 0); } } /* Try to connect to the other side. */ if(connect(spNetCon->nSd, (struct sockaddr *)&sServer, sizeof(struct sockaddr)) == -1) { #if defined(_WIN32) /* What was the error...? Under windows its bound to be bad news!!! * Call windows own function (well blow me down and call me * gandolfo) to liberate the error code. */ switch((nWinErr = WSAGetLastError())) #endif #if defined(SOLARIS) || defined(SUNOS) || defined(LINUX) || defined(ZPU) /* What was the error..? Bad news...? */ switch(errno) #endif { /* A previous connect on this socket has now completed, so get * out and finish connection process. */ case EISCONN: break; /* The connection process would block, but the underlying is * still trying to connect. */ case EINPROGRESS: case EWOULDBLOCK: spNetCon->nStatus = SSL_DOWN; Errno = E_BADCONNECT; return(R_FAIL); /* Soft errors which may be resolved dynamically, so just back * off for a while. */ case EADDRINUSE: case EALREADY: case EBADF: case ECONNREFUSED: case EINTR: case ENOTSOCK: case ETIMEDOUT: case EINVAL: case EIO: /* Get rid of socket, no longer needed. */ SocketClose(spNetCon->nSd); spNetCon->nSd = -1; spNetCon->nStatus = SSL_DOWN; Errno = E_NOCONNECT; return(R_FAIL); /* Hard errors which cannot be resolved, mark the connection * as down permanently. */ case EADDRNOTAVAIL: case EAFNOSUPPORT: case EFAULT: case ENETUNREACH: default: /* Get rid of socket, no longer needed. */ SocketClose(spNetCon->nSd); spNetCon->nSd = -1; Lgr(LOG_WARNING, szFunc, "Cannot connect (%s, %s, %d) Error (%d)", spNetCon->szServerName, SL_HostIPtoString(spNetCon->lServerIPaddr), spNetCon->nServerPortNo, #if defined(SOLARIS) || defined(SUNOS) || defined(LINUX) || defined(ZPU) errno); #endif #if defined(_WIN32) nWinErr); #endif spNetCon->nStatus = SSL_FAIL; Errno = E_NOCONNECT; return(R_FAIL); } } /* Set up KEEPALIVE, so the underlying keeps an eye on the net/ * processes going up/down. */ if( setsockopt(spNetCon->nSd, SOL_SOCKET, SO_KEEPALIVE, (UCHAR *)&Sl.nSockKeepAlive, sizeof(Sl.nSockKeepAlive)) < 0 ) { Lgr(LOG_WARNING, szFunc, "Couldnt set KEEPALIVE on socket (%d)", spNetCon->nSd); } /* Set up LINGER to be disabled, if we die unexpectedly, the socket * /ports should be freed up. We lose data, but nothing can be done. */ sLinger.l_onoff = 0; sLinger.l_linger = 0; if( setsockopt(spNetCon->nSd, SOL_SOCKET, SO_LINGER, (UCHAR *)&sLinger, sizeof(struct linger)) < 0 ) { Lgr(LOG_WARNING, szFunc, "Couldnt disable LINGER on socket (%d)", spNetCon->nSd); } /* Finally, call the users control callback to let him * know about the new connection. */ spNetCon->nCntrlCallback(SLC_CONNECT, spNetCon->nChanId, _SL_GetPortNo(spNetCon), spNetCon->lServerIPaddr, spNetCon->nOurPortNo); /* Mark connection as up. */ spNetCon->nStatus = SSL_UP; /* Finished, success, get out!! */ return( R_OK ); } /****************************************************************************** * Function: _SL_ReceiveFromSocket * Description: Receive data from a given socket, growing the receive buffer * if needed. * Thread Safe: No, forces SL thread entry only. * Returns: R_OK - * R_FAIL - * E_NOMEM - Memory exhaustion. * E_BADPARM - Bad parameters passed to function. * E_NOSERVICE - No service on socket. ******************************************************************************/ int _SL_ReceiveFromSocket( SL_NETCONS *spNetCon ) /* IO: Active connection */ { /* Local variables. */ UINT nBufLen; UINT nNdx; int nRet; int nReturn = R_FAIL; #if defined(_WIN32) int nWinErr; #endif char *szFunc = "_SL_ReceiveFromSocket"; UCHAR *spNewBuf; UCHAR *spTmp; UCHAR *spTmp2; SL_THREAD_ONLY; /* Check parameters. */ if( spNetCon == NULL ) { Errno = E_BADPARM; Lgr(LOG_DEBUG, szFunc, "spNetCon is null"); return(nReturn); } do { /* Calculate number of bytes to read. */ if((nBufLen=(spNetCon->nRecvBufLen-spNetCon->nRecvLen)) > MAX_RECVLEN) { nBufLen = MAX_RECVLEN; } /* Read as many bytes as possible from the active socket upto the * size of the receive buffer. If more bytes are available, then * grow the receive buffer to accomodate. */ nRet=recv(spNetCon->nSd, &(spNetCon->spRecvBuf[spNetCon->nRecvLen]), nBufLen, 0); /* If data has been received, then check on the number of bytes read. */ if( nRet > 0 ) { /* Does the number of bytes read fill the buffer? */ if((nRet + spNetCon->nRecvLen) >= spNetCon->nRecvBufLen) { /* For safety's sake, an upper limit on the size of the * receive buffer has to be implemented. If this ceiling * is hit, then assume something is going wrong, so keep * the current buffer, but dump the contents. */ if( spNetCon->nRecvBufLen >= MAX_RECVBUFSIZE ) { Lgr(LOG_WARNING, szFunc, "Exceeded maximum size of recv buffer, dumping"); spNetCon->nRecvLen = 0; break; } /* OK, lets grow the buffer by a fixed size. If no memory * is available, then keep the current buffer, just stop * reading. Later functionality will prune the data. */ if((spNewBuf=(UCHAR *)malloc(spNetCon->nRecvBufLen+ DEF_BUFINCSIZE)) == NULL) { Lgr(LOG_DEBUG, szFunc, "Couldnt malloc (%d) bytes", DEF_BUFINCSIZE); nRet = -1; break; } /* Copy the old buffer onto the new, so we can release it * back to the sys pool. */ for(spTmp=spNetCon->spRecvBuf, spTmp2=spNewBuf, nNdx=spNetCon->nRecvBufLen; nNdx != 0; nNdx--, *spTmp2 = *spTmp, spTmp++, spTmp2++); /* Get rid of old buffer, and update structure pointers * to accomodate the new buffer and new size values. */ free(spNetCon->spRecvBuf); spNetCon->spRecvBuf = spNewBuf; spNetCon->nRecvBufLen += DEF_BUFINCSIZE; /* Log a message indicating that the buffer has grown in size. */ Lgr(LOG_DEBUG, szFunc, "Allocated new receive buffer of %d bytes", spNetCon->nRecvBufLen); } /* Update the total number of bytes held in the receive buffer. */ spNetCon->nRecvLen += nRet; nReturn = R_OK; } } while( nRet > 0 ); #if defined(SOLARIS) || defined(SUNOS) || defined(LINUX) || defined(ZPU) /* Check for errors. Errno should never be anything but EWOULDBLOCK. */ if(nRet < 1 && errno != EWOULDBLOCK) #endif #if defined(_WIN32) /* Get the error code from windows to decide on our course of action. */ nWinErr = WSAGetLastError(); /* Test for errors. */ if(nRet < 1 && nWinErr != EWOULDBLOCK) #endif { Errno = E_NOSERVICE; nReturn = R_FAIL; } /* Finished, get out!! */ return( nReturn ); } /****************************************************************************** * Function: _SL_ProcessRecvBuf * Description: Process the data held in a network connection's receive buffer. * If a complete packet has been assembled and passed a CRC check, * pass the data to the subscribing application via its callback. * Thread Safe: No, forces SL thread entry only. * Returns: R_OK - * R_FAIL - * E_NOMEM - Memory exhaustion. * E_NOSOCKET - Couldnt allocate a socket for connection. ******************************************************************************/ int _SL_ProcessRecvBuf( SL_NETCONS *spNetCon ) { /* Local variables. */ int nReturn = R_OK; UINT nTmpLen; char *szFunc = "_SL_ProcessRecvBuf"; UCHAR *spTmp; SL_THREAD_ONLY; if(spNetCon->nRawMode == FALSE) { do { /* Go through the receive buffer and look for two SYNch characters * followed by a start of message (STX), this identifies the start * of a data packet. Once found, extract the two preceeding bytesi * which indicate the total length of the data packet. */ for(spTmp=spNetCon->spRecvBuf, nTmpLen=0; (spTmp+5) < (spNetCon->spRecvBuf+spNetCon->nRecvLen); spTmp++, nTmpLen=0) { /* Start of packet? */ if( *(spTmp+0) == A_SYN && *(spTmp+1) == A_SYN && *(spTmp+2) == A_STX ) { /* Get length. */ nTmpLen = GetIntFromChar(spTmp+3); /* If there are not enough bytes in the buffer, then loop, * because this probably isnt a valid packet or we havent * yet received the packet in its entirety. */ if((spTmp+nTmpLen+7) <= (spNetCon->nRecvLen+spNetCon->spRecvBuf) /* Using the length, take a peek at the end of the data * packet and establish that it is a valid packet by the * presence of an end of message (ETX). */ && *(spTmp+5+nTmpLen) == A_ETX /* Finally, check to see if the CRC on the data within the * packet matches that stored within it. */ && _SL_CheckCRC(spTmp+5, nTmpLen+2) == R_OK ) { break; } } } /* If we have isolated a packet, then call the data callback to * process it. */ if(nTmpLen > 0) { /* Execute the callback function with the obtained data. */ if(spNetCon->nDataCallback != NULL) { spNetCon->nDataCallback(spNetCon->nChanId,spTmp+5,nTmpLen); } else { Lgr(LOG_DEBUG, szFunc, "Data arriving on a channel (%d) with no handler", spNetCon->nChanId); } /* Finally, shift data up in the buffer from the end of the last * byte of the packet we've just processed. */ spNetCon->nRecvLen -= ((spTmp+nTmpLen+8) - spNetCon->spRecvBuf); if(spNetCon->nRecvLen > 0) memcpy(spNetCon->spRecvBuf, spTmp+nTmpLen+8, spNetCon->nRecvLen); } } while(nTmpLen > 0); } else { /* Execute the callback function with all the data in the buffer. */ if(spNetCon->nDataCallback != NULL) { spNetCon->nDataCallback(spNetCon->nChanId, spNetCon->spRecvBuf, spNetCon->nRecvLen); spNetCon->nRecvLen = 0; } else { Lgr(LOG_DEBUG, szFunc, "Data arriving on a channel (%d) with no handler", spNetCon->nChanId); } } /* Finished, get out!! */ return( nReturn ); } /****************************************************************************** * Function: _SL_ProcessWaitingPorts * Description: * Thread Safe: No, forces SL Thread only. * Returns: R_OK - Select succeeded. * R_FAIL - Catastrophe, see Errno. * E_BADSELECT - Internal failure causing select to fail. * E_NONWAITING - No sockets waiting processing. ******************************************************************************/ int _SL_ProcessWaitingPorts( ULNG nHibernationPeriod ) /* I: Select sleep*/ { /* Local variables. */ int nReturn = R_FAIL; int nStatus; ULNG lCurrTimeMs; fd_set ReadList; fd_set WriteList; fd_set ExceptList; LINKLIST *spNext; SL_NETCONS *spNetCon; struct timeb sTp; struct timeval sTimeDelay; char *szFunc = "_SL_ProcessWaitingPorts"; #if defined(SOLARIS) || defined(SUNOS) || defined(LINUX) || defined(ZPU) pid_t nPid; SL_NETCONS *spNewClnt; #endif SL_THREAD_ONLY; /* Zap select lists, only interested in our own Sockets. */ FD_ZERO(&ReadList); FD_ZERO(&WriteList); FD_ZERO(&ExceptList); /* Get current time to validate comms down timers. */ ftime(&sTp); lCurrTimeMs = (sTp.time * 1000L) + (ULNG)sTp.millitm; /* Scan list and enable all read/write/except flags on active sockets. */ for(spNetCon=(SL_NETCONS *)StartItem(Sl.spHead, &spNext); spNetCon != NULL; spNetCon=(SL_NETCONS *)NextItem(&spNext)) { /* Connection still waiting to be connected to server? */ if(spNetCon->nStatus == SSL_DOWN && spNetCon->cCorS == STP_CLIENT && spNetCon->lDownTimer < lCurrTimeMs) { /* If connection fails, set down timer, so we dont bother re-trying * to process for a while. */ if(_SL_ConnectToServer(spNetCon) == R_FAIL) { switch(Errno) { case E_BADCONNECT: spNetCon->lDownTimer = lCurrTimeMs + DEF_CONWAITPER; break; default: case E_NOCONNECT: spNetCon->lDownTimer = lCurrTimeMs + DEF_CONFAILPER; break; } } } /* Server port still listening? See if any inbounds are waiting. */ if(spNetCon->nStatus == SSL_LISTENING) { FD_SET(spNetCon->nSd, &ReadList); } /* Active connections? Need to know if they are ready to accept * data or have data awaiting. */ if(spNetCon->nStatus == SSL_UP) { FD_SET(spNetCon->nSd, &ReadList); FD_SET(spNetCon->nSd, &WriteList); } /* If there is data which is awaiting xmission, then try to send it. */ if(spNetCon->nStatus == SSL_UP && spNetCon->spXmitBuf != NULL) { SL_SendData(spNetCon->nChanId, NULL, 0); } } /* Issue select on ports of interest, should return immediately or after * the programmed delay, thereby not blocking action for too long. */ sTimeDelay.tv_sec = nHibernationPeriod/1000; nHibernationPeriod -= sTimeDelay.tv_sec * 1000; sTimeDelay.tv_usec = (nHibernationPeriod * 1000L); #if defined(SOLARIS) || defined(SUNOS) || defined(LINUX) || defined(ZPU) nStatus=select(getdtablesize(), &ReadList, NULL, NULL, &sTimeDelay); #endif #if defined(_WIN32) nStatus=select(MAX_WIN_RLIMIT, &ReadList, NULL, NULL, &sTimeDelay); #endif if(nStatus > 0) { /* Go through lists and process any pending server connections, data * for reception or transmit buffer waiting sessions. */ for(spNetCon=(SL_NETCONS *)StartItem(Sl.spHead, &spNext); spNetCon != NULL; spNetCon=(SL_NETCONS *)NextItem(&spNext)) { /* Dont process any inactive ports. */ if(spNetCon->nStatus == SSL_FAIL || spNetCon->nStatus == SSL_DOWN) continue; /* If the read bit is set, receive all data from the socket and * store in internal buffer, ready for processing. */ if(FD_ISSET(spNetCon->nSd, &ReadList)) { /* Is this a listening device.... server port? */ if(spNetCon->nStatus == SSL_LISTENING) { #if defined(SOLARIS) || defined(LINUX) || defined(SUNOS) || defined(ZPU) /* If the option to Fork on a new connection has been set, * then fork a child and let it perform the accept of the * incoming connections. */ if(spNetCon->nForkForAccept == TRUE) { /* Accept the connection prior to child fork. */ if(_SL_AcceptClient(spNetCon->nSd, spNetCon, &spNewClnt) == R_OK) { /* Fork child to handle new connection. */ if((nPid=fork()) < 0) { Lgr(LOG_DEBUG, szFunc, "Couldnt fork a new process, will retry.."); } else /* If we are the child then close the parent service * port as we are not interested in it. */ if(nPid == 0) { _SL_Close(spNetCon, FALSE); } else /* If we are the parent then close the accepted * child socket. */ { _SL_Close(spNewClnt, FALSE); fflush(stdout); } } } else { /* For standard comms, just accept the connection. * No need to worry about forking etc. */ _SL_AcceptClient(spNetCon->nSd, spNetCon, NULL); } #endif #if defined(_WIN32) /* For standard comms, just accept the connection. * No need to worry about forking etc. */ _SL_AcceptClient(spNetCon->nSd, spNetCon, NULL); #endif } else { if(_SL_ReceiveFromSocket(spNetCon) == R_OK) { /* See if a full packet has been assembled. */ _SL_ProcessRecvBuf(spNetCon); } else { /* Process any remaining valid packets prior to * exception handling. */ _SL_ProcessRecvBuf(spNetCon); /* Indicate that an exception has occurred on * this socket. */ if(Errno == E_NOSERVICE) FD_SET(spNetCon->nSd, &ExceptList); } } } /* Any exceptions occurred on a socket? */ if(FD_ISSET(spNetCon->nSd, &ExceptList)) { /* If this is a server then delete the connection. */ if(spNetCon->cCorS == STP_SERVER) { /* Close the connection as it is no longer required. */ _SL_Close(spNetCon, TRUE); } else { /* A client failure just requires the link to be marked * down and it will eventually be rebuilt. */ spNetCon->nStatus = SSL_DOWN; spNetCon->nCntrlCallback(SLC_LINKDOWN, spNetCon->nChanId, _SL_GetPortNo(spNetCon), spNetCon->lServerIPaddr); } } /* This Channel marked for closure? Close it only if all data * for transmission has been sent. */ if(spNetCon->nClose == TRUE && spNetCon->spXmitBuf == NULL) { _SL_Close(spNetCon, TRUE); } } nReturn = R_OK; } else { if(nStatus == 0) { nReturn = R_OK; } else { printf("Bad Select (%d)\n", nStatus); fflush(stdout); Errno = E_BADSELECT; } } #if defined(SUNOS) || defined(LINUX) || defined(ZPU) /* Addition: 26/5/1996. Soak up all child result codes for fork on accept * children. */ wait4(0, &nStatus, WNOHANG, NULL); #endif #if defined(SOLARIS) /* Addition: 4/9/1996. Soak up all child result codes for fork on accept * children. */ wait3(&nStatus, WNOHANG|WUNTRACED, NULL); #endif /* Return result code to caller. */ return(nReturn); } /****************************************************************************** * Function: _SL_ProcessCallbacks * Description: Activate any callbacks whose timers are active and have * expired. * Thread Safe: No, only allows SL Thread. * Returns: Time in mS till next callback. * ******************************************************************************/ ULNG _SL_ProcessCallbacks( void ) { /* Local variables. */ ULNG nReturn = DEF_MAXBLOCKPERIOD; ULNG lCurrTimeMs; LINKLIST *spNext; SL_CALLIST *spCB; struct timeb sTp; SL_THREAD_ONLY; /* Get current time to set any new expiration timers. */ ftime(&sTp); lCurrTimeMs = (sTp.time * 1000L) + (ULNG)sTp.millitm; /* Go through callback list and invoke callback function if timer has * expired. */ for(spCB=(SL_CALLIST *)StartItem(Sl.spCBHead, &spNext); spCB != NULL; spCB=(SL_CALLIST *)NextItem(&spNext)) { /* If the callback is active, and its timer has expired, execute * the callback. */ if( spCB->nStatus == TCB_UP && spCB->lTimeExpire <= lCurrTimeMs ) { if( spCB->nCallback != NULL ) spCB->nCallback(spCB->lCBData); /* Update counter according to options flag. */ switch(spCB->nOptions) { case TCB_ASTABLE: /* Astable means that the time is equidistant from * the last. */ spCB->lTimeExpire = lCurrTimeMs + spCB->lTimePeriod; break; case TCB_FLIPFLOP: /* Re-read time, as flip flop commences from whence the * function completed. */ ftime(&sTp); lCurrTimeMs = (sTp.time * 1000L) + (ULNG)sTp.millitm; spCB->lTimeExpire = lCurrTimeMs + spCB->lTimePeriod; break; case TCB_ONESHOT: default: spCB->nStatus = TCB_DOWN; break; } } /* If this is an active callback, then see if its expiration time * is the lowest. The lowest time period is used for the select * hibernation time. */ if( spCB->nStatus == TCB_UP && nReturn > (spCB->lTimeExpire - lCurrTimeMs) ) { nReturn = (spCB->lTimeExpire - lCurrTimeMs); } } /* Return result code to caller. */ return(nReturn); } /****************************************************************************** * Function: SL_HostIPtoString * Description: Convert a given IP address into a string dot notation. * Thread Safe: No, API only allows one thread at a time. * Returns: 16bit CRC ******************************************************************************/ UCHAR *SL_HostIPtoString( ULNG lIPaddr ) /* I: IP address to convert */ { /* Statics and local variables. */ static UCHAR szIPaddr[16]; UCHAR szTmpBuf[5]; SL_SINGLE_THREAD_ONLY; /* Convert long into 4 bytes. */ PutCharFromLong(szTmpBuf, lIPaddr); /* Copy into string. */ sprintf(szIPaddr, "%d%c%d%c%d%c%d", (UINT)szTmpBuf[0], '.', (UINT)szTmpBuf[1], '.', (UINT)szTmpBuf[2], '.', (UINT)szTmpBuf[3]); /* Return address of string to caller. */ SL_SINGLE_THREAD_EXIT(szIPaddr); } /****************************************************************************** * Function: SL_GetIPaddr * Description: Get the Internet address of the local machine or a named * machine. * Thread Safe: No, API only allows one thread at a time. * Returns: R_OK - IP address obtained. * R_FAIL - IP address not obtained. ******************************************************************************/ int SL_GetIPaddr( UCHAR *szHost, /* I: Hostname string */ ULNG *lIPaddr ) /* O: Storage for the Internet Addr */ { /* Local variables. */ int nReturn = R_FAIL; char szHostName[MAX_MACHINENAME+1]; struct hostent *spHostEnt; SL_SINGLE_THREAD_ONLY; /* If no hostname provided, use local machine name. */ if(szHost == NULL) { /* Get host name from /etc/hosts. */ gethostname(szHostName, MAX_MACHINENAME); } else { strcpy(szHostName, szHost); } if( (spHostEnt=gethostbyname( szHostName )) != NULL ) { if( spHostEnt->h_addrtype == AF_INET ) { /* This is cheating a little, as Im assuming in_addr will * always be 32 bit. */ memcpy((UCHAR *)lIPaddr, spHostEnt->h_addr, 4); *lIPaddr = ntohl(*lIPaddr); nReturn = R_OK; } } /* Finished, get out!! */ SL_SINGLE_THREAD_EXIT( nReturn ); } /****************************************************************************** * Function: SL_GetService * Description: Get the TCP/UDP service port number from the Services File. * Thread Safe: No, API Function only allows one thread at a time. * Returns: R_OK - Service port obtained. * R_FAIL - Service port not obtained. ******************************************************************************/ int SL_GetService( UCHAR *szService, /* I: Service Name string */ UINT *nPortNo ) /* O: Storage for the Port Number */ { /* Local variables. */ int nReturn = R_FAIL; struct servent *spServiceEnt; SL_SINGLE_THREAD_ONLY; /* Call unix functions to search the services file/database for the * requested service. */ if( (spServiceEnt=getservbyname( szService, NULL )) != NULL ) { /* Setup callers variable with the service port number. */ *nPortNo = ntohs(spServiceEnt->s_port); nReturn = R_OK; } /* Finished, get out!! */ SL_SINGLE_THREAD_EXIT( nReturn ); } /****************************************************************************** * Function: SL_Init * Description: Initialise communication variables and connect or setup * listening for required socket connections. * Thread Safe: No, API function only allows one thread at a time. * Returns: R_OK - Comms functionality initialised. * R_FAIL - Initialisation failed, see Errno. * E_NOMEM - Memory exhaustion. ******************************************************************************/ int SL_Init( UINT nSockKeepAlive, /* I: Socket keep alive time period */ UCHAR *szErrMsg ) /* O: Error message buffer */ { /* Local variables. */ int nReturn = R_OK; SL_SINGLE_THREAD_ONLY; #if defined(_WIN32) WSADATA WSAData; if(WSAStartup(MAKEWORD(1,1), &WSAData) != 0) { sprintf(szErrMsg, "Failed to initialise Windows Socket API"); SL_SINGLE_THREAD_EXIT(R_FAIL); } #endif /* Save configuration information. */ Sl.nSockKeepAlive = nSockKeepAlive; /* Initialise all variables as needed. */ Sl.spHead = NULL; Sl.spTail = NULL; Sl.spCBHead = NULL; Sl.spCBTail = NULL; /* Finished, get out!! */ SL_SINGLE_THREAD_EXIT( nReturn ); } /****************************************************************************** * Function: SL_Exit * Description: Decommission the Comms module ready for program termination * or re-initialisation. * Thread Safe: No, API function, only allows one thread at a time. * Returns: R_OK - Exit succeeded. * R_FAIL - Couldnt perform exit processing, see errno. * ******************************************************************************/ int SL_Exit( UCHAR *szErrMsg ) /* O: Error message buffer */ { /* Local variables. */ int nReturn = R_OK; LINKLIST *spNext; SL_NETCONS *spNetCon; SL_SINGLE_THREAD_ONLY; /* Free up network connection receive buffer memory. */ for(spNetCon=(SL_NETCONS *)StartItem(Sl.spHead, &spNext); spNetCon != NULL; spNetCon=(SL_NETCONS *)NextItem(&spNext)) { if(spNetCon->spRecvBuf != NULL) free(spNetCon->spRecvBuf); if(spNetCon->spXmitBuf != NULL) free(spNetCon->spXmitBuf); } /* Free up linked list memory. */ if(Sl.spHead != NULL) DelList(&Sl.spHead, &Sl.spTail); if(Sl.spCBHead != NULL) DelList(&Sl.spCBHead, &Sl.spCBTail); /* Free up any character buffers... */ /* Finished, get out!! */ SL_SINGLE_THREAD_EXIT( nReturn ); } /****************************************************************************** * Function: SL_PostTerminate * Description: Post a request for the program to terminate as soon as * possible. * Thread Safe: Yes * Returns: Non. ******************************************************************************/ void SL_PostTerminate( void ) { /* Local variables. */ /* To terminate, simply set the flag nCloseDown. */ Sl.nCloseDown = TRUE; /* Get out and let the wheels of the Kernel close us down. */ return; } /****************************************************************************** * Function: SL_GetChanId * Description: Get a channel Id from a given IP addr. If the IP addr doesnt * exist or is still pending a connection, return 0 as an error. * Thread Safe: No, API function only allows one thread at a time. * Returns: > 0 - Channel Id associated with IP address. * 0 - Couldnt find an associated channel. ******************************************************************************/ UINT SL_GetChanId( ULNG lIPaddr ) /* I: Address to xlate */ { /* Local variables. */ UINT nChanId = 0; SL_NETCONS *spNetCon; LINKLIST *spNext; SL_SINGLE_THREAD_ONLY; /* Go through list of client/server connections and see if we can obtain * an IP address match. If a match occurs, then see if the Channel Id * is valid. */ for(spNetCon=(SL_NETCONS *)StartItem(Sl.spHead, &spNext); spNetCon != NULL; spNetCon=(SL_NETCONS *)NextItem(&spNext)) { if(spNetCon != NULL && spNetCon->lServerIPaddr == lIPaddr) { /* Consider the channel id to be valid if its active (UP) or * temporarily out-of-service (DOWN). */ if( spNetCon->nStatus == SSL_DOWN || spNetCon->nStatus == SSL_UP ) { nChanId = spNetCon->nChanId; } break; } } /* Finished, get out!! */ SL_SINGLE_THREAD_EXIT( nChanId ); } /****************************************************************************** * Function: SL_RawMode * Description: Function to switch a channel into/out of raw mode processing. * Raw mode processing foregoes all forms of checking and is * typically used for connections with a non SL lib server/client. * Thread Safe: No, API Function, only allows one thread at a time. * Returns: R_FAIL - Illegal Channel Id given. * R_OK - Mode set * ******************************************************************************/ int SL_RawMode( UINT nChanId, /* I: Channel to apply change to */ UINT nMode ) /* I: Mode to set channel to */ { /* Local variables. */ int nReturn = R_FAIL; LINKLIST *spNext; SL_NETCONS *spNetCon; SL_SINGLE_THREAD_ONLY; /* Scan list to find corresponding channel information. */ for(spNetCon=(SL_NETCONS *)StartItem(Sl.spHead, &spNext); spNetCon != NULL; spNetCon=(SL_NETCONS *)NextItem(&spNext)) { /* Find the greatest Channel Id. */ if(spNetCon != NULL && spNetCon->nChanId == nChanId) break; } /* Did we find it? */ if(spNetCon != NULL) { spNetCon->nRawMode = (nChanId == 0 ? FALSE : TRUE); nReturn = R_OK; } /* Return result code to caller. */ SL_SINGLE_THREAD_EXIT(nReturn); } /****************************************************************************** * Function: SL_AddServer * Description: Add an entry into the Network Connections table as a Server. * An entry is built up and a socket created and set listening. * Thread Safe: No, API Function, only allows one thread at a time. * Returns: R_OK - Successfully added. * R_FAIL - Error, see Errno. * E_NOMEM - Memory exhaustion. * E_BADPARM - Bad parameters passed. * E_EXISTS - Entry already exists. * E_NOSOCKET - Couldnt grab a socket. * E_NOLISTEN - Couldnt listen on given port. ******************************************************************************/ int SL_AddServer( UINT nPortNo, /* I: Port to listen on */ UINT nForkForAccept, /* I: Fork prior to accept */ void (*nDataCallback)(), /* I: Data ready callback */ void (*nCntrlCallback)(int, ...) ) /* I: Control callback */ { /* Local variables. */ int nReturn = R_FAIL; char *szFunc = "SL_AddServer"; LINKLIST *spNext; SL_NETCONS *spNetCon; struct sockaddr_in sServer; SL_SINGLE_THREAD_ONLY; /* Scan list to see if an entry exists for requested server, if it does * then just exit. */ for(spNetCon=(SL_NETCONS *)StartItem(Sl.spHead, &spNext); spNetCon != NULL; spNetCon=(SL_NETCONS *)NextItem(&spNext)) { if(spNetCon != NULL && spNetCon->cCorS == STP_SERVER && spNetCon->nOurPortNo == nPortNo) { Errno = E_EXISTS; SL_SINGLE_THREAD_EXIT(nReturn); } } /* Create a Network Connection record, populate, set in motion and add * to the support lists. */ if((spNetCon=(SL_NETCONS *)malloc(sizeof(SL_NETCONS))) == NULL) { Lgr(LOG_DEBUG, szFunc, "Couldnt malloc (%d) bytes", sizeof(SL_NETCONS)); Errno = E_NOMEM; } else { /* Wash the new memory, just in case. */ memset((UCHAR *)spNetCon, '\0', sizeof(SL_NETCONS)); /* Fill out the remaining structure conflab. */ spNetCon->cCorS = STP_SERVER; spNetCon->nClose = FALSE; spNetCon->nOurPortNo = nPortNo; spNetCon->nDataCallback = nDataCallback; spNetCon->nCntrlCallback = nCntrlCallback; spNetCon->nRecvBufLen = DEF_INITRECVBUF; spNetCon->nStatus = SSL_LISTENING; spNetCon->nForkForAccept = nForkForAccept; spNetCon->spXmitBuf = NULL; spNetCon->nXmitLen = 0; spNetCon->nXmitPos = 0; /* Build up Server address info, so it can be publicised by bind to * the big wide world. */ memset((UCHAR *)&sServer, '\0', sizeof(struct sockaddr)); sServer.sin_family = AF_INET; sServer.sin_port = htons((USHRT)spNetCon->nOurPortNo); sServer.sin_addr.s_addr = htonl(INADDR_ANY); /* Fire up a socket and lets listen. */ if((spNetCon->nSd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { Errno = E_NOSOCKET; free(spNetCon->spRecvBuf); free(spNetCon); } else if(bind(spNetCon->nSd, (struct sockaddr *)&sServer, sizeof(struct sockaddr)) == -1) { Errno = E_NOBIND; free(spNetCon->spRecvBuf); free(spNetCon); } else if(listen(spNetCon->nSd, MAX_SOCKETBACKLOG) == -1) { Errno = E_NOLISTEN; free(spNetCon->spRecvBuf); free(spNetCon); } else { /* OK, almost there, now will it stick onto the lists!!? */ if(AddItem(&Sl.spHead, &Sl.spTail, SORT_NONE, &spNetCon->nChanId, &spNetCon->lServerIPaddr, spNetCon->szServerName, spNetCon) == R_OK) { nReturn = R_OK; } else { /* Free used memory, Errno already set by AddItem. */ free(spNetCon); } } } /* Return Channel ID or error to caller. */ SL_SINGLE_THREAD_EXIT(nReturn); } /****************************************************************************** * Function: SL_AddClient * Description: Add an entry into the Network Connections table as a client. * Socket creation and connect are left to the kernels * discretion. * Thread Safe: No, API function, only allows one thread at a time. * Returns: >= 0 - Channel Id. * -1 - Error, see Errno. * E_NOMEM - Memory exhaustion. * E_EXISTS - A client of same detail exists. ******************************************************************************/ int SL_AddClient( UINT nServerPortNo, /* I: Server port to talk on */ ULNG lServerIPaddr, /* I: Server IP address */ UCHAR *szServerName, /* I: Name of Server */ void (*nDataCallback)(), /* I: Data ready callback */ void (*nCntrlCallback)(int, ...) ) /* I: Control callback */ { /* Local variables. */ UINT nChanId = DEF_CHANID; int nReturn = -1; char *szFunc = "SL_AddClient"; LINKLIST *spNext; SL_NETCONS *spNetCon; SL_SINGLE_THREAD_ONLY; /* Scan list to see if an entry exists for requested client, if it does * then just exit. */ for(spNetCon=(SL_NETCONS *)StartItem(Sl.spHead, &spNext); spNetCon != NULL; spNetCon=(SL_NETCONS *)NextItem(&spNext)) { /* Find the greatest Channel Id. */ if(spNetCon != NULL && spNetCon->nChanId > nChanId) nChanId = spNetCon->nChanId; /* Duplicate? if(spNetCon != NULL && spNetCon->cCorS == STP_CLIENT && spNetCon->nServerPortNo == nServerPortNo && spNetCon->lServerIPaddr == lServerIPaddr ) { Errno = E_EXISTS; SL_SINGLE_THREAD_EXIT(nReturn); } */ } /* Create a Network Connection record, populate, see if a connection with * the remote server can be obtained, then add to the support lists. */ if((spNetCon=(SL_NETCONS *)malloc(sizeof(SL_NETCONS))) == NULL) { Lgr(LOG_DEBUG, szFunc, "Couldnt malloc (%d) bytes", sizeof(SL_NETCONS)); Errno = E_NOMEM; } else { /* Wash the new memory, just in case. */ memset((UCHAR *)spNetCon, '\0', sizeof(SL_NETCONS)); /* Try and allocate an initial receive buffer. */ if((spNetCon->spRecvBuf=(UCHAR *)malloc(DEF_INITRECVBUF)) == NULL) { Lgr(LOG_DEBUG, szFunc, "Couldnt malloc (%d) bytes", DEF_INITRECVBUF); Errno = E_NOMEM; free(spNetCon); } else { /* Fill out the remaining structure conflab. */ spNetCon->cCorS = STP_CLIENT; spNetCon->nSd = -1; spNetCon->nRawMode = FALSE; spNetCon->nServerPortNo = nServerPortNo; spNetCon->lServerIPaddr = lServerIPaddr; strcpy(spNetCon->szServerName, szServerName); spNetCon->nDataCallback = nDataCallback; spNetCon->nCntrlCallback = nCntrlCallback; spNetCon->nRecvBufLen = DEF_INITRECVBUF; spNetCon->nStatus = SSL_DOWN; spNetCon->nChanId = nChanId + 1; spNetCon->lDownTimer = 0L; spNetCon->spXmitBuf = NULL; spNetCon->nXmitLen = 0; spNetCon->nXmitPos = 0; /* OK, almost there, now will it stick onto the lists!!? */ if(AddItem(&Sl.spHead, &Sl.spTail, SORT_NONE, &spNetCon->nChanId, &spNetCon->lServerIPaddr, spNetCon->szServerName, spNetCon) == R_OK) { nReturn = spNetCon->nChanId; } else { /* Free up used memory, Errno has been set by AddItem. */ free(spNetCon->spRecvBuf); free(spNetCon); } } } /* Return Channel ID or error to caller. */ SL_SINGLE_THREAD_EXIT(nReturn); } /****************************************************************************** * Function: SL_AddTimerCB * Description: Add a timed callback. Basically, a timed callback is a function * which gets invoked after a period of time. This function * can be invoked once (TCB_ONESHOT), every Xms (TCB_ASTABLE) or * a fixed period of time Xms from last execution (TCB_FLIPFLOP). * Each callback can pass a predefined variable/pointer, so * multiple instances of the same callback can exist, each * referring to the same function, but passing different values * to it. The callbacks are maintained in a dynamic link list. * Thread Safe: No, API Function, only allows single thread at a time. * Returns: R_OK - Callback added successfully. * R_FAIL - Failure, see Errno. * E_NOMEM - Memory exhaustion. ******************************************************************************/ int SL_AddTimerCB( ULNG lTimePeriod, /* I: Time between callbacks */ UINT nOptions, /* I: Option flags on callback */ ULNG lCBData, /* I: Data to be passed to cb */ void (*nCallback)() ) /* I: Function to call */ { /* Local variables. */ int nReturn = R_FAIL; ULNG lCurrTimeMs; char *szFunc = "SL_AddTimerCB"; SL_CALLIST *spCB; LINKLIST *spNext; struct timeb sTp; SL_SINGLE_THREAD_ONLY; /* Go through callback list to locate an existing entry. Existing entries * may occur as the application is just updating the configuration of * the given callback. */ for(spCB=(SL_CALLIST *)StartItem(Sl.spCBHead, &spNext); spCB != NULL; spCB=(SL_CALLIST *)NextItem(&spNext)) { /* If the callback is the same... and the data back is the same, then * we are updating an existing record. */ if( spCB->nCallback == nCallback && spCB->lCBData == lCBData ) break; } /* Does a current entry exist? If not, allocate and add to linked list. */ if( spCB == NULL ) { /* Create record. */ if((spCB = (SL_CALLIST *)malloc(sizeof(SL_CALLIST))) == NULL) { Lgr(LOG_DEBUG, szFunc, "Couldnt malloc (%d) bytes", sizeof(SL_CALLIST)); Errno = E_NOMEM; } else { /* Wash the new memory, just in case. */ memset((UCHAR *)spCB, '\0', sizeof(SL_CALLIST)); } /* Add to callback list. */ if(AddItem(&Sl.spCBHead, &Sl.spCBTail, SORT_NONE, NULL, NULL, NULL, spCB) == R_FAIL) { /* Dont modify Errno as AddItem has already set it for the * correct error condition. */ free(spCB); spCB = NULL; } else { /* Store the callback address, no need to accomplish in an update * scenario. */ spCB->nCallback = nCallback; } } /* Still ok...? ie Where in an update situation or we successfully * allocated, initialised and linked in a new record! */ if( spCB != NULL ) { /* Get current time to set expiration timer. */ ftime(&sTp); lCurrTimeMs = (sTp.time * 1000L) + (ULNG)sTp.millitm; /* Copy in/update the required parameters. */ if((spCB->nOptions = nOptions) == TCB_OFF) { spCB->nStatus = TCB_DOWN; } else { spCB->lTimePeriod = lTimePeriod; spCB->lCBData = lCBData; spCB->lTimeExpire = lCurrTimeMs + lTimePeriod; spCB->nStatus = TCB_UP; } nReturn = R_OK; } /* Return result code to caller. */ SL_SINGLE_THREAD_EXIT(nReturn); } /****************************************************************************** * Function: SL_DelServer * Description: Delete an entry from the Network Connections table and * disable the actual communications associated with it. * Thread Safe: No, API function allows one thread at a time. * Returns: R_OK - Successfully deleted. * R_FAIL - Error, see Errno. * E_BADPARM - Bad parameters passed. ******************************************************************************/ int SL_DelServer( UINT nPortNo ) /* I: Port number that server on */ { /* Local variables. */ int nReturn = R_FAIL; char *szFunc = "SL_DelServer"; LINKLIST *spNext; SL_NETCONS *spNetCon; SL_SINGLE_THREAD_ONLY; /* Scan list to find the required entry for deletion. */ for(spNetCon=(SL_NETCONS *)StartItem(Sl.spHead, &spNext); spNetCon != NULL; spNetCon=(SL_NETCONS *)NextItem(&spNext)) { if(spNetCon != NULL && spNetCon->cCorS == STP_SERVER && spNetCon->nOurPortNo == nPortNo) { /* Entry found, so close it down. */ nReturn=_SL_Close(spNetCon, FALSE); /* Exit with result code. */ SL_SINGLE_THREAD_EXIT(nReturn); } } /* Didnt find the entry so exit with fail. */ SL_SINGLE_THREAD_EXIT(nReturn); } /****************************************************************************** * Function: SL_DelClient * Description: Delete a client entry from the Network Connections table and * free up all resources that it used. * Thread Safe: No, API function allows on thread at a time. * Returns: R_OK - Client connection successfully closed. * R_FAIL - Error, see Errno. * ******************************************************************************/ int SL_DelClient( UINT nChanId ) /* I: Channel Id of client to del*/ { /* Local variables. */ int nReturn = R_FAIL; char *szFunc = "SL_DelClient"; LINKLIST *spNext; SL_NETCONS *spNetCon; SL_SINGLE_THREAD_ONLY; /* Scan list to see if an entry exists for requested client, if it does * then delete it. */ for(spNetCon=(SL_NETCONS *)StartItem(Sl.spHead, &spNext); spNetCon != NULL; spNetCon=(SL_NETCONS *)NextItem(&spNext)) { /* If we have a match on channel Id then we have located the correct * entry. */ if(spNetCon != NULL && /* spNetCon->cCorS == STP_CLIENT &&*/ spNetCon->nChanId == nChanId ) { /* Entry found, so close it down. */ nReturn=_SL_Close(spNetCon, FALSE); /* Exit with result code. */ SL_SINGLE_THREAD_EXIT(nReturn); } } /* Didnt find the required entry so exit with failure code. */ SL_SINGLE_THREAD_EXIT(nReturn); } /****************************************************************************** * Function: SL_Close * Description: Close a socket connection (Client or Server) based on the * given channel ID. Due to the nature of the socket library, * this cannot be performed immediately, as nearly always, the * application requesting the close is within a socket library * callback, and modifying internal structures in this state is * fraught with danger. * Thread Safe: No, API function, only allows one thread at a time. * Returns: Non. ******************************************************************************/ int SL_Close( UINT nChanId ) /* I: Channel Id to close */ { /* Local variables. */ char *szFunc = "SL_Close"; LINKLIST *spNext; SL_NETCONS *spNetCon; SL_SINGLE_THREAD_ONLY; /* Using the channel Id, seek out the corresponding Net Connection * record. */ for(spNetCon=(SL_NETCONS *)StartItem(Sl.spHead, &spNext); spNetCon != NULL && spNetCon->nChanId != nChanId; spNetCon=(SL_NETCONS *)NextItem(&spNext)); /* If we cant locate a record corresponding to the given channel Id, * then the calling application has passed us a bad value or internal * workings are going wrong. */ if(spNetCon != NULL && spNetCon->nChanId != nChanId) { Errno = E_INVCHANID; SL_SINGLE_THREAD_EXIT(R_FAIL); } /* OK, record found, mark the channel for closure and get out. */ spNetCon->nClose = TRUE; /* Get out, big bang time! */ SL_SINGLE_THREAD_EXIT(R_OK); } /****************************************************************************** * Function: SL_SendData * Description: Transmit a packet of data to a given destination identified * by it channel Id. * Thread Safe: No, API function, only allows one thread at a time. * Returns: R_OK - Data sent successfully. * R_FAIL - Couldnt send data, see Errno. * E_INVCHANID - Invalid channel Id. * E_BUSY - Channel is busy, retry later. * E_BADSOCKET - Internal failure on socket, terminal. * E_NOSERVICE - No remote connection established yet. ******************************************************************************/ int SL_SendData( UINT nChanId, /* I: Channel Id to send data on */ UCHAR *szData, /* I: Data to be sent */ UINT nDataLen ) /* I: Length of data */ { /* Local variables. */ UINT nDataCRC; int nNewBuf = FALSE; int nReturn = R_FAIL; int nSend; #if defined(_WIN32) int nWinErr; #endif char *szFunc = "SL_SendData"; LINKLIST *spNext; SL_NETCONS *spNetCon; SL_SINGLE_THREAD_ONLY; /* Scan list to see if an entry exists for requested channel. */ for(spNetCon=(SL_NETCONS *)StartItem(Sl.spHead, &spNext); spNetCon != NULL && spNetCon->nChanId != nChanId; spNetCon=(SL_NETCONS *)NextItem(&spNext)); /* If the channel is invalid, get out. */ if(spNetCon == NULL) { Errno = E_INVCHANID; SL_SINGLE_THREAD_EXIT(nReturn); } /* If the caller has passed no data in then he is wanting to flush any * existing buffer out and get a result from it. If there is no data * pending for transmission then exit with OK. */ if(szData == NULL && spNetCon->spXmitBuf == NULL) { SL_SINGLE_THREAD_EXIT(R_OK); } /* If the caller wants to send a buffer but a previous buffer is still * pending transmission, then exit with busy. */ if(szData != NULL && spNetCon->spXmitBuf != NULL) { Errno = E_BUSY; SL_SINGLE_THREAD_EXIT(R_FAIL); } /* Is there data currently pending transmission...? */ if(spNetCon->spXmitBuf == NULL && szData != NULL) { /* Pre-load sizing variables. */ spNetCon->nXmitLen = nDataLen; if(spNetCon->nRawMode == FALSE) spNetCon->nXmitLen += 8; spNetCon->nXmitPos = 0; /* Set the new buffer flag to indicate that we have just undertaken * to transmit a new buffer. This flag affects the return code in * the event that we get a busy on the send port. */ nNewBuf = TRUE; /* If not in Raw Mode, format the data in format: * <..DATA..> */ if((spNetCon->spXmitBuf=(UCHAR *)malloc(spNetCon->nXmitLen)) == NULL) { Lgr(LOG_DEBUG, szFunc, "Couldnt malloc (%d) bytes", spNetCon->nXmitLen); Errno = E_NOMEM; SL_SINGLE_THREAD_EXIT(nReturn); } else { /* Copy data as is into buffer. */ if(spNetCon->nRawMode == FALSE) { memcpy((UCHAR *)spNetCon->spXmitBuf+5,(UCHAR *)szData,nDataLen); /* Add formatting data. */ spNetCon->spXmitBuf[0] = A_SYN; spNetCon->spXmitBuf[1] = A_SYN; spNetCon->spXmitBuf[2] = A_STX; PutCharFromInt(&spNetCon->spXmitBuf[3], nDataLen); spNetCon->spXmitBuf[nDataLen+5] = A_ETX; /* Finally, get CRC on data. */ nDataCRC = _SL_CalcCRC(szData, nDataLen); PutCharFromInt(&spNetCon->spXmitBuf[nDataLen+6], nDataCRC); } else { memcpy((UCHAR *)spNetCon->spXmitBuf, (UCHAR *)szData, nDataLen); } } } /* If we've got info to xmit, then transmit it. */ if(spNetCon->spXmitBuf != NULL) { switch(spNetCon->nStatus) { case SSL_UP: /* Send the data, if an error occurs, group into relevant * internal code. */ nSend = send(spNetCon->nSd, &spNetCon->spXmitBuf[spNetCon->nXmitPos], spNetCon->nXmitLen - spNetCon->nXmitPos, 0); /* Any failure's. */ if(nSend == -1) { #if defined(_WIN32) /* Windows again folks, old bill just doesnt like standards. */ switch((nWinErr = WSAGetLastError())) #endif #if defined(SOLARIS) || defined(SUNOS) || defined(LINUX) || defined(ZPU) switch(errno) #endif { case EINTR: case ENOBUFS: case EWOULDBLOCK: Errno = E_BUSY; break; case EBADF: case EFAULT: case EINVAL: case ENOTSOCK: default: Errno = E_BADSOCKET; break; } } else { /* Move position pointer on according to number of bytes * transmitted. */ spNetCon->nXmitPos += nSend; /* If we've finished transmitting the entire buffer, then * clean up. */ if(spNetCon->nXmitPos == spNetCon->nXmitLen) { /* No further need for dynamic buffer. */ free(spNetCon->spXmitBuf); spNetCon->spXmitBuf = NULL; } nReturn = R_OK; } break; case SSL_LISTENING: case SSL_DOWN: Errno = E_NOSERVICE; break; case SSL_FAIL: default: Errno = E_BADSOCKET; break; } } else { nReturn = R_OK; } /* If a failure occurs due to the send-buffer becoming full, tell * the user that the packet has been sent ok, as we'll flush it out * in the background. */ if(nNewBuf == TRUE && nReturn == R_FAIL && Errno == E_BUSY) { nReturn = R_OK; } /* Return result code to caller. */ SL_SINGLE_THREAD_EXIT(nReturn); } /****************************************************************************** * Function: SL_BlockSendData * Description: Transmit a packet of data to a given destination but ensure it * is sent prior to exit. If an error occurs, then return it * to the caller. * Thread Safe: No, API function, only allows one thread at a time. * Returns: R_OK - Data sent successfully. * R_FAIL - Couldnt send data, see Errno. * E_INVCHANID - Invalid channel Id. * E_BADSOCKET - Internal failure on socket, terminal. * E_NOSERVICE - No remote connection established yet. ******************************************************************************/ int SL_BlockSendData( UINT nChanId, /* I: Channel Id to send data on */ UCHAR *szData, /* I: Data to be sent */ UINT nDataLen ) /* I: Length of data */ { /* Local variables. */ int nReturn; SL_SINGLE_THREAD_ONLY; /* Flush out any data that is already queued for transmission. */ for(; (nReturn=SL_SendData(nChanId, NULL, 0))==R_FAIL && Errno == E_BUSY; ); /* Check to ensure that no other failure occurred with flushing the * buffers. If there is, pass back values setup by SL_SendData. */ if(nReturn != R_OK) { SL_SINGLE_THREAD_EXIT(nReturn); } /* Send the actual data. Return if an error occurs. */ if((nReturn=SL_SendData(nChanId, szData, nDataLen)) == R_FAIL) { SL_SINGLE_THREAD_EXIT(nReturn); } /* Flush out the data which we have just queued. */ for(; (nReturn=SL_SendData(nChanId, NULL, 0))==R_FAIL && Errno == E_BUSY; ); /* Return the result code given when flushing the data out as it * will reflect any errors exactly. */ SL_SINGLE_THREAD_EXIT(nReturn); } /****************************************************************************** * Function: SL_Poll * Description: Function for programs which cant afford UX taking control of * the CPU. This function offers these type of applications the * ability to allow comms processing by frequently calling this * Poll function. * Thread Safe: No, API function, only allows one thread at a time. * Returns: R_OK - System closing down. * R_FAIL - Catastrophe, see Errno. * ******************************************************************************/ int SL_Poll( ULNG lSleepTime ) { /* Local variables. */ int nReturn = R_OK; SL_SINGLE_THREAD_ONLY; /* Process list of callback routines. A callback works in time, when * a certain amount of time has elapsed an application provided * function is called. */ _SL_ProcessCallbacks(); /* Any sockets awaiting attention? */ _SL_ProcessWaitingPorts(lSleepTime); /* Return result code to caller. */ SL_SINGLE_THREAD_EXIT(nReturn); } /****************************************************************************** * Function: SL_Kernel * Description: Application process control is passed over to this function * and it allocates and manages time/events. The application * registers callbacks with this library, and they are invoked * as events occur or as time elapses. Control passes out of * this function on application completion. * Thread Safe: No, Assumes main thread or one control thread. * Returns: R_OK - System closing down. * R_FAIL - Catastrophe, see Errno. * ******************************************************************************/ int SL_Kernel( void ) { /* Local variables. */ int nReturn = R_FAIL; ULNG nHibernationPeriod; /* Stay in this function forever until a serious event occurs or an * external event modifies the CloseDown flag. */ Sl.nCloseDown = 0; do { /* Process list of callback routines. A callback works in time, when * a certain amount of time has elapsed an application provided * function is called. */ nHibernationPeriod=_SL_ProcessCallbacks(); /* Any sockets awaiting attention? */ _SL_ProcessWaitingPorts(1000); } while(!Sl.nCloseDown); /* Return result code to caller. */ return(nReturn); }