// This may look like C code, but it is really -*- C++ -*-
// 
// <copyright> 
//  
//  Copyright (c) 1994
//  Institute for Information Processing and Computer Supported New Media (IICM), 
//  Graz University of Technology, Austria. 
//  
// </copyright> 
// 
// 
// <file> 
// 
// Name:        inetsocket.h
// 
// Purpose:     
// 
// Created:     4 May 95   Joerg Faschingbauer
// 
// Modified:    
// 
// Description: 
// 
// $Id: inetsocket.C,v 1.21 1997/02/13 13:37:43 gorasche Exp $
// 
// $Log: inetsocket.C,v $
// Revision 1.21  1997/02/13 13:37:43  gorasche
// error states for Win32
//
// Revision 1.20  1997/01/29 15:27:07  gorasche
// connect thread for timed connect in Win32
//
// Revision 1.19  1996/10/29 14:58:25  jfasch
// verbose.h things
//
// Revision 1.18  1996/10/23 12:12:03  jfasch
// take correcting action if accept returns a socket whose address family
// is not AF_INET. happened under Linux 2.???
//
// Revision 1.17  1996/10/15 15:59:37  jfasch
// have to close on that LINUX bug in accept()
//
// Revision 1.16  1996/10/03 15:59:07  jfasch
// adaptions due to moving it from the previous location in DcCommon to HgUtilities
//
// Revision 1.15  1996/09/26 15:05:43  jfasch
// nearly nothing
//
// Revision 1.14  1996/08/29 12:00:06  jfasch
// fixed memory leak
//
// Revision 1.13  1996/08/28 09:00:18  jfasch
// as last revision, with getsockname()
//
// Revision 1.12  1996/08/28 08:59:37  jfasch
// go into getpeername() with zeroed sockaddr_in
//
// Revision 1.11  1996/08/20 13:26:58  jfasch
// memset
//
// Revision 1.10  1996/08/20 13:17:40  jfasch
// memset
//
// Revision 1.9  1996/07/31 14:33:39  jfasch
// removed some compiler warnings
//
// Revision 1.8  1996/07/22 08:18:19  jfasch
// *** empty log message ***
//
// Revision 1.7  1996/07/08 13:16:36  jfasch
// *** empty log message ***
//
// Revision 1.6  1996/03/22 16:23:11  jfasch
// *** empty log message ***
//
// Revision 1.5  1996/02/21 14:28:36  jfasch
// due to changes to class File had to remove set_close_() and get_close_().
// call close() from dtor only if opened().
//
// Revision 1.4  1996/02/12 16:27:54  jfasch
// *** empty log message ***
//
// Revision 1.3  1996/01/23 16:31:14  jfasch
// - minor changes
//
// 
// </file> 
#include "inetsocket.h"

#include "assert.h"
#include "hgunistd.h"
#include "inetaddr.h"
#include "new.h"
#include "verbose.h"



// for perror (differs between systems)
#include <errno.h>
#include <stdio.h>

#include <ctype.h>
#include <string.h>

#include <fcntl.h>

#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

#ifndef SOMAXCONN
#  define SOMAXCONN 8
#endif

#ifndef INADDR_NONE
#  define INADDR_NONE (u_long)0xffffffff
#endif


// --------------------------------------------------------------------
const char* INETSocket :: version1 = "$Id: inetsocket.C,v 1.21 1997/02/13 13:37:43 gorasche Exp $" ;

INETSocket :: INETSocket()
: this_addr_(nil),
  that_addr_(nil) {}

INETSocket :: INETSocket (int fd, boolean)
: this_addr_(nil),
  that_addr_(nil) {
   attach (fd) ;
}

INETSocket :: INETSocket (const char* host, int port, unsigned long timeout)
: this_addr_(nil),
  that_addr_(nil) {
   connect (host, port, timeout) ;
}

INETSocket :: INETSocket (const INETAddress& addr, unsigned long timeout) 
: this_addr_(nil),
  that_addr_(nil) {
   connect (addr, timeout) ;
}

INETSocket :: INETSocket (int port) 
: this_addr_(nil),
  that_addr_(nil) {
   listen (port) ;
}

INETSocket :: ~INETSocket() {
   if (! closed())
      close() ;
   HGDELETE (this_addr_) ;
   HGDELETE (that_addr_) ;
}

boolean INETSocket :: close() {
   hgassert (!closed(), "INETSocket::close(): not using a file number") ;
   return Socket::close() ;
}

void INETSocket :: attach (int fd) {
   Socket::attach (fd) ;
   // assert blocking vs listening with finer granularity than Socket ++++
}


boolean INETSocket :: connect (const char* host, int port, unsigned long timeout) {
   hgassert (fd()<0, "INETSocket::connect(): this already in use") ;
   hgassert (!get_listening_(), "INETSocket::connect(): called though listening") ;

   INETAddress addr (host, port) ;

   if (! addr) {
      // host was not in numbers-and-dots. have to do a name server lookup.

      // gethostbyname() will hang (at least on some systems, dont
      // know anymore exactly, but I think Solaris (2.3?) was one of
      // them) if the string contains a whitespace catch that:
      if (::strchr (host, ' ') || ::strchr (host, '\t') ||
          ::strchr (host, '\n')) {
         set_errno_(EFAULT) ; // is there a better error code ? ++++
         return false ;
      }

      struct hostent* hp = ::gethostbyname ((char*)host) ; /* some systems want it non-const */
      if (hp == nil) {
         DEBUGNL ("INETSocket::connect(): no such host") ;
         return false ;
      }
      if (hp->h_addrtype != AF_INET) {
         DEBUGNL ("INETSocket::connect(): not an internet host") ;
         return false ;
      }

      // set up the addr where the connect should go to. (a bit un-pretty ++++)
      addr.sin_family = AF_INET ; // already set above, but ...
      ::memcpy (&addr.sin_addr, hp->h_addr, sizeof(addr.sin_addr)) ; 
      addr.sin_port = htons (port) ;
   }
   
   return connect (addr, timeout) ;
}

#ifdef WIN32
typedef struct threadparam
{
  int socket;
  struct sockaddr* address;
  int size;
} TPARAM;

// this thread makes a blocking connect and
// is killed after a timeout period (when not connected)
unsigned int __stdcall ConnectThread(void* myData)
{
  TPARAM* myParam=(TPARAM*)myData;
  int err=0;
  if(::connect(myParam->socket,myParam->address,myParam->size)<0)
    err=WSAGetLastError();
  return err;
}
#endif

boolean INETSocket :: connect (const INETAddress& addr, unsigned long timeout) {
   DEBUGNL ("INETSocket::connect(const INETAddress&, unsigned long)") ;
   hgassert (fd()<0, "INETSocket::connect(): this already in use") ;
   hgassert (!get_listening_(), "INETSocket::connect(): called though listening") ;
   hgassert (addr.ok(), "INETSocket::connect(): addr not ok") ;

#ifdef WIN32   
  int wsaerr;
  // get me a tcp socket
  int sock=::socket(AF_INET,SOCK_STREAM,0);
  if (sock<0)
  {
    DEBUGNL ("INETSocket::connect(): could not create socket");
    wsaerr=WSAGetLastError();
    set_errno_(wsaerr) ;
    return false ;
  }

  if (timeout > 0)
  {
    HANDLE hArr;
    unsigned int threadid;
    TPARAM ActParam;
    DEBUGNL ("INETSocket::connect(): applying timeout of "<<timeout<<" secs") ;

    // set up data structure for the thread
    ActParam.socket=sock;
    ActParam.address=(struct sockaddr*)&addr;
    ActParam.size=sizeof(addr);

    // Create a connect thread and wait for the connect
    hArr=(HANDLE)_beginthreadex(nil,2048,ConnectThread,&ActParam,0,&threadid);

    int res=WaitForSingleObject(hArr,timeout*1000);
    if(res==WAIT_FAILED)
    {
      res=GetLastError();
      DEBUGNL("INETSocket::connect(): waitobject failed:" << res);
      HGSOCKCLOSE(sock) ;
      return false;
    }
    else if(res==WAIT_TIMEOUT)
    {
      set_errno_(ETIMEDOUT) ; // ++++ i.e., my timeout (cheated)
      DEBUGNL ("INETSocket::connect(): timed out; terminating t:"<<threadid);
      // let us assume this thread does not own critical sections etc.
      TerminateThread(hArr,-1);
      HGSOCKCLOSE(sock) ;
      return false ;
    }

// this is code for Winsock 2, don't have it under 3.51!!!
#ifdef GERBERTS_FUTURE_PLANS      
    int syserr;
    HANDLE hTemp=WSACreateEvent(nil,false,false,nil);
    WSAEventSelect(sock,hTemp,FD_ACCEPT);
    syserr=WaitForSingleObject(hTemp,timeout*1000);
    WSACloseEvent(hTemp);
    if(syserr==WAIT_FAILED)
    {
      syserr=GetLastError();
      set_errno_(syserr) ;
      DEBUGNL ("INETSocket::connect(): ::select() error") ;
      HGSOCKCLOSE(sock) ;
      return false ;
    }

    if(syserr==WAITTIMEOUT)
    {
      set_errno_(ETIMEDOUT) ; // ++++ i.e., my timeout (cheated)
      DEBUGNL ("INETSocket::connect(): timed out") ;
      HGSOCKCLOSE(sock) ;
      return false ;
    }
#endif 

    // my socket is ready, connected or not. have to look if it is.
    // (++++ is there a better way??)
    sockaddr_in that ;
    int thatlen = sizeof (that) ;
    if (::getpeername(sock,(struct sockaddr*)&that,&thatlen)<0)
    {
      // not connected?
      wsaerr=WSAGetLastError();
      set_errno_(wsaerr) ;
      DEBUGNL ("INETSocket::connect(): not connected"<<wsaerr) ;
      HGSOCKCLOSE(sock) ;
      return false ;
    }
  }
  // no timeout wanted (take the system tcp timeout)
  else
  { 
    DEBUGNL ("INETSocket::connect(): connecting with system tcp timeout") ;
    if (::connect (sock, (struct sockaddr*)&addr, sizeof(addr)) < 0)
    {
      wsaerr=WSAGetLastError();
      set_errno_(wsaerr) ;
      DEBUGNL ("INETSocket::connect(): cannot connect:"<<wsaerr) ;
      HGSOCKCLOSE(sock) ;
      return false ;
    }
  }
#else

   // get me a tcp socket
   int sock = ::socket (AF_INET, SOCK_STREAM, 0) ;

   if (sock < 0) {
      DEBUGNL ("INETSocket::connect(): could not create socket") ;
      set_errno_(errno) ;
      perror_("INETSocket::connect(): ::socket()") ;
      return false ;
   }


   if (timeout > 0) {
      DEBUGNL ("INETSocket::connect(): applying timeout of "<<timeout<<" secs") ;
      
      // temporarily set the socket non-blocking
      if (::set_blocking (sock, false) < 0) {
         perror_("INETSocket::connect(): ::fcntl() set nonblocking") ;
         HGSOCKCLOSE(sock) ;
         return false ;
      }

      // request a connect
      if (::connect (sock, (struct sockaddr*)&addr, sizeof (addr)) < 0) {
         if (errno != EINPROGRESS) {
            // for example, when connecting to the local systems TCP
            // (i.e., inside local host), a TCP can decide
            // *immediately* if a connection can be established.
            // at least Solaris does so.
            set_errno_(errno) ;
            perror_("INETSocket::connect(): ::connect()") ;
            HGSOCKCLOSE(sock) ;
            return false ;
         }
      }
         
      // wait for it to complete or time out (with *my own* timeout)
      fd_set fds ;
      ::memset (&fds, 0, sizeof(fd_set)) ;
      FD_SET (sock, &fds) ;
      timeval tv ;
      tv.tv_sec = timeout ;
      tv.tv_usec = 0 ;

      int nfound ;
#ifdef HPUX
      while ((nfound = ::select (sock+1, 0, (int*)&fds, 0, &tv)) < 0  &&  errno == EINTR) ;
#else
      while ((nfound = ::select (sock+1, 0, &fds, 0, &tv)) < 0  &&  errno == EINTR) ;
#endif
         
      if (nfound < 0) {
         set_errno_(errno) ;
         DEBUGNL ("INETSocket::connect(): ::select() error") ;
         perror_("INETSocket::connect(): ::select()") ;
         HGSOCKCLOSE(sock) ;
         return false ;
      }
      else if (nfound == 0) {
         // timed out
         set_errno_(ETIMEDOUT) ; // ++++ i.e., my timeout (cheated)
         DEBUGNL ("INETSocket::connect(): timed out") ;
         HGSOCKCLOSE(sock) ;
         return false ;
      }

      // my socket is ready, connected or not. have to look if it is.
      // (++++ is there a better way??)
      sockaddr_in that ;
      int thatlen = sizeof (that) ;
      if (::getpeername (sock, (struct sockaddr*)&that, &thatlen) < 0) {
         // not connected
         if (! (errno==ENOTCONN || errno==ECONNREFUSED))
            perror_("INETSocket::connect(): ::getpeername()") ;
         set_errno_(errno) ;
         DEBUGNL ("INETSocket::connect(): not connected") ;
         HGSOCKCLOSE(sock) ;
         return false ;
      }
         
      // reset the socket to blocking
      if (::set_blocking (sock, true) < 0) {
         perror_("INETSocket::connect(): ::fcntl() set blocking") ;
         HGSOCKCLOSE(sock) ;
         return false ;
      }
   }



   else { // no timeout wanted (take the system tcp timeout)
      DEBUGNL ("INETSocket::connect(): connecting with system tcp timeout") ;
      if (::connect (sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
         set_errno_(errno) ;
         DEBUGNL ("INETSocket::connect(): cannot connect") ;
         perror_("INETSocket::connect(): ::connect()") ;
         HGSOCKCLOSE(sock) ;
         return false ;
      }
   }

#endif /* WIN32 */
   DEBUGNL ("INETSocket::connect(): connected") ;
   
   set_fd_(sock) ;
//    set_close_(true) ;
   return true ;
}

const INETAddress* INETSocket :: thisAddress() const {
   if (this_addr_)
      return this_addr_ ;
   else {
      sockaddr_in sain ;
      int len = sizeof (sockaddr_in) ;
      ::memset (&sain, 0, len) ;
      if (::getsockname (fd(), (sockaddr*)&sain, &len) < 0) {
         ((INETSocket*)this)->set_errno_(errno) ;
         perror_("INETSocket::thisAddress(): ::getsockname()") ;
         return nil ;
      }
      hgassert (len==sizeof(sockaddr_in), "INETSocket::thisAddress(): len not ok") ;
      return ((INETSocket*)this)->this_addr_ = HGNEW (INETAddress (sain)) ;
   }
}

const INETAddress* INETSocket :: thatAddress() const {
   if (that_addr_)
      return that_addr_ ;
   else {
      sockaddr_in sain ;
      int len = sizeof (sockaddr_in) ;
      ::memset (&sain, 0, len) ;
      if (::getpeername (fd(), (sockaddr*)&sain, &len) < 0) {
#ifdef WIN32
        int iError=WSAGetLastError();
        ((INETSocket*)this)->set_errno_(iError) ;
        cerr << "INETSocket::thatAddress(): ::getpeername() code:" << iError << endl;
#else
         ((INETSocket*)this)->set_errno_(errno) ;
         perror_("INETSocket::thatAddress(): ::getpeername()") ;
#endif
         return nil ;
      }
      hgassert (len==sizeof(sockaddr_in), "INETSocket::thatAddress(): len not ok") ;
      return ((INETSocket*)this)->that_addr_ = HGNEW (INETAddress (sain)) ;
   }
}

boolean INETSocket :: thisAddress (INETAddress& addr) const {
   hgassert (fd()>=0, "INETSocket::thisAddress(): not yet using a file number") ;
   const INETAddress* a = thisAddress() ;
   if (! a)
      return false ;
   addr = *a ;
   return true ;
}

boolean INETSocket :: thatAddress (INETAddress& addr) const {
   hgassert (fd()>=0, "INETSocket::thatAddress(): not yet using a file number") ;
   const INETAddress* a = thatAddress() ;
   if (! a)
      return false ;
   addr = *a ;
   return true ;
}

boolean INETSocket :: listen (int port) {
   hgassert (fd()<0, "INETSocket::listen(): this already in use") ;
   hgassert (port>=0, "INETSocket::listen(): invalid port number") ;

//    set_close_(false) ;
   
   struct sockaddr_in name ;
   ::memset (&name, 0, sizeof (name)) ;
   name.sin_family = AF_INET ;
   name.sin_port = htons (port) ;
   name.sin_addr.s_addr = htonl (INADDR_ANY) ;
   // errno = 0 ;   
   int sock = ::socket (AF_INET, SOCK_STREAM, 0) ;
   if (sock < 0) {
#ifdef WIN32
      DEBUGNL ("INETSocket::listen(): could not create socket") ;
      int iError=WSAGetLastError();
      set_errno_(iError) ;
      cerr << "INETSocket::listen(): ::socket()code:" << iError << endl;
#else
      set_errno_(errno) ;
      perror_("INETSocket::listen(): ::socket()") ;
#endif

      return false ;
   }
   
   // reuse address of socket
   int optval = 1 ;
   int optlen = sizeof (int) ;
   
   if (::setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, (char*)&optval, optlen) < 0) {
#ifdef WIN32
     int iError=WSAGetLastError();
     set_errno_(iError) ;
     cerr << "INETSocket::listen(): ::setsockopt() code:" << iError;
#else
      set_errno_(errno) ;
      perror_("INETSocket::listen(): ::setsockopt()") ;
#endif
      HGSOCKCLOSE(sock) ;
      return false ;
   }
   
   if (::bind (sock, (struct sockaddr*)&name, sizeof(name)) < 0) {
#ifdef WIN32
     int iError=WSAGetLastError();
     set_errno_(iError) ;
     cerr << "INETSocket::listen(): ::bind() code:" << iError;
#else
      set_errno_(errno) ;
      perror_("INETSocket::listen(): ::bind()") ;
#endif
      HGSOCKCLOSE(sock) ;
      return false ;
   }
   
   if (::listen (sock, SOMAXCONN) < 0) {
#ifdef WIN32
     int iError=WSAGetLastError();
     set_errno_(iError) ;
     cerr << "INETSocket::listen(): ::listen() code:" << iError;
#else
      set_errno_(errno) ;
      perror_("INETSocket::listen(): ::listen()") ;
#endif
      HGSOCKCLOSE(sock);
      return false ;
   }
   
   set_fd_(sock) ;
   set_listening_(true) ;

   return true ;
}

boolean INETSocket :: accept (int& fd) {
   sockaddr_in addr ;
   ::memset (&addr, 0, sizeof(addr)) ;
   fd = accept_(&addr) ;
   return fd >= 0 ;
}

boolean INETSocket :: accept (INETSocketPtr& sp, INETAddress& peer) {
   sp = INETSocketPtr() ; // be sure to nil it out (++++)
   sockaddr_in addr ;
   ::memset (&addr, 0, sizeof(addr)) ;

   int newfd = accept_(&addr) ;
   
   if (newfd >= 0 && addr.sin_family != AF_INET) {
      addr.sin_family = AF_INET ;
//       errno = EAFNOSUPPORT ;
//       ::close (newfd) ;
//       set_errno_(errno) ;
//       perror ("INETSocket::accept(): bad hack: bad address family") ;
//       return false ;
   }
   
   if (newfd < 0)
      return false ;

   sp = INETSocketPtr (HGNEW (INETSocket (newfd, true))) ;
   peer = addr ;
   return true ;
}

boolean INETSocket :: accept (INETSocketPtr& sp) {
   INETAddress addr ;
   return accept (sp, addr) ;
}

boolean INETSocket :: accept (SocketPtr& sp) {
   sp = SocketPtr() ; // be sure to nil it out (++++)
   INETSocketPtr isp ;
   if (! accept (isp))
      return false ;
   sp = SocketPtr (isp.ptr()) ;
   return true ;
}

int INETSocket :: port() const {
   hgassert (fd()>=0, "INETSocket::port(): not using a file number yet") ;
   hgassert (get_listening_(), "INETSocket::port(): not listening") ;

   const INETAddress* a = thisAddress() ;
   if (! a)
      return 0 ;
   else
      return a->port() ;
}

int INETSocket :: accept_(struct sockaddr_in* addr) {
   int len = sizeof (sockaddr_in) ;
   return Socket::accept_((struct sockaddr*)addr, &len) ;
}
