/*
**	Command & Conquer Generals(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program 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 program 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 <http://www.gnu.org/licenses/>.
*/

/****************************************************************************\
TCP                   Neal Kettler        neal@westwood.com
******************************************************************************

A general purpose TCP class that can be used in either CLIENT or
SERVER mode.  Note that this uses non-blocking sockets.

The FD_* macros:

  FD_CLR(int fd, fd_set *set);     // clear a single FD 
  FD_ISSET(int fd, fd_set *set);   // check whether a single FD is set
  FD_SET(int fd, fd_set *set);     // set a single FD
  FD_ZERO(fd_set * set);           // clear the entire set

NOTE:  The fd_set returned by 'Wait' is static, don't call delete
on it!


If you are writing a CLIENT:
   The last argument to many functions is an integer whichFD, this is used
only by SERVER mode, so you can omit this argument.  Sample Code:

fd_set *fdSet;
uint8  *buff=new uint8[1024];
int     retval;
TCP tcp(CLIENT);

tcp.Bind((uint32)0,(uint16)0); // let system pick local IP and a Port for you 
tcp.Connect("tango",13);       // can connect by name or "10.1.1.10"
                               // or the integerßin host byte order

fdSet=tcp.Wait(10,0);          // wait for UP TO 10 sec and 0 microseconds
if (FD_ISSET(tcp.GetFD(),fdSet))   // Is there something to read?
{
  retval=tcp.Read(buff,1024);  // Read something
                               //  Retval will contain the number of
                               //  bytes read, or...
                               //  0 = remote end closed connection
                               // -1 = nothing to read
  fprintf(stderr,"%s",buff);
}
else
  fprintf(stderr,"Nothing was read!\n");


If you are writing a SERVER:

    The structure called 'clientList' contains all the File Descriptors
that have connected to the server.  Make sure you look at the FD_*
functions so you can use this sort of structure.  When you are writing
a server, you need to specify the 'whichFD' arguments to all the
functions. Sample Code:

fd_set *fdSet;
uint8  *buff=new uint8[1024];
int     retval;
TCP     tcp(SERVER);

tcp.Bind((uint32)0,(uint16)2121);    // You need to bind to a well defined
                              //  port number or nobody will know where
                              //  to connect to.

while (1)
{
  fdSet=tcp.Wait(-1,-1);        // Wait until there is something on the socket
  if (FD_ISSET(tcp.GetFD(),fdSet))  // somebody must want a connection
  {
    retval=tcp.GetConnection(); // Get a connection if somebody's trying
    if (retval!=-1)
    {
      tcp.Write("Hello World!\n",strlen("Hello World!\n"),retval);
      tcp.Close(retval);
    }
  }
}


\****************************************************************************/


#include "tcp.h"
#include <stdarg.h>

#ifndef _WINDOWS
#include <errno.h>
#define closesocket close
#endif

// newMode should be either CLIENT or SERVER
TCP::TCP(int new_mode)
{
  mode=CLIENT;
  maxFD=0;
  fd = -1;
  clientCount=0;
  if ((new_mode==CLIENT)||(new_mode==SERVER))
    mode=new_mode; 
  FD_ZERO(&clientList);
  connectionState=CLOSED;
  inputDelay=5;
  outputDelay=5;
}

// Create a TCP object on a pre-existing socket
TCP::TCP(int new_mode,sint16 socket)
{
  sint32 retval;

  mode=CLIENT;
  maxFD= socket;
  fd = socket;
  clientCount=0;
  if ((new_mode==CLIENT)||(new_mode==SERVER))
    mode=new_mode; 
  FD_ZERO(&clientList);

  inputDelay=5;
  outputDelay=5;


  retval=SetBlocking(FALSE,socket);      // set to NB mode
  //DBGMSG("Setblocking: "<<retval);

  connectionState=CLOSED;
  if (mode==CLIENT)               // determine what state the socket is in
  {
    connectionState=CONNECTING;   // this is used when state is unsure
    if (IsConnected(socket))
      connectionState=CONNECTED;
    else
      connectionState=CLOSED;
  }
  //DBGMSG("Connstate = "<<connectionState);
}


TCP::~TCP()
{
   CloseAll();
}


int TCP::GetFD()
{
  return(fd);
}


// private function
sint32 TCP::SetBlocking(bit8 block,sint32 whichFD)
{
   if (whichFD==0)
     whichFD=fd;

   #ifdef _WINDOWS
   unsigned long flag=1;
   if (block)
     flag=0;
   int retval;
   retval=ioctlsocket(whichFD,FIONBIO,&flag);
   if (retval==SOCKET_ERROR)
     return(-1);
   else
     return(0);
   #else
   int flags = fcntl(whichFD, F_GETFL, 0);
   if (block==FALSE)          // set nonblocking
     flags |= O_NONBLOCK;
   else                       // set blocking
     flags &= ~(O_NONBLOCK);  

   if (fcntl(whichFD, F_SETFL, flags) < 0)
   {
     return(-1);
   }
   return(0);
   #endif
}


sint32 TCP::GetMaxFD(void)
{
  if (mode==CLIENT)
    return(fd);
  else if (mode==SERVER)
    return(maxFD);
  else
    return(-1);
}

// Only specify whichFD if this is a server application
sint32 TCP::Write(const uint8 *msg,uint32 len,sint32 whichFD)
{
  sint32 retval;

  if (whichFD==0)
  {
    if (mode==SERVER)
      assert(FALSE);
    whichFD=fd;
  }
  SetBlocking(TRUE,whichFD); 
  retval=send(whichFD,(const char *)msg,len,0);
  #ifdef _WINDOWS
    if (retval==SOCKET_ERROR)
      retval=-1;
  #endif
  SetBlocking(FALSE,whichFD);
  return(retval);
}


// Only specify whichFD if this is a server application
// NON BLOCKING WRITE
sint32 TCP::WriteNB(uint8 *msg,uint32 len,sint32 whichFD)
{
  sint32 retval;

  if (whichFD==0)
  {
    if (mode==SERVER)
      assert(FALSE);
    whichFD=fd;
  }
  retval=send(whichFD,(const char *)msg,len,0);
  #ifdef _WINDOWS
    if (retval==SOCKET_ERROR)
      retval=-1;
  #endif
  return(retval);
}


// Encapsulate data for lame ass proxys that won't pass 0's or 255's through
//   0 goes to 1,1
//   1 goes to 1,2
// 255 goes to 1,3
// everything else is the same
sint32 TCP::EncapsulatedWrite(uint8 *msg,uint32 len,sint32 whichFD)
{
  sint32 retval;
  uint32 i,bytesSent=0;
  uint8  data,one=1;

  if (mode==CLIENT)
    whichFD=fd;
  SetBlocking(TRUE,whichFD);
  for (i=0; i<len; i++)
  {
    data=msg[i];
    if ((data>1)&&(data<255))
    {
      retval=send(whichFD,(char *)&data,1,0);
      if (retval<1)
      {
        SetBlocking(FALSE,whichFD);
        return(i);
      }
      bytesSent++;
    }
    else
    {
      retval=send(whichFD,(char *)&one,1,0);
      if (retval<1)
      {
        SetBlocking(FALSE,whichFD);
        return(i);
      }
      if (data==0)
        data=1;
      else if (data==1)
        data=2;
      else if (data==255) 
        data=3;

      retval=send(whichFD,(char *)&data,1,0);
      if (retval<1)
      {
        SetBlocking(FALSE,whichFD);
        return(i);
      }
      bytesSent+=2;
    }
  }
  SetBlocking(FALSE,whichFD);
  ///fprintf(stderr,"\n\nENCAP SENT %d\n\n",bytesSent);
  return(len);
}


// Make sure string is '\0' terminated
sint32 TCP::WriteString(char *msg,sint32 whichFD)
{
  if (mode==CLIENT)
    whichFD=fd;

  WaitWrite(whichFD);

  sint32 retval;

  if (mode==CLIENT)
  {
    SetBlocking(TRUE,fd);
    retval=send(fd,msg,strlen(msg),0);
    SetBlocking(FALSE,fd);
    return(retval);
  }
  else if (mode==SERVER)
  {
    if ((whichFD<=maxFD) && (FD_ISSET(whichFD,&clientList)))
    {
      SetBlocking(TRUE,whichFD);
      retval=send(whichFD,msg,strlen(msg),0);
      SetBlocking(FALSE,whichFD);
      return(retval);
    }
  }
  return(-1);
}


// only use for strings up to 1024 chars!
sint32 TCP::Printf(sint32 whichFD,const char *format,...)
{
  va_list arg;
  char string[1024];
  sint32 retval;
  va_start(arg,format);
  vsprintf(string,format,arg);
  va_end(arg);

  if (mode==CLIENT)
    whichFD=fd;

  WaitWrite(fd);
  if (mode==CLIENT)
  {
    SetBlocking(TRUE,whichFD);
    retval=send(fd,string,strlen(string),0);
    SetBlocking(FALSE,whichFD);
    return(retval);
  }
  else if (mode==SERVER)
  {
    if ((whichFD<=maxFD) && (FD_ISSET(whichFD,&clientList)))
    {
      SetBlocking(TRUE,whichFD);
      retval=send(whichFD,string,strlen(string),0);
      SetBlocking(FALSE,whichFD);
      return(retval);
    }
  }
  return(-1);
}



// Returns 0 on failure
// Returns IP in host byte order!
uint32 TCP::GetRemoteIP(sint32 whichFD)
{
  struct sockaddr_in sin;
  int    sinSize=sizeof(sin);

  if (mode==CLIENT)
  {
    if(getpeername(fd,(sockaddr *)&sin,&sinSize)==0)
      return(ntohl(sin.sin_addr.s_addr));
  }
  else if (mode==SERVER)
  {
    if(getpeername(whichFD,(sockaddr *)&sin,&sinSize)==0)
      return(ntohl(sin.sin_addr.s_addr));
  }
  return(0);
}


// Returns 0 on failure
// Returns Port in host byte order!
uint16 TCP::GetRemotePort(sint32 whichFD)
{
  struct sockaddr_in sin;
  int    sinSize=sizeof(sin);

  if (mode==CLIENT)
  {
    if(getpeername(fd,(sockaddr *)&sin,&sinSize)==0)    
      return(ntohs(sin.sin_port));
  }
  else if (mode==SERVER)
  {
    if(getpeername(whichFD,(sockaddr *)&sin,&sinSize)==0)   
      return(ntohs(sin.sin_port));
  }
  return(0); 
}


// Is the FD connected?
bit8 TCP::IsConnected(sint32 whichFD)
{
  struct sockaddr_in sin;
  int    sinSize=sizeof(sin);

  if (mode==CLIENT)
    whichFD=fd;

  if (mode==CLIENT)
  {
    if (connectionState==CONNECTED)
      return(TRUE);
    if (connectionState==CLOSED)
      return(FALSE);
  }

  // only get here if state==CONNECTING
  if(getpeername(whichFD,(sockaddr *)&sin,&sinSize)==0)
    if ( (sin.sin_addr.s_addr!=htonl(0)) && (CanWrite(whichFD)) )
    {
      connectionState=CONNECTED;
      return(TRUE);
    }
  return(FALSE);
}
    

// Not portable?
/**************
sint32 TCP::GetSockStatus(sint32 whichFD)
{
  sint32 retval;
  int status,size=sizeof(int);

  if (whichFD==0)
    whichFD=fd;
  retval=getsockopt(whichFD,SOL_SOCKET,SO_ERROR,(char *)&status,&size);
  if (retval==-1)
    return(-1);
  return(status); 
}
*******************/



// The TCP equivalent of fgets()
char *TCP::Gets(char *string,int n,int whichFD)
{
  char c;
  int  retval,i=0;
  fd_set fdSet;

  if (whichFD==0)
    whichFD=GetFD();

  if (whichFD <= 0)
    return(NULL);

  memset(string,0,n);

  while(1)
  {
    if (i==n)
      return(string);

    Wait(inputDelay,0,fdSet,whichFD);   // inputDelay = 5 sec or so
    if (! FD_ISSET(whichFD,&fdSet))
    {
      DBGMSG("Gets timeout: " << inputDelay);
      return(NULL);
    }

    retval=Read((unsigned char *)&c,1,whichFD);
    if ((retval>0)&&(c!=0))
    {
      string[i]=c;
      if (c=='\n')
        return(string);
      i++;
    }
    else if ((retval==0)&&(i==0))
    {
      DBGMSG("Remote endpoint closed (1)");
      return(NULL);
    }
    else if (retval==0)
      return(string);
  }
  return(string);
}


// only specify whichFD if this is a server
sint32 TCP::Read(uint8 *msg,uint32 len,sint32 whichFD)
{
  sint32 retval;
  //DBGMSG("In read, mode: "<<mode<<"  FD: "<<fd);
  if (mode==CLIENT)
  {
    retval=recv(fd,(char *)msg,len,0);
    ////////DBGMSG("READ: "<<retval << "   ON FD: " << fd << "  LEN: "<< len);
    if (retval==0)
      Close();
    return(retval);
  }
  else if (mode==SERVER)
  {
    if ((whichFD<=maxFD) && (FD_ISSET(whichFD,&clientList)))
    {
      retval=recv(whichFD,(char *)msg,len,0);
      if (retval==0)
      {
        Close(whichFD);
      }
      return(retval);
    }
    else
    {
      return(0);  // closed
    }
  }
  return(-1);
}


// only specify whichFD if this is a server
// Try and read 'len' bytes until the timer goes out.
// This is effectively a blocking call, but it's still useful
// in threaded environments.
sint32 TCP::TimedRead(uint8 *msg,uint32 len,int seconds,sint32 whichFD)
{
  fd_set    set;
  sint32    bytes_read=0;
  sint32    retval;

  time_t stop_time=time(NULL)+seconds;
  while ((time(NULL)<=stop_time)&&((uint32)bytes_read<len))
  {
    Wait(1,0,set,whichFD);
    //DBGMSG("Calling read");
    retval=Read(msg+bytes_read,len-bytes_read,whichFD);
    if (retval==0)    // they closed
    {
      DBGMSG("Remote close!\n");
      return(bytes_read);
    }
    else if (retval>0)
      bytes_read+=retval;
    // otherwise some error
  }
  return(bytes_read);
}



// only specify whichFD if this is a server
// Peek at data in system buffer
sint32 TCP::Peek(uint8 *msg,uint32 len,sint32 whichFD)
{
  sint32 retval;
  if (mode==CLIENT)
  {
    retval=recv(fd,(char *)msg,len,MSG_PEEK);
    if (retval==0)
      Close();
    return(retval);
  }
  else if (mode==SERVER)
  {
    if ((whichFD<=maxFD) && (FD_ISSET(whichFD,&clientList)))
    {
      retval=recv(whichFD,(char *)msg,len,MSG_PEEK);
      if (retval==0)
        Close(whichFD);
      return(retval);
    }
    else
     return(0);  // closed
  }
  return(-1);
}


// only specify whichFD if this is a server
// (this is used for non-8 bit clean pipes, you probably don't
//   want to use it!)
sint32 TCP::EncapsulatedRead(uint8 *msg,uint32 len,sint32 whichFD)
{
  sint32 retval,bytesRead=0;
  uint32 i;
  char data;

  if (mode==CLIENT)
    whichFD=fd;
  else if (mode==SERVER)
  {
   if ((whichFD>maxFD) || (!FD_ISSET(whichFD,&clientList)))
     return(0);
  }
  else
    return(-1);

  for (i=0; i<len; i++)
  {
    retval=recv(fd,&data,1,0);
    if (retval==0)
    {
      Close();
      return(bytesRead);
    }
    if (retval==1)
    {
      bytesRead++;
      if (data==1)
      {
        retval=0;
        while(retval!=1)
        {
          retval=recv(fd,&data,1,0);
          if (retval==0)
          {
            Close();
            return(bytesRead);
          }
        }
        if (data==1)
          data=0;
        else if (data==2)
          data=1;
        else if (data==3)
          data=(char)255; 
      }
      msg[i]=data; 
    }
    if (retval==-1)
      return(bytesRead);
  }
  return(bytesRead);
}


sint32 TCP::CloseAll(void)
{
  int i;

  if (mode==CLIENT)
    return(Close());
  
  for(i=0; i<=maxFD; i++)
  {
    if ((i!=fd)&&(FD_ISSET(i,&clientList)))
      Close(i);
  }
  return(Close(fd));  // close the master fd last
}

//
// For clients this is used to give up ownership of a socket.
//  Often used so the destructor won't call close on a socket.
//
void TCP::DisownSocket(void)
{
  if (mode==CLIENT)
  {
    fd=-1;
    connectionState=CLOSED;
  }
}

// for a server 0 = master FD, or a client FD can be passed in
// for a client the whichFD argument is ignored completely
sint32 TCP::Close(sint32 whichFD)
{
  int i;
  if (mode==CLIENT)
  {
    connectionState=CLOSED;
    if(fd != -1)
    {
      sint32 retval = closesocket(fd);
      fd = -1;
      return retval;
    }
  }
  else if (mode==SERVER)
  {
     if (whichFD==0)
     {
       if (shutdown(fd,2)==0)
         return(closesocket(fd));
       else
         return(-1);
     } 
     else if ((whichFD<=maxFD) && (FD_ISSET(whichFD,&clientList)))
     {
       if (whichFD==maxFD)  // make sure maxFD is still correct
       {
          for (i=maxFD; i>=0; i--)
            if (FD_ISSET(i,&clientList))
            {
               maxFD=i;
               break;
            }
       }
       FD_CLR((uint32)whichFD,&clientList);
       clientCount--;
       return(closesocket(whichFD));
     }
  }
  return(-1);
}



// if 'sec' AND 'usec' are -1 then this will sleep until
// there is socket activity

int TCP::Wait(sint32 sec,sint32 usec,fd_set &returnSet,sint32 whichFD)
{
  fd_set inputSet;

  FD_ZERO(&inputSet);

  if (mode==SERVER)
  {
    if (whichFD==0)
    {
      inputSet=clientList;
      if (fd > 0)
        FD_SET(fd,&inputSet);
    }
    else if (whichFD > 0)
      FD_SET(whichFD,&inputSet);
  }
  else if (mode==CLIENT)
  {
    if (whichFD==0)
      whichFD=fd;
    if (whichFD > 0)
      FD_SET(whichFD,&inputSet);
  }

  return(Wait(sec,usec,inputSet,returnSet));
}

int TCP::Wait(sint32 sec,sint32 usec,fd_set &givenSet,fd_set &returnSet)
{
  Wtime        timeout;
  Wtime        timenow;
  Wtime        timethen;
  fd_set       backupSet;
  int          retval=0,done,givenMax;
  bit8         noTimeout=FALSE;
  timeval      tv;

  returnSet=givenSet;
  backupSet=returnSet;

  if ((sec==-1)&&(usec==-1))
    noTimeout=TRUE;

  timeout.SetSec(sec);
  timeout.SetUsec(usec);
  timethen+=timeout;

  givenMax=maxFD;
  for (uint32 i=0; i<(sizeof(fd_set)*8); i++)   // i=maxFD+1
  {
    if (FD_ISSET(i,&givenSet))
      givenMax=i;
  }

  done=0;
  while( ! done)
  {
    if (noTimeout)
      retval=select(givenMax+1,&returnSet,0,0,NULL);
    else
    {
      timeout.GetTimevalMT(tv);
      retval=select(givenMax+1,&returnSet,0,0,&tv);
    }

    if (retval>=0)
      done=1;

    else if ((retval==-1)&&(errno==EINTR))  // in case of signal
    {
      if (noTimeout==FALSE)
      {
        timenow.Update();
        timeout=timethen-timenow;
      }
      if ((noTimeout==FALSE)&&(timenow.GetSec()==0)&&(timenow.GetUsec()==0))
        done=1;
      else
        returnSet=backupSet;
    }
    else  // maybe out of memory?
    {
      done=1;
    }
  }
  return(retval);
}


void TCP::WaitWrite(sint32 whichFD)
{
  fd_set       backupSet;
  int          retval=0,done;
  fd_set       outputSet;

  if (whichFD==0)
    whichFD=fd;

  if (whichFD==-1)
    return;

  FD_ZERO(&outputSet);
  FD_SET(whichFD,&outputSet);
  backupSet=outputSet;

  done=0;
  while( ! done)
  {
    retval=select(maxFD+1,0,&outputSet,0,NULL);

    if (retval>=0)
      done=1;

    else if ((retval==-1)&&(errno==EINTR))  // in case of signal
      outputSet=backupSet;
    else  // maybe out of memory?
      done=1;
  }
}

// Can a FD be written to?
bit8 TCP::CanWrite(sint32 whichFD)
{
  int          retval=0;
  fd_set       outputSet;
  Wtime     timeout;
  timeval      tv;

  timeout.SetSec(0);
  timeout.SetUsec(0);

  if (whichFD==0)
    whichFD=fd;

  FD_ZERO(&outputSet);
  FD_SET(whichFD,&outputSet);

  timeout.GetTimevalMT(tv);
  retval=select(whichFD+1,0,&outputSet,0,&tv);
  if (retval>0)
    return(TRUE);
  else
    return(FALSE);
}


bit8 TCP::Bind(char *Host,uint16 port,bit8 reuseAddr)
{
  char hostName[100];
  struct hostent *hostStruct;
  struct in_addr *hostNode;

  if (isdigit(Host[0]))
    return ( Bind( ntohl(inet_addr(Host)), port,reuseAddr) );

  strcpy(hostName, Host);

  hostStruct = gethostbyname(Host);
  if (hostStruct == NULL)
    return (0);
  hostNode = (struct in_addr *) hostStruct->h_addr;
  return ( Bind(ntohl(hostNode->s_addr),port,reuseAddr) );
}


// You must call bind, implicit binding is for sissies
//   Well... you can get implicit binding if you pass 0 for either arg

bit8 TCP::Bind(uint32 IP,uint16 Port,bit8 reuseAddr)
{
  int retval; 
  int status;

  IP=htonl(IP);
  Port=htons(Port);

  addr.sin_family=AF_INET;
  addr.sin_port=Port;
  addr.sin_addr.s_addr=IP;
  fd=socket(AF_INET,SOCK_STREAM,DEFAULT_PROTOCOL);
  if (fd==-1)
    return(FALSE);

  retval=SetBlocking(FALSE,fd);
  if (retval==-1)
    ERRMSG("Couldn't set nonblocking mode!");

  if (reuseAddr==TRUE)
  {
    uint32 opval;

    #ifdef SO_REUSEPORT
/******************  this may make the socket get garbage data??
    opval=1;
    retval=setsockopt(fd,SOL_SOCKET,SO_REUSEPORT,(char *)&opval,sizeof(opval));
    if (retval!=0) 
      fprintf(stderr,"Could not set socket to SO_REUSEPORT\n");
**********************/
    #endif
    #ifdef SO_REUSEADDR 
    opval=1;
    retval=setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char *)&opval,sizeof(opval));
    if (retval!=0)
      fprintf(stderr,"Could not set socket to SO_REUSEADDR\n");
    #endif    
  }

  retval=bind(fd,(struct sockaddr *)&addr,sizeof(addr));
  #ifdef _WINDOWS
    if (retval==SOCKET_ERROR)
      retval=-1;
  #endif

  if (retval==-1)
  {
    status=GetStatus();
    DBGMSG("Bind failure (" << status << ") IP "<< IP <<" PORT "<< ntohs(Port));
    return(FALSE);
  }
  myIP=IP;
  myPort=Port;
  maxFD=fd;

  if (mode==SERVER)
    listen(fd,64);  //Solaris needs lots of listen slots for some reason

  return(TRUE);
}


// This is only for clients

bit8 TCP::Connect(char *Host,uint16 port)
{
  char hostName[100];
  struct hostent *hostStruct;
  struct in_addr *hostNode;

  if (isdigit(Host[0]))
    return ( Connect( ntohl(inet_addr(Host)), port) );

  strcpy(hostName, Host);

  hostStruct = gethostbyname(Host);
  if (hostStruct == NULL)
  {ERRMSG("Can't resolve host");return (0);}
  hostNode = (struct in_addr *) hostStruct->h_addr;
  return ( Connect(ntohl(hostNode->s_addr),port) );
}

bit8 TCP::Connect(uint32 IP,uint16 Port)
{
  int tries,result;
  struct timeval sleep_time;
  struct sockaddr_in serverAddr;
  int status;

  IP=htonl(IP);
  Port=htons(Port);

  serverAddr.sin_family=AF_INET;
  serverAddr.sin_port=Port;
  serverAddr.sin_addr.s_addr=IP;

  if (mode!=CLIENT)
  {ERRMSG("Can't connect in server mode");return(FALSE);}

  tries=0;
  result=-1;


  // try 10 connects with a greater and greater sleep time after each one
  // this can go on for upto 5.4 seconds
  while ((tries < 10) && (result == -1))
  {
    ClearStatus();
    result = connect(fd,(struct sockaddr *)&serverAddr, sizeof(serverAddr));
    status=GetStatus();

    #ifdef _WINDOWS
      if (result==SOCKET_ERROR)
        result=-1;
    #endif

    if ((status == ISCONN) && (result == -1))
    {
      result = 0;
    }
    if (result == -1)
    {
      if ((status!=INPROGRESS)&&(status!=ALREADY)&&(status!=AGAIN)&&
        (status!=WOULDBLOCK))
      {
        Close();
        Bind(myIP,myPort);
      }
      tries++;
      sleep_time.tv_sec = 0;
      sleep_time.tv_usec = (100000*(tries+1));
      #ifdef WIN32
        Sleep((sleep_time.tv_usec)/1000);
      #else
        select(0, 0, 0, 0, &sleep_time);
      #endif
    }
  }

  if (result == -1)
  {
    return(FALSE);
  }
  connectionState=CONNECTED;
  return (TRUE);
}



// Asynchronous Connection
bit8 TCP::ConnectAsync(char *Host,uint16 port)
{
  char hostName[100];
  struct hostent *hostStruct;
  struct in_addr *hostNode;

  if (isdigit(Host[0]))
    return ( ConnectAsync( ntohl(inet_addr(Host)), port) );

  strcpy(hostName, Host);

  hostStruct = gethostbyname(Host);
  if (hostStruct == NULL)
    return (0);
  hostNode = (struct in_addr *) hostStruct->h_addr;
  return ( ConnectAsync(ntohl(hostNode->s_addr),port) );
}

// Asynchronous Connection
bit8 TCP::ConnectAsync(uint32 IP,uint16 Port)
{
  int result;
  struct sockaddr_in serverAddr;
  int status,connectErrno;
  int retval;

  IP=htonl(IP);
  Port=htons(Port);

  serverAddr.sin_family=AF_INET;
  serverAddr.sin_port=Port;
  serverAddr.sin_addr.s_addr=IP;

  if (mode!=CLIENT)
    return(FALSE);

  result=-1;

  if (connectionState==CONNECTING)
  { 
    if (IsConnected(fd))
    {
      DBGMSG("CONNECTION COMPLETE at point 1");
      connectionState=CONNECTED;
      return(TRUE);
    }
    else
      return(TRUE);  // Still trying
  }

  ClearStatus();
  result = connect(fd,(struct sockaddr *)&serverAddr, sizeof(serverAddr));
  connectErrno=errno;
  status=GetStatus();

  #ifdef _WINDOWS
    if (result==SOCKET_ERROR)
    {
      DBGMSG("Socket error 1  " << status);
      result=-1;
    }
  #endif

  // If we have a bogus FD, try again after closing and re-binding
  if ((result==-1)&&((status==BADF)||(status==NOTSOCK)||(status==INVAL)))
  {
    Close();
    retval=Bind(myIP,myPort);
    DBGMSG("BIND = "<<retval);
    ClearStatus();
    result = connect(fd,(struct sockaddr *)&serverAddr, sizeof(serverAddr));
    status=GetStatus();
    #ifdef _WINDOWS
      if (result==SOCKET_ERROR)
      {
        DBGMSG("Socket error 2  " << status);
        result=-1;
      }
    #endif
  }

  if (result==-1)
  {
    if ((status==ISCONN)||(status==INPROGRESS)||(status==ALREADY)||
      (status==WOULDBLOCK))
    {
      connectionState=CONNECTING;
      return(TRUE);   // The socket's trying to connect
    }
    else  // Must be a "real" problem
    {
      Close();
      DBGMSG("Fail " << connectErrno << " " << status);
      connectionState=CLOSED;
      return(FALSE);
    }
  }
  //printf("Connected for real\n");
  connectionState=CONNECTED;
  return(TRUE);
}




void TCP::ClearStatus(void)
{
  #ifndef _WINDOWS
  errno=0;
  #endif
}

int TCP::GetStatus(void)
{
  #ifdef _WINDOWS
  int status=WSAGetLastError();
  if (status==0) return(OK);
  else if (status==WSAEINTR) return(INTR);
  else if (status==WSAEINPROGRESS) return(INPROGRESS);
  else if (status==WSAECONNREFUSED) return(CONNREFUSED);
  else if (status==WSAEINVAL) return(INVAL);
  else if (status==WSAEISCONN) return(ISCONN);
  else if (status==WSAENOTSOCK) return(NOTSOCK);
  else if (status==WSAETIMEDOUT) return(TIMEDOUT);
  else if (status==WSAEALREADY) return(ALREADY);
  else if (status==WSAEWOULDBLOCK) return(WOULDBLOCK);
  else if (status==WSAEBADF) return(BADF);
  else     return(UNKNOWN);
  #else
  int status=errno;
  if (status==0) return(OK);
  else if (status==EINTR) return(INTR);
  else if (status==EINPROGRESS) return(INPROGRESS);
  else if (status==ECONNREFUSED) return(CONNREFUSED);
  else if (status==EINVAL) return(INVAL);
  else if (status==EISCONN) return(ISCONN);
  else if (status==ENOTSOCK) return(NOTSOCK);
  else if (status==ETIMEDOUT) return(TIMEDOUT);
  else if (status==EALREADY) return(ALREADY);
  else if (status==EAGAIN) return(AGAIN);
  else if (status==EWOULDBLOCK) return(WOULDBLOCK);
  else if (status==EBADF) return(BADF);
  else     return(UNKNOWN);
  #endif
}


// this is only for servers

sint32 TCP::GetConnection(void)
{
  if (mode!=SERVER)
    return(-1);

  sint32 clientFD;
  struct sockaddr_in clientAddr;
  int addrlen=sizeof(clientAddr);

  clientFD=accept(fd,(struct sockaddr *)&clientAddr,&addrlen);
  if (clientFD!=-1)
  {
    if (clientFD>maxFD)
      maxFD=clientFD;
    FD_SET(clientFD,&clientList);
    clientCount++;
  }
  return(clientFD);
}

sint32 TCP::GetConnection(struct sockaddr *clientAddr)
{
  if (mode!=SERVER)
    return(-1);

  sint32 clientFD;
  int addrlen=sizeof(struct sockaddr);

  clientFD=accept(fd,(struct sockaddr *)clientAddr,&addrlen);
  if (clientFD!=-1)
  {
    if (clientFD>maxFD)
      maxFD=clientFD;
    FD_SET(clientFD,&clientList);
    clientCount++;
  }
  return(clientFD);
}

