/* $Id: SocketBase.h,v 1.14 1999/10/28 17:10:14 v2kea318 Exp $

$Log: SocketBase.h,v $
 * Revision 1.14  1999/10/28  17:10:14  v2kea318
 * Added the port to the ConnectFailure exception.
 *
 * Revision 1.13  1999/10/22  20:09:08  v2kea318
 * Got rid of the #include <sys/socketvar.h> line.
 *
 * Revision 1.12  1999/08/13  18:29:28  v2kea318
 * Simplified the check of the signed bit in the SocketIOBadNBytesException
 * constructor by using the LONG_MAX constant from the standard limits.h file.
 *
 * Revision 1.11  1999/08/13  18:10:53  v2kea318
 * Use virtual inheritance to derive all exceptions from SocketIOException.
 * This makes the diamond inheritance in the SocketReadErrorBadNBytesException
 * and SocketWriteErrorBadNBytesException simpler to handle as there is only
 * one SocketIOException object inside them.
 *
 * Revision 1.10  1999/07/30  20:44:12  v2kea318
 * Converted the code to G++ compliance.
 *
 * Revision 1.9  1999/07/13  21:19:53  v2kea318
 * Add the ClosedSocketPipe exception.
 *
 * Revision 1.8  1999/05/10  15:28:58  v2kea318
 * For the bad nbytes exceptions, add the capability to return the data
 * that was actually read.
 *
 * Revision 1.7  1999/05/06  19:53:47  v2kea318
 * *** empty log message ***
 *
 * Revision 1.6  1999/04/30  21:04:29  v2kea318
 * Added the ClosedSocket exception.
 *
 * Revision 1.5  1999/04/27  15:46:29  v2kea318
 * Add a set of IO exceptions to indicate that the number of bytes written
 * or read from a socket is not that expected.
 *
 * Revision 1.4  1999/04/27  14:57:36  v2kea318
 * Store the local copy of the hostent data in an internally defined
 * class, which will fully copy the data from the original.  The hostent
 * structure contains arrays and strings, so doing a simple byte copy
 * of the structure is insufficient.  The HostEnt class will take care
 * of memory cleanup.
 *
 * Revision 1.3  1999/04/26  14:21:43  v2kea318
 * Store a smart pointer to a struct hostent, which will store a copy of
 * hostent data internally.  This is constructed by a gethostby... function,
 * which returns a pointer to static storage.  We want to keep whatever is
 * there on the call made by this class, so we need to store a copy of the
 * contents.
 *
 * Revision 1.2  1999/04/23  19:52:37  v2kea318
 * *** empty log message ***
 *
 * Revision 1.1  1999/04/23  18:55:31  v2kea318
 * Initial revision
 *
***************************************************************************

Define a base class for Internet sockets.  These sockets will use the
TCP proctocol.  This class will only provide basic functionality
common to all sockets; derived classes will provide the rest.

***************************************************************************/

#ifndef SOCKET_BASE_MYLIBS_H
#define SOCKET_BASE_MYLIBS_H

#include <netdb.h> 
#include <string>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "pointer.h"
#include <cstring>
#include <stdexcept>
#include <climits>

class SocketBase {
protected:
  // define a container for a struct hostent object.
  class HostEnt {
  private:
    Pointer<struct hostent> ptr;
  public:
    HostEnt() : ptr(NULL) {}
    HostEnt(const struct hostent* hp) 
      {
	// just call the copy method
	copy(hp);
      }
    HostEnt(const HostEnt& h) : ptr(h.ptr) {}

    // assignment operators.
    HostEnt& operator = (const HostEnt& h) 
      {
	if (this != &h) ptr = h.ptr;
	return *this;
      }
    
    // copy the contents of one hostent to another.
    void copy(const struct hostent* hp);

    // destructor.
    ~HostEnt() { cleanup(); }
    void cleanup();

    // get the data.
          struct hostent* data() { return ptr.get(); }
    const struct hostent* data() const { return ptr.get(); }
  };

  // data representation.
  sockaddr_in sa;              // address descriptor
  HostEnt hp;                  // host entry information
  int sock;                    // socket descriptor

protected:
  // constructors
  SocketBase() : hp(), sock(-1) {};
  SocketBase(const string& host, int p);
  SocketBase(const SocketBase& sb) { *this = sb; }

  // assignment operator.
  SocketBase& operator = (const SocketBase& sb)
    {
      if (this != &sb) {
	if (sock > 0) close();
	sa = sb.sa;
	hp = sb.hp;
	sock = sb.sock;
      }
      return *this;
    }

  /* build the socket.  The sa variable must be filled in by derived
     classes BEFORE this method is invoked.  The base class constructor
     will fill in part of the sa object, but not everything.  In particular,
     the sin_addr field is not set; derived class constructors must
     do this. */
  void build();

public:
  // destructor.
  virtual ~SocketBase() { close(); }

  // close the socket.
  void close() { ::close(sock); }

  // get the host name.
  string gethostname() const;

  // get the port.
  int getport() const;

  /*****************************************************************
  Exception classes.
  ******************************************************************/
  // base class
  class SocketException : public runtime_error {
  public:
    SocketException(const string& d) // pass the description
      : runtime_error(d) {}
  };

  // socket errors with host name defined.
  class SocketExceptionWithHost : public SocketException {
  public:
    SocketExceptionWithHost(const string& desc, const string& h)
      : SocketException(desc), host(h) {}
    const string& hostname() const { return host; }
  private:
    string host;
  };

  // unknown host
  class UnknownHost : public SocketExceptionWithHost {
  public:
    UnknownHost(const string& b_host) 
      : SocketExceptionWithHost("unknown host", b_host)
      {}
  };

  // connection failure
  class ConnectFailure : public SocketExceptionWithHost {
  public:
    ConnectFailure(const string& b_host, int p) 
      : SocketExceptionWithHost("connection refused", b_host), pnum(p)
      {}
    int port() const { return pnum; };
  private:
    int pnum;  // the port for the attempted connection.
  };

  // I/O exceptions.
  class SocketIOException : public SocketException {
  protected:
    SocketIOException(const string& d) : SocketException(d) {}
    SocketIOException() : SocketException("IO error") {}
  };
  class SocketReadError : virtual public SocketIOException {
  public:
    SocketReadError() : SocketIOException("error reading from socket") {}
  };
  class SocketWriteError : virtual public SocketIOException {
  public:
    SocketWriteError() : SocketIOException("error writing to socket") {}
  };

  // Reading from or writing to a closed socket.
  class ClosedSocket : public SocketIOException {
  public:
    ClosedSocket() : SocketIOException("socket is not open") {};
  };

  // socket closed, indicated by a SIGPIPE signal.
  class ClosedSocketPipe : public ClosedSocket {
  public:
    ClosedSocketPipe(int s) : ClosedSocket(), sgnl(s) {}
    int signal_number() const { return sgnl; }
  private:
    int sgnl;
  };

  // classes to indicate a bad number of bytes written or read
  class SocketIOBadNBytesException : virtual public SocketIOException {
  public:
    SocketIOBadNBytesException(size_t nb_wanted,
			       size_t nb_used, void* d = NULL)
      : SocketIOException("insufficient data transferred"),
        b_want(nb_wanted), b_used(nb_used), data(NULL)
      {
	/* if we have data and the signed bit of b_used is off, i.e.,
	   we don't have -1 (EOF), copy the data that was read. */
	if (d && (b_used & ~LONG_MAX) == 0) {
	  data = new char [ b_used ];
	  memcpy(data, d, b_used);
	}
      }
    ~SocketIOBadNBytesException() 
	{ 
	    if (data) delete [] static_cast<char *>(data); 
	    data = NULL;
	}
    size_t nbytes_wanted() const { return b_want; }
    size_t nbytes_used()   const { return b_used; }
    const void* get_data() const { return data; }
  private:
    size_t b_want, b_used;
    void* data;  // the data that was read.
  };
  class SocketReadErrorBadNBytes : public SocketReadError,
    public SocketIOBadNBytesException {
  public:
    SocketReadErrorBadNBytes(size_t nb_want, size_t nb_used, void* d)
      : SocketReadError(), SocketIOBadNBytesException(nb_want, nb_used, d)
      {}
  };
  class SocketWriteErrorBadNBytes : public SocketWriteError,
    public SocketIOBadNBytesException {
  public:
    SocketWriteErrorBadNBytes(size_t nb_want, size_t nb_used)
      : SocketWriteError(), SocketIOBadNBytesException(nb_want, nb_used)
      {}
  };
};

#endif
