/****************************************************************************
**
**  Name: tcpip.c
**
**  Description: Host-specific implementation of the generic network
**     operations.
**
**     References:
#ifdef PC_NFS
**        PC-NFS Programmer's Toolkit: Library Reference (for version 4.0),
**        Sun Microsystems, 1992.
**          close: delete descriptor (Borland C++ lib ref), pg 82.
**          connect: connect to another socket, pg 232.
**          gethostbyname: get host addr from name, pg 8.
**          htons: converts between host and network byte order, pg 258.
**          is_pc_nfs_installed: determine if PC-NFS is running, pg 272.
**          recv: read bytes, pg 244.
**          rnm_version: get version # of resident name-service module, pg 280.
**          rtm_version: returns version# of resident transport module, pg 285.
**          select: check for OK to read/write, pg 286.
**          send: write bytes, pg 249.
**          shutdown: discard bytes waiting to be read or written, and disallow
**            any further read/write operations, pg 252.
**          socket: open socket, pg 253.
**          tkdll_version: returns version number of toolkit DLL, pg 289.
#endif
#ifdef OS2_LAN_SERVER
**        TCP/IP Version 2.1 for DOS:  Programmer's Reference, SC31-7046-00,
**        IBM, January 1993.
**          connect: Request a connection to a foreign server, pg 37.
**          gethostbyname: Return information about a host, pg 45.
**          htons: Translates byte order from host to network, pg 59.
**          recv: Receives messages on a connected socket, pg 69.
**          select: Returns read/write/exception status on sockets, pg 76.
**          send: Sends packets on a connected socket, pg 78.
**          shutdown: Shuts down all or part of a connection, pg 88.
**          socket: Requests that a socket be created, pg 89.
**          sock_init: Initializes socket data structures, and checks if
**            INET.EXE is running, pg 88.
**          soclose: Closes the socked associated with a descriptor, pg 92.
#endif
**
**  $Log:   S:/tbird/arcppc/lanservr/tcpip.c_v  $
** 
**    Rev 1.0   17 Jan 1997 09:22:12   kevin
** Initial revision.
** 
**    Rev 1.0   03 Jun 1996 11:30:40   gene
** Initial revision.
** 
**    Rev 1.0   07 Sep 1995 10:37:54   gene
** Initial revision.
** 
**    Rev 1.4   29 Apr 1994 06:35:52   doug
** Also accept WSAEINPROGRESS as a successful return from connect for OS/2.
** 
**    Rev 1.3   28 Mar 1994 11:20:26   nghia
** Remove call to StrLib.  - lmemcpy() is local function now.
** 
**    Rev 1.2   23 Mar 1994 16:48:52   tom
** Check for compatible versions
** 
**    Rev 1.1   11 Mar 1994 16:04:08   tom
** Cleanup and code merge with PC-NFS version.
** 
**    Rev 1.0   17 Jan 1994 11:49:04   tom
** Initial revision.
**
**  $Header:   S:/tbird/arcppc/lanservr/tcpip.c_v   1.0   17 Jan 1997 09:22:12   kevin  $
**
**  Copyright (C) 1993-4 Microtek International.  All rights reserved.
**
*****************************************************************************/

                       /****************************
                        *                          *
                        *       INCLUDE FILES      *
                        *                          *
                        ****************************/
// the following 2 lines indicates to PC-NFS that we're building for Windows
#define _Windows
#define _WINDLL

#ifndef _BASEWIND_
#include "basewind.h"
#endif

#ifndef _NETSERVE_
#include "netserve.h"
#endif

#ifndef _GENERICN_
#include "genericn.h"
#endif

#ifndef _HOSTERRS_
#include "hosterrs.h"
#endif

#ifndef _HLPENTRY_
#include "hlpentry.h"
#endif

#include <stdio.h>
#include <mem.h>
#include <string.h>

/* use with Borland C++ compiler */
#ifndef _BORLANDC_
#define _BORLANDC_
#endif

#ifdef PC_NFS
/* PC-NFS include files */
#include <sys\tk_types.h>
#include <sys\nfs_time.h>
#include <netinet\in.h>
#include <sys\socket.h>
#include <netdb.h>
#include <tklib.h>
#include <io.h>  // for close prototype
#include <tk_errno.h>  // for errno
#endif

#ifdef OS2_LAN_SERVER
/* OS/2 LAN SERVER files */
#include <winsock.h>
#endif

#ifndef _VERSIONS_
#include "versions.h"
#endif

#ifndef _CLIULIB_
#include "cliulib.h"
#endif

                       /****************************
                        *                          *
                        *     LOCAL DEFINITIONS    *
                        *                          *
                        ***************************/

// define toolkit dependent macros
#ifdef PC_NFS
#define FAR_POINTER _TKFAR
#define SOCKET_TYPE int
#endif

#ifdef OS2_LAN_SERVER
#define FAR_POINTER __far
#define SOCKET_TYPE SOCKET
#endif

// number of iterations through network operation before giving up
#define CONNECTION_WAIT_CYCLES 5
#define READ_RETRY_LIMIT 5

// shutdown both read and write circuits
#define READ_AND_WRITE_CIRCUITS 2

// macro to close socket and return error
#define CLOSE_AND_RETURN(socket,error) \
{ \
   genericNetworkClose((U32)(socket)); \
   return((RETCODE)(error)); \
}

// share the processor
#ifdef OS2_LAN_SERVER
#define SHARE_PROCESSOR \
{ \
   MSG msg; \
   (void)PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE); \
}
#endif

//
// read buffer; read a large chunk from the net, and then dole out of the
// buffer rather than going out to the net each time
//
#define READ_BUFFER_SIZE 512
static S8 readBuffer[READ_BUFFER_SIZE];
static S8 FAR *readBufferPointer;
static S16 readBufferBytesRemaining = 0;

//
// write buffer; buffer up the write requests until the buffer is full,
// or a flush command is received.
//
#define WRITE_BUFFER_SIZE 512
static S8 writeBuffer[WRITE_BUFFER_SIZE];
static S8 FAR *writeBufferPointer = (S8 FAR *)&writeBuffer;
static S16 writeBufferBytesFree = WRITE_BUFFER_SIZE;

static U16 readDelaySeconds = 0;

// buffer length for ID string
#define ID_STRING_LENGTH 32


                        /****************************
                         *                          *
                         *    EXTERNAL VARIABLES    *
                         *                          *
                         ****************************/

                        /****************************
                         *                          *
                         *     LOCAL PROTOTYPES     *
                         *                          *
                         ****************************/
RETCODE networkReadWithRetry(U32 connection, S8 FAR *buffer, U16 length);
RETCODE networkWriteAndFlush(U32 connection, S8 FAR *buffer, U16 length);
LPSTR lmemcpy(LPSTR dest, LPSTR source, WORD len);
                        /****************************
                         *                          *
                         *      EXECUTABLE CODE     *
                         *                          *
                         ****************************/
/**************************************************************************
**
** lmemcpy
**
***************************************************************************/
LPSTR lmemcpy(LPSTR dest, LPSTR source, WORD len) {
   WORD i;
   LPSTR s, d;

   s = source; d = dest;
   for (i = 0; i < len; i++) *d++ = *s++;
   if (i == len)
      return(dest);
   return(NULL);
}

/*****************************************************************************
**
**    genericNetworkConnect
**
*****************************************************************************/
RETCODE FAR PASCAL
genericNetworkConnect(U8 *hostName, U32 *connection)
{
   SOCKET_TYPE sock;     // socket handle
   S16 status;          // status of previous operation
   struct hostent FAR_POINTER *hp;  // ptr to emulator's inet name structure
   struct sockaddr_in server;    // emulator address structure
   struct sockaddr FAR_POINTER *saddr;  // pointer to server structure
   fd_set fds;          // for checking if connection completed
   struct timeval timeout;       // amount of time to wait for select
   S16 connected = FALSE;  // true if connection completed
   S16 index;           // loop index
   S8 idString[ID_STRING_LENGTH];  // holds ID string
   S8 currentIdString[ID_STRING_LENGTH];  // ID string of current user
   struct hostent *hostAddress;  // host addr corresponding to hostName
   S8 *addrBytes;
   S8 myHostName[ID_STRING_LENGTH];  // local host info for ID string
   U16 pvMajorVersion, myMajorVersion = VER_MAJOR;
   U16 pvMinorVersion, myMinorVersion = VER_MINOR;
   CHAR pvBuildVersion, myBuildVersion = VER_BUILD;
#ifdef OS2_LAN_SERVER
   WORD versionRequired = 1;  // requires winsock version 1.0 or above
   struct WSAData WSADataInstance;  // winsock info
   int errnoValue;
#endif
  
   // Make sure versions compatible
   (VOID)GetPowerViewsVersion(&pvMajorVersion, &pvMinorVersion,
      &pvBuildVersion);
   if ((pvMajorVersion != myMajorVersion) ||
       (pvMinorVersion != myMinorVersion) ||
       (pvBuildVersion != myBuildVersion))
   {
       return(ER_NET_INCOMPATIBLE_VERSION);
   }

   // Perform host-specific initialization, system checks.

#ifdef PC_NFS
   // make sure pc-nfs is running; returns 1 if yes, 0 if no
   if (is_pc_nfs_installed() == 0) {
      return(ER_PCNFS_NOT_RUNNING);
   }
   // make sure rtm tsr is installed
   if (rtm_version() == -1) {
      return(ER_PCNFS_RTM_NOT_RESIDENT);
   }

   // make sure tkdll.dll is accessible
   if (tkdll_version() == -1) {
      return(ER_PCNFS_TKDLL_NOT_FOUND);
   }

   // make sure rnm.dll is accessible
   if (rnm_version() == -1) {
      return(ER_PCNFS_RNM_NOT_FOUND);
   }
#endif

#ifdef OS2_LAN_SERVER
   errnoValue = 
      WSAStartup (versionRequired, (struct WSAData FAR *)&WSADataInstance);
   if (errnoValue != 0) {
      return(ER_OS2_LAN_SERVER_NOT_RUNNING);
   }
#endif

   // open stream socket; -1 returned if error, otherwise socket handle
#ifdef PC_NFS
   sock = socket(AF_INET, SOCK_STREAM, 0);
   if (sock == -1) {
#endif
#ifdef OS2_LAN_SERVER
   sock = socket(PF_INET, SOCK_STREAM, 0);
   
   if (sock == INVALID_SOCKET) {
      errnoValue = h_errno;
#endif
      return(ER_NET_COMM_ENDPOINT);
   }

#ifdef OS2_LAN_SERVER
   // set to non-blocking
   {
      u_long blockingOn = 1;

      if (ioctlsocket(sock, FIONBIO, &blockingOn)) {
         errnoValue = h_errno;
         return(ER_NET_COMM_ENDPOINT);
      }
   }
#endif

   // get host address structure given name; returns null if fails
   hp = gethostbyname((S8 *)hostName);
   if (hp == (struct hostent *) 0) {
#ifdef OS2_LAN_SERVER
      errnoValue = h_errno;
#endif
      CLOSE_AND_RETURN(sock,ER_NET_EMULATOR_INFO);
   }

   // place emulator address, net, and port into emulator address structure
   server.sin_port = htons(EMULATOR_PORT);    // port is in network byte order
   server.sin_family = AF_INET;
#ifdef PC_NFS
   memmove((void *)&server.sin_addr,    // destination
     (const void *)hp->h_addr_list[0],  // source
      hp->h_length);                    // length
#endif
#ifdef OS2_LAN_SERVER
   server.sin_addr.s_addr = *((unsigned long *)hp->h_addr);
#endif

   // connect; since this is non-blocking, expect failure due to block.
   // returns GOOD if success, -1 if failure.
   saddr = (struct sockaddr FAR_POINTER *)&server;
   status = connect(sock, saddr, sizeof(server));
   if (!status) {
      connected = TRUE;  // connection completed
   }
   else {
#ifdef PC_NFS
      if (errno != EWOULDBLOCK) {
#endif
#ifdef OS2_LAN_SERVER
      if ((h_errno != WSAEWOULDBLOCK) && (h_errno != WSAEINPROGRESS)) {
         errnoValue = h_errno;
#endif
         CLOSE_AND_RETURN(sock,ER_NET_CONNECT);   // connection failed
      }

      // connection not completed; poll for completion
      for(index = 1; index < CONNECTION_WAIT_CYCLES && !connected; ++index) {
         FD_ZERO(&fds);  // clear fd's to check
         FD_SET(sock, &fds);   // check fd for connect completed
         timeout.tv_sec = 1;   // set timeout for 1 second
         timeout.tv_usec = 0;
         connected = select(FD_SETSIZE, NULL, &fds, NULL, &timeout);
         if (connected == -1) {
#ifdef OS2_LAN_SERVER
            errnoValue = h_errno;
#endif
            CLOSE_AND_RETURN(sock,ER_NET_CONNECT);  // connection failed
         }
#ifdef OS2_LAN_SERVER
         // share the processor
         if (!connected) {
            SHARE_PROCESSOR;
         }
#endif
      }
   }

   // if not connected, return timeout error
   if (!connected) {
      CLOSE_AND_RETURN(sock,ER_NET_CONNECT_TIMEOUT);
   }

#ifdef OS2_LAN_SERVER
   // fill the receiving string with nulls (cover for bug in IBM toolkit)
   {
      int i;

      for (i=0; i < ID_STRING_LENGTH; ++i)
         myHostName[i] = '\0';
   }
#endif

   // gather information about host, and send to emulator
   {
      BOOLEAN errorDetected = FALSE;

      // get host name
      if (gethostname(myHostName, ID_STRING_LENGTH)) {
         ErrDisplayError(ER_NET_HOST_INFO, FORCE_POPUP);
         errorDetected = TRUE;
      }
      else {
         // get host address
         hostAddress = gethostbyname(myHostName);
         if (hostAddress == (struct hostent *) 0) {
            ErrDisplayError(ER_NET_HOST_INFO, FORCE_POPUP);
            errorDetected = TRUE;
         }
      }

      // put ID string together
      if (!errorDetected) {
         addrBytes = hostAddress->h_addr_list[0];
         sprintf(idString, "%u.%u.%u.%u (%s)",
            (unsigned)(0xFF & addrBytes[0]),
            (unsigned)(0xFF & addrBytes[1]),
            (unsigned)(0xFF & addrBytes[2]),
            (unsigned)(0xFF & addrBytes[3]),
            myHostName);
      }
      else {
         strcpy(idString, "<<unknown>>");
      }

      // write ID string to emulator
      status = networkWriteAndFlush(sock, idString, ID_STRING_LENGTH);
      if (status != GOOD) {
         CLOSE_AND_RETURN(sock, status);
      }
   }

   // read ID of current user if any
   status = networkReadWithRetry(sock, currentIdString, ID_STRING_LENGTH);
   if (status != GOOD) {
      CLOSE_AND_RETURN(sock, status);
   }

   // process current ID
   if (currentIdString[0] != '\0') {
      S8 promptString[128];
      S16 buttonID;

      // already connected to someone else; prompt for override
      sprintf(promptString, "Emulator \"%s\" is in use by %s.  \
         Do you wish to override the current connection?",
         (S8 *)hostName, currentIdString);

      status = ErrMessageBox((LPSTR) "PowerViews", (LPSTR)promptString,
         MB_ICONQUESTION | MB_YESNO, HE_NET_EMULATOR_BUSY,
         (S16 FAR *) &buttonID);
      if (status != GOOD) {
         return (status);
      }

      // write "1" to override, "0" to abort
      if (buttonID == IDYES) {
         status = networkWriteAndFlush(sock, "1", 1);
         if (status != GOOD) {
            CLOSE_AND_RETURN(sock, status);
         }
      }
      else {
         (VOID)networkWriteAndFlush(sock, "0", 1);
         // ignore return code; abort connection
         CLOSE_AND_RETURN(sock, ER_NET_EMULATOR_IN_USE);
      }
   }

   // otherwise, connection complete; return connection fd
   *connection = (U32)sock;
   return(GOOD);
}

/*****************************************************************************
**
**  genericNetworkRead
**
**  Note: only try once; must not block.  Upper levels are responsible for
**    retry if needed.
**
*****************************************************************************/
RETCODE FAR PASCAL
genericNetworkRead(U32 connection, U8 FAR *buffer, U16 FAR *length)
{
#ifdef PC_NFS
   fd_set fds;          // for checking if connection completed
   struct timeval timeout;       // amount of time to wait for select
#endif
   RETCODE err;

   // nothing to do if length less than or equal to 0
   if (*length < 1) {
      *length = 0;
      return(GOOD);
   }

   // a read operation forces a flush; avoid call if possible
   if (WRITE_BUFFER_SIZE != writeBufferBytesFree) {
      err = genericNetworkFlush(connection);
      if (err != GOOD) {
         return(err);
      }
   }

   // if no bytes are in the buffer, read some.
   if (!readBufferBytesRemaining) {
#ifdef PC_NFS
      // check the connection for bytes to be read
      FD_ZERO(&fds);
      FD_SET(connection, &fds);
      timeout.tv_sec = readDelaySeconds;
      timeout.tv_usec = 0;

      // see if there are bytes to be read; returns -1 if error, 0 if timed out
      // before bytes available to read, and 1 if bytes to be read.
      switch(select(FD_SETSIZE, &fds, NULL, NULL, &timeout)) {
         case -1:
            // fatal error
            readBufferBytesRemaining = 0;
            CLOSE_AND_RETURN(connection, ER_NET_READ);
            // break not needed; previous macro exits function
         case  0:
            // no bytes available to read
            *length = 0;  // indicate no bytes read
            return(GOOD);
         default:
            // bytes available to read; fill the local buffer
            readBufferBytesRemaining =
               recv((SOCKET_TYPE)connection, readBuffer, READ_BUFFER_SIZE, 0);
            if (readBufferBytesRemaining < 0) {
               // fatal read error
               readBufferBytesRemaining = 0;
               CLOSE_AND_RETURN(connection, ER_NET_READ);
            }
            readBufferPointer = (S8 FAR *)&readBuffer;

      }  // end switch
#endif
#ifdef OS2_LAN_SERVER
      readBufferBytesRemaining =
         recv((SOCKET_TYPE)connection, readBuffer, READ_BUFFER_SIZE, 0);
      switch (readBufferBytesRemaining) {
         case -1:
            readBufferBytesRemaining = 0;  // place useful value (prevent wrap)
            // OK if error is WSAEWOULDBLOCK
            if (h_errno == WSAEWOULDBLOCK) {
               *length = 0;  // indicate no bytes read
               return(GOOD);
            }
            // fatal error
            CLOSE_AND_RETURN(connection, ER_NET_READ);
            // break not needed; previous macro exits function
         case  0:
            // no bytes available to read
            *length = 0;  // indicate no bytes read
            return(GOOD);
         default:
            // bytes read; fill the local buffer
            readBufferPointer = (S8 FAR *)&readBuffer;

      }  // end switch
#endif
   }

   // There are now bytes in the read buffer; transfer them.
   *length = min(*length, readBufferBytesRemaining);
   lmemcpy((LPSTR)buffer, (LPSTR)readBufferPointer, *length);
   readBufferBytesRemaining -= *length;  // reduce byte count
   readBufferPointer += *length;   // advance buffer pointer

   return(GOOD);
}

/*****************************************************************************
**
**    genericNetworkWrite
**
*****************************************************************************/
RETCODE FAR PASCAL
genericNetworkWrite(U32 connection, U8 FAR *buffer, U16 FAR *length)
{
   S16 bytesLeft;  // number of bytes remaining to be written
   S16 transferLength;  // number of bytes to place in buffer this iteration
   S8 FAR *bufptr; // pointer into buffer where next write segment starts
   RETCODE err;

   // nothing to do if length less than or equal to 0
   if (*length < 1) {
      *length = 0;
      return(GOOD);
   }

   // length, buffer pointer initially match input parameters.
   bytesLeft = (S16)*length;
   bufptr = (S8 *)buffer;

   // write(buffer) bytes as long as there are bytes available
   while(bytesLeft) {
      // transfer bytes into buffer, update pointers & lengths
      transferLength = min(bytesLeft, writeBufferBytesFree);
      lmemcpy((LPSTR)writeBufferPointer, (LPSTR)bufptr, transferLength);
      bytesLeft -= transferLength;
      bufptr += transferLength;
      writeBufferBytesFree -= transferLength;
      writeBufferPointer += transferLength;

      // flush buffer if full
      if (writeBufferBytesFree == 0) {
         err = genericNetworkFlush(connection);
         if (err != GOOD) {
            return(err);
         }
      }
   }  // end while

   // will always write (buffer) all of the bytes, so leave length
   // unchanged to indicate all of the bytes were processed
   return(GOOD);
}

/*****************************************************************************
**
**    genericNetworkFlush: Flush the buffered writes.
**
*****************************************************************************/
RETCODE FAR PASCAL
genericNetworkFlush(U32 connection)
{
#ifdef PC_NFS
   fd_set fds;          // for checking if connection completed
   struct timeval timeout;       // amount of time to wait for select
#endif
   S16 bytesToWrite;   // total number of bytes (left) to write
   S16 bytesWritten;   // number of bytes written so far

   bytesToWrite = WRITE_BUFFER_SIZE - writeBufferBytesFree;

   // nothing to do if nothing is in the buffer
   if (bytesToWrite == 0) {
      return(GOOD);
   }

   // reposition pointer to beginning of buffer to be written
   writeBufferPointer = (S8 FAR *)&writeBuffer;

   // write bytes as long as there are bytes to be written
   while(bytesToWrite) {
#ifdef PC_NFS
      // check the connection for OK to write
      FD_ZERO(&fds);
      FD_SET(connection, &fds);
      timeout.tv_sec = 1;  // set timeout for 1 sec
      timeout.tv_usec = 0;

      // see if it is OK to write; returns -1 if error, 0 if timed out
      // before bytes can be written, and 1 if OK to write.
      switch(select(FD_SETSIZE, NULL, &fds, NULL, &timeout)) {
#endif
#ifdef OS2_LAN_SERVER
      bytesWritten = send((SOCKET_TYPE)connection, writeBufferPointer,
         bytesToWrite, 0);
      switch(bytesWritten) {
#endif
         case -1:
#ifdef OS2_LAN_SERVER
            // OK if error is WSAEWOULDBLOCK
            if (h_errno == WSAEWOULDBLOCK) {
               break;  // do nothing: can't write yet
            }
#endif
            CLOSE_AND_RETURN(connection,ER_NET_WRITE);
            // break not needed; previous macro returns
         case 0:
            break;  // do nothing: can't write yet
         default:
#ifdef PC_NFS
            bytesWritten = send(connection, writeBufferPointer,
               bytesToWrite, 0);
            if (bytesWritten == -1) {
               // fatal read error
               CLOSE_AND_RETURN(connection,ER_NET_WRITE);
            }
#endif
            // update bytes remaining to be read, buffer pointer
            bytesToWrite -= bytesWritten;
            writeBufferPointer += bytesWritten;
      }  // end switch
#ifdef OS2_LAN_SERVER
      // share the processor
      if (bytesToWrite) {
         SHARE_PROCESSOR;
      }
#endif
   }  // end while

   // mark buffer as empty
   writeBufferPointer = (S8 FAR *)&writeBuffer;
   writeBufferBytesFree = WRITE_BUFFER_SIZE;

   return(GOOD);
}

/*****************************************************************************
**
**    genericNetworkClose
**
*****************************************************************************/
RETCODE FAR PASCAL
genericNetworkClose(U32 connection)
{
   S16 stat1, stat2;


   // shut down both read and write circuits, then close file handle.
   stat1 = shutdown((SOCKET_TYPE)connection, READ_AND_WRITE_CIRCUITS);
#ifdef PC_NFS
   stat2 = close((SOCKET_TYPE)connection);
#endif
#ifdef OS2_LAN_SERVER
   stat2 = closesocket((SOCKET_TYPE)connection);
#endif

   // return error status if either call failed
   if (stat1 || stat2) {
      return(ER_NET_CLOSE);
   }

   return(GOOD);
}

/*****************************************************************
**
** networkReadWithRetry
**
** Purpose:  Read the specified number of bytes; don't return until
**    they've been read, or the read times out.
**
** Parameters:
**    connection (in):  the connection.
**    buffer (in/out):  the bytes read.
**    length (in):  the number of bytes to read.
**
******************************************************************/
RETCODE networkReadWithRetry(U32 connection, S8 FAR *buffer, U16 length)
{
#ifdef PC_NFS
   U16 index;
#endif
   U16 bytesRead;
   U16 tempLength, *tempLengthP;
   S8 FAR *bufP;
   RETCODE status;

   tempLength = length;
   tempLengthP = &tempLength;
   bytesRead = 0;
   bufP = buffer;

   /* continue until all bytes written, or timed out */
#ifdef PC_NFS
   for (index = 1; (index < READ_RETRY_LIMIT) && (bytesRead < length); ++index)
#endif
#ifdef OS2_LAN_SERVER
   while (bytesRead < length)
#endif
   {
      readDelaySeconds = 1;
      status = genericNetworkRead(connection, (U8 FAR *)bufP, tempLengthP);
      readDelaySeconds = 0;
      if (status != GOOD) {
         return (status);
      }

      /* update pointers and counters */
      bytesRead += tempLength;
      bufP += tempLength;
      tempLength = length - bytesRead;
   }

   /* see if task completed */
   if (bytesRead < length) {
      return (ER_NET_READ_TIMEOUT);
   }

   return (GOOD);
}


/*****************************************************************
**
** networkWriteAndFlush
**
** Purpose:  Write the specified number of bytes, and flush them.
**
** Parameters:
**    connection (in):  the connection.
**    buffer (in):  the bytes to write.
**    length (in):  the number of bytes to write.
**
******************************************************************/
RETCODE networkWriteAndFlush(U32 connection, S8 FAR *buffer, U16 length)
{
   U16 tempLength;
   RETCODE status;

   tempLength = length;
   status = genericNetworkWrite(connection, (U8 FAR *)buffer, &tempLength);
   if (status != GOOD) {
      return (status);
   }

   status = genericNetworkFlush(connection);
   return (status);
}

//-------------------------- E O F -----------------------------------------
