/****************************************************************************
 * TCPclient.c                                                              *
 *  JRogers, Jan/2003                                                       *
 *                                                                          *
 *  Pedagogical TCP client example                                          *
 *  Usage:                                                                  *
 *   TCPclient [ hostname [sport [lport] ] ]                                *
 *                                                                          *
 *  Connects to hostname (default localhost), sport (default 50000)         *
 *  Optional third argument specifies local port number to use              *
 *  Copies keyboard input to socket and socket input to keyboard            *
 *  Quits when keyboard input is "q\n" or "x\n"                             *
 ****************************************************************************/
#include <stdio.h>       // for printf() and perror()
#include <sys/types.h>   // for system dependent types
#include <sys/socket.h>  // for socket stuff
#include <netinet/in.h>  // for INET protocols and addresses
#include <arpa/inet.h>   // for inet_ntop()
#include <netdb.h>       // for getprotobyname(),gethostbyname() 
#include <errno.h>       // for errno
#include <stdlib.h>      // for strtol()
#include <unistd.h>      // for close()
#include <string.h>      // for memcpy()

#define TRUE ( 0 == 0 )
#define FALSE !TRUE

const char DEFAULT_HOST[] = "localhost"; // Default hostname
const int DEFAULT_PORT = 50000;          // Default server port
const int BUFLEN = 1024;                 // Data buffer length
const int NAMELEN = 16;                  // Max length of dotted octet string 

// Uniform error handler
void errorExit( char* errmsg );

// Extract socket address and ports from command line args
struct sockaddr_in parseAddr( struct hostent** hostaddp, int* localportp,
                              int argc, char** argv);

/****************************************************************************
 * main                                                                     *
 ****************************************************************************/
int main( int argc, char** argv )
{

  int dsock;                 // Data socket descriptor
  int dport = 0;             // Local port number (0 if none assigned)
  struct sockaddr_in daddr;  // Data socket address (See ip(7))

  struct sockaddr_in saddr;  // Server socket address (See ip(7))
  struct hostent* sname;     // ptr to server's hostname (See gethostbyname(3))
  char sdots[NAMELEN];       // Server hostname as dotted octets

  fd_set readfds;            // Bit mask of fds for select
  fd_set readyfds;           // Bit mask of fds with input

  int quit = FALSE;          // Exit flag

  char buffer[ BUFLEN ];     // I/O buffer
  int msglen;                // length of message

  // Parse address arguments, build address
  saddr = parseAddr( &sname, &dport, argc, argv );

  // Get dotted octet form of server address
  //  inet_ntop() converts network addresses to dotted octet strings
  //   (You will also see inet_ntoa, which does not include the AF
  //    parameter and has been obsoleted by inet_ntop().)
  //  This also returns a pointer to the buffer
  inet_ntop( AF_INET,         // Address Family
             &saddr.sin_addr, // address structure for AF (in_addr)
             sdots, NAMELEN   // buffer to hold string
             );

  // Create socket
  //    getprotobyname() returns a protoent struct
  //      the actual protocol number is in its p_proto field
  dsock = socket( PF_INET,      // IPv4 internet protocols
                  SOCK_STREAM,  // connection oriented reliable transport
                  getprotobyname("tcp")->p_proto);  // Protocol number
  if ( dsock < 0 )
    errorExit( "Could not create socket" );

  // Optionally bind socket to requested port
  if ( dport != 0 )
    {
      // Build local socket address
      //   sockaddr_in is the structure of internet domain socket addresses:
      //    (See ip(7).  Machine dependent types as given here are typical.)
      //      struct sockaddr_in
      //      {
      //        short    sin_family;         --- should be AF_INET
      //        u_short  sin_port;	       --- Port number
      //        struct   in_addr sin_addr;   --- Internet address.
      //        unsigned char sin_zero[...]  --- pad (unused)        
      //      };
      //   where in_addr is the structure of an internet domain address:
      //    (See ip(7).)
      //    struct in_addr
      //      {
      //        u_long s_addr;
      //      };
      daddr.sin_family = AF_INET;                // IPv4 address family
      daddr.sin_addr.s_addr = htonl(INADDR_ANY); // Bind on all interfaces
      daddr.sin_port = htons(dport);             // requested port

      // Bind socket to address
      if ( bind( dsock,                       // Socket
                 ( struct sockaddr* ) &daddr, // socket address
                 sizeof( daddr )
                 )
           < 0 )
        errorExit( "Could not bind local socket" );
      printf( "Using port %d\n", ntohs(daddr.sin_port) );
    }

  //  Connect to server socket
  if ( connect( dsock,
                ( struct sockaddr * ) &saddr,
                sizeof( saddr ) )
       < 0 )
    errorExit( "Could not connect server port" );
  
  printf( "Ready to send to %s (%s:%d)\n", 
          sname->h_name, sdots, ntohs(saddr.sin_port) );

  // The client services console and socket input in parallel using select()
  // select() monitors sets of filedescriptors for being ready for read,
  //   write, or for exceptions.  The sets to monitor are passed in a
  //   bit vector of type fd_set.
  // Setup read fd mask
  FD_ZERO(&readfds);                // zero out the fd_set
  FD_SET(fileno(stdin), &readfds);  // set the bit for stdin
  FD_SET(dsock, &readfds);          // set the bit for dsock

  while ( !quit )
    {
      // The fd_set arguments to select() are input/output
      //  on return the bits corresponding to the ready fds are set
      // We use two of the (readfds and readyfds) so that readfds does not
      //  get modified
      readyfds = readfds;
      if ( select( dsock+1,     // Highest numbered fd +1
                   &readyfds,   // fds to monitor for read
                   NULL,        // fds to monitor for write
                   NULL,        // fds to monitor for exceptions
                   NULL)        // timeout (block indefinitely here)
           < 0 )
        errorExit( "Error selecting read fds" );

      if ( FD_ISSET( fileno(stdin), &readyfds ) )
        // console input
        {
          if ( fgets( buffer, BUFLEN, stdin ) == NULL )
            errorExit( "Error reading console input" );
          msglen = strlen(buffer);

          // strip trailing newline, if any
          if ( buffer[msglen-1] == '\n' )
            msglen -= 1;
          // add crlf
          if (msglen <= BUFLEN-2)
            {
              buffer[msglen] = '\r';
              buffer[msglen+1] = '\n';
              msglen += 2;
            }

          if ( send( dsock, buffer, msglen, MSG_NOSIGNAL )
               < msglen )
            errorExit( "Error sending to server" );

          // if message was "q\r\n" or "x\r\n" set exit flag
          quit =  ( msglen == 3 && ( buffer[0] == 'q' 
                                     || buffer[0] == 'x') );
        }

      if ( FD_ISSET( dsock, &readyfds ) )
        // input from server
        {
          // Recieve data from socket
          msglen = recv( dsock,          // Socket descriptor
                         buffer, BUFLEN, // Buffer, length
                         0               // Flags
                         );
          // EAGAIN is set for non-blocking reads when no data is available
          if ( msglen < 0 && errno != EAGAIN )
            errorExit( " Failed to recv from data socket" );

          if ( msglen > 0 )
            {
              if ( write( fileno(stdout), buffer, msglen ) < msglen )
                errorExit( "Failed to echo message to stdout" );
            }
        }
    } // end of while ( !quit )

  //  Close data socket
  close( dsock );
  exit( EXIT_SUCCESS );
}


/****************************************************************************
 *  void errorExit( char* errmsg )                                          *
 *   Log errmsg and system explanation of most recent error to stderr       *
 *   Exit with EXIT_FAILURE                                                 *
 ****************************************************************************/
void errorExit( char* errmsg )
{
  perror( errmsg );
  exit( EXIT_FAILURE );
}

/****************************************************************************
 *  sockaddr_in parseAddr( int argc, char** argv )                          *
 *                         int argc, char** argv )                          *
 *   Parses server hostname, server port and local port from command line   *
 *                                                                          *
 *   Builds socket address and hostent for server                           *
 *   The returned value is the network address of the server is             *
 *   A pointer to the  hostent for the server is returned via the parameter * 
 *     hostaddp (which is, thus, a pointer to a hostent pointer             *
 *                                                                          *
 *  The server's address is determined as follows:                          *
 *   If there are at least two args, the second is parsed as port number    *
 *   Otherwise DEFAULT_PORT is used                                         *
 *   If there is at least one arg, the first is parsed as the hostname      *
 *   Otherwise DEFAULT_HOST is used                                         *
 *                                                                          *
 *   If there are exactly three args, the third is parsed as the local      *
 *    port number which is returned via lport                               *
 *                                                                          *
 *   If there are more than three args, a usage message is printed and      *
 *     exit with FAILURE                                                    *
 ****************************************************************************/
struct sockaddr_in parseAddr( struct hostent** hostaddp, int* lport,
                              int argc, char** argv)
{
  const char* hostname = DEFAULT_HOST; // Pointer to hostname
  int portno = DEFAULT_PORT;           // Port number
  struct sockaddr_in sockaddr;         // socket address
 
  if ( argc > 4 )
    {
      fprintf(stderr, "Usage: %s [hostname [sport [lport]]]\n", argv[0] );
      exit( EXIT_FAILURE );
    }
  if ( argc == 4 )
      *lport = strtol( argv[3], NULL, 10 );
  if ( argc >= 3 )
    portno = strtol( argv[2], NULL, 10 );
  if ( argc >= 2 )
      hostname = argv[1];

  //  Build socket address
  sockaddr.sin_family = AF_INET;         // IPv4 Address Family
  sockaddr.sin_port = htons( portno );   // must be in network byte order
  // Construct sockaddr.sin_addr
  // gethostbyname() provides translation from arbitrary hostname strings
  //   to host addresses.  It returns a pointer to a hostent structure
  //  The hostent structure is defined in <netdb.h> as follows:
  //            struct hostent {
  //                    char    *h_name;        /* official name of host
  //                    char    **h_aliases;    /* alias list
  //                    int     h_addrtype;     /* host address type
  //                    int     h_length;       /* length of address
  //                    char    **h_addr_list;  /* list of addresses
  //            }
  //            #define h_addr  h_addr_list[0]  /* for backward compatibility
  // We're interested in the in h_addr_list[0] which is a pointer
  //  to the host address as a sequence of four bytes in network byte order
  // For the sockaddr_in.sin_addr field this needs to be an in_addr, which
  //  is to say a u_long.  
  // One way to beat this format incompatibility is to use memcpy to do
  //  a byte-by-byte copy from the address of the hostent value to the
  //  address of the sockaddr field
  if ( ( *hostaddp = gethostbyname( hostname ) ) == NULL)
    {
      fprintf(stderr, "Failed to convert %s to host address\n", argv[1] );
      exit( EXIT_FAILURE );
    }
  memcpy((void*) &sockaddr.sin_addr, 
         (*hostaddp)->h_addr_list[0],
         (*hostaddp)->h_length); 

  return sockaddr;
}

      

