// 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: 
// 
// 
// </file> 
#include "inetsocket.h"

#include <hyperg/utils/hgunistd.h>
#include <hyperg/hyperg/assert.h>

// both for perror() on the various platforms
#include <stdio.h>
#include <errno.h>

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

#include <fcntl.h>

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

#ifdef LINUX
#  define SOMAXCONN 8
#endif

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

#ifdef AIX
#  include <sys/select.h>  /* fd_set */
#endif

#define VERBOSE
#include <hyperg/hyperg/verbose.h>



// --------------------------------------------------------------------
INETAddress :: INETAddress() {
   ::memset (this, '\0', sizeof (INETAddress)) ;
   sin_family = AF_INET ;
}

INETAddress :: INETAddress (const char* hostaddr, int port) {
   ::memset (this, '\0', sizeof (INETAddress)) ;
   sin_family = AF_INET ;
   unsigned long addr = inet_addr (hostaddr) ;
   if (addr == INADDR_NONE) {
      DEBUGNL ("INETAddress::INETAddress(const char*,int): ::inet_addr() failed") ;
      return ;
   }
   sin_addr.s_addr = addr ;
   sin_port = htons ((u_short)port) ;
}

INETAddress :: INETAddress (const sockaddr_in& a) {
   operator = (a) ;
}

INETAddress& INETAddress :: operator = (const sockaddr_in& a) {
   hgassert (a.sin_family==AF_INET, "INETAddress::operator=(const sockaddr_in&): not AF_INET") ;
   ::memcpy (this, &a, sizeof (INETAddress)) ;
   return *this ;
}

const char* INETAddress :: host() const {
   return ::inet_ntoa (sin_addr) ;
}

int INETAddress :: port() const {
   return ntohs (sin_port) ;
}

boolean INETAddress :: operator == (const INETAddress& a) const {
   return sin_addr.s_addr == a.sin_addr.s_addr  &&  sin_port == a.sin_port ;
}

boolean INETAddress :: operator < (const INETAddress& a) const {
   return sin_addr.s_addr < a.sin_addr.s_addr ||
      (sin_addr.s_addr == a.sin_addr.s_addr && sin_port < a.sin_port) ;
}

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

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

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) ;
}

void INETSocket :: attach (int fd, boolean close) {
   Socket::attach (fd, close) ;
   // 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") ;

   set_close_(false) ; // ++++ is that necessary ?

   INETAddress addr (host, port) ;

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

      // gethostbyname() will hang 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 ;
      }

#ifdef VERBOSE_SOCKET
      ::write (STDERR_FILENO, "g", 1) ;
#endif
      
      struct hostent* hp = ::gethostbyname ((char*)host) ; /* some systems want it non-const */
      if (hp == nil) {
         DEBUGNL ("INETSocket::connect(): no such host") ;
#ifdef VERBOSE_SOCKET
         ::write (STDERR_FILENO, "!", 1) ;
#endif
         return false ;
      }
      if (hp->h_addrtype != AF_INET) {
         DEBUGNL ("INETSocket::connect(): not an internet host") ;
#ifdef VERBOSE_SOCKET
         ::write (STDERR_FILENO, "!", 1) ;
#endif
         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) ;

#ifdef VERBOSE_SOCKET
      ::write (STDERR_FILENO, ".", 1) ;
#endif

   }
   
   return connect (addr, timeout) ;
}

boolean INETSocket :: connect (const INETAddress& addr, unsigned long timeout) {
   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") ;

   set_close_(false) ;
   
   // 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) ;
#ifdef VERBOSE_SOCKET
      ::perror ("INETSocket::connect(): ::socket()") ;
#endif
      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) {
#ifdef VERBOSE_SOCKET
         ::perror ("INETSocket::connect(): ::fcntl() set nonblocking") ;
#endif
         ::close (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.
            // Solaris does so.
            set_errno_(::errno) ;
#ifdef VERBOSE_SOCKET            
            ::perror ("INETSocket::connect(): ::connect()") ;
#endif
            ::close (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") ;
#ifdef VERBOSE_SOCKET
         ::perror ("INETSocket::connect(): ::select()") ;
#endif
         ::close (sock) ;
         return false ;
      }
      else if (nfound == 0) {
         // timed out
         set_errno_(ETIMEDOUT) ; // ++++ i.e., my timeout (cheated)
         DEBUGNL ("INETSocket::connect(): timed out") ;
         ::close (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)) {
#ifdef VERBOSE_SOCKET
            ::perror ("INETSocket::connect(): ::getpeername()") ;
#endif
         }
         set_errno_(::errno) ;
         DEBUGNL ("INETSocket::connect(): not connected") ;
         ::close (sock) ;
         return false ;
      }
         
      // reset the socket to blocking
      if (::set_blocking (sock, true) < 0) {
#ifdef VERBOSE_SOCKET
         ::perror ("INETSocket::connect(): ::fcntl() set blocking") ;
#endif
         ::close (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") ;
#ifdef VERBOSE_SOCKET
         ::perror ("INETSocket::connect(): ::connect()") ;
#endif
         ::close (sock) ;
         return false ;
      }
   }

   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) ;
      if (::getsockname (fd(), (sockaddr*)&sain, &len) < 0) {
         ((INETSocket*)this)->set_errno_(::errno) ;
#ifdef VERBOSE_SOCKET      
         ::perror ("INETSocket::thisAddress(): ::getsockname()") ;
#endif
         return nil ;
      }
      hgassert (len==sizeof(sockaddr_in), "INETSocket::thisAddress(): len not ok") ;
      return ((INETSocket*)this)->this_addr_ = new INETAddress (sain) ;
   }
}

const INETAddress* INETSocket :: thatAddress() const {
   if (that_addr_)
      return that_addr_ ;
   else {
      sockaddr_in sain ;
      int len = sizeof (sockaddr_in) ;
      if (::getpeername (fd(), (sockaddr*)&sain, &len) < 0) {
         ((INETSocket*)this)->set_errno_(::errno) ;
#ifdef VERBOSE_SOCKET      
         ::perror ("INETSocket::thatAddress(): ::getpeername()") ;
#endif
         return nil ;
      }
      hgassert (len==sizeof(sockaddr_in), "INETSocket::thatAddress(): len not ok") ;
      return ((INETSocket*)this)->that_addr_ = new 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 ;
   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) {
      DEBUGNL ("INETSocket::listen(): could not create socket") ;
      set_errno_(::errno) ;
#ifdef VERBOSE_SOCKET      
      ::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) {
      set_errno_(::errno) ;
#ifdef VERBOSE_SOCKET      
      ::perror ("INETSocket::listen(): ::setsockopt()") ;
#endif
      ::close (sock) ;
      return false ;
   }
   
   if (::bind (sock, (struct sockaddr*)&name, sizeof(name)) < 0) {
      set_errno_(::errno) ;
#ifdef VERBOSE_SOCKET      
      ::perror ("INETSocket::listen(): ::bind()") ;
#endif
      ::close (sock) ;
      return false ;
   }
   
   if (::listen (sock, SOMAXCONN) < 0) {
      set_errno_(::errno) ;
#ifdef VERBOSE_SOCKET      
      ::perror ("INETSocket::listen(): ::listen()") ;
#endif
      ::close (sock);
      return false ;
   }
   
   set_fd_(sock) ;
   // set flags
   set_close_(true) ;
   set_listening_(true) ;

   return true ;
}

boolean INETSocket :: accept (int& fd) {
   sockaddr_in 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 ;

   int newfd = accept_(&addr) ;
   if (newfd < 0)
      return false ;
   sp = INETSocketPtr (new 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() ;
}

