/*  voevent_client_demo.c -- to demonstrate a VOEvent socket connection to GCN/TAN
 *
 *  USAGE:
 *      voevent_client_demo  <ip_address>  <port_num>
 *
 *          Where "ip_adress" is the ip address of the GCN/VOEvent server.
 *          Where "port_num" is the port number of the GCN/VOEvent server. 
 *
 *          Example:     voevent_client_demo  209.208.78.170  8099
 *
 *  DESCRIPTION:
 *      INTRO:  This standalone C program shows all the neccessary parts
 *      that a site would use to become a GCN/TAN site connected via
 *      the IVOA VOEvent socket method.  With this program you can connect
 *      to the GCN/TAN system of VOEvent servers and receive VOEvents.
 *      The parts of this program can be cut and spliced into your
 *      site's instrument-control software,  or  this program can be used
 *      as the basis of your instrument's control program (there is a place
 *      noted in the code where to put your instrument-specific code),
 *      or you can use it to just receive the VOEvents.
 *
 *      CLIENT-SERVER:  The site (ie your end of the socket connection)
 *      is "client" end.  The "server" end (ie the VOEvent server) is running on 
 *      one of the GCN/TAN machines.  There are currently 3 servers:
 *          209.208.78.170    (on Atlantic.net cloud service)
 *          50.116.49.68      (on Linode.com cloud service)
 *          68.169.57.253     (on eApps.com cloud service)
 *      All the GCN/TAN VOEvent servers are listening on port 8099.
 *      You, the client, initiate a connection to the server.
 *      The client-server uses the TCP/IP socket method with the protocol
 *      described in  http://www.ivoa.net/Documents/Notes/VOEventTransport/ .
 *
 *      PACKET TYPES:  This program handles all the types of VOEvent Notices
 *      that the GCN/TAN system produces directly and redistributes from other servers.
 *
 *      FILTERING:  There are two modes to receive these VOEvents. 
 *      1) A site can register with GCN/TAN and set up a "configuration".
 *      The configuration contains which Notice types you want to receive and
 *      what other filtering functions to be applied (exactly like the original
 *      GCN filtering functionality, in fact, it is the same "sites.cfg" file).
 *      2) A site can connect (without registering) anonymously.  As such,
 *      no filtering can be applied (since there is no pre-arranged configuration),
 *      and you will receive everything that GCN/TAN produces (as of Feb 2012,
 *      this is about 2000 VOEvents per day).
 *
 *      SERVER DUPLICATION:  All 3 GCN/TAN servers listed above (and any others
 *      that might be brought on-line in the future) have identical streams of VOEvents.
 *      So connecting to any single server will provide you with the 100% full content
 *      that GCN/TAN produces.  And since all 3 servers  use identical "sites.cfg" files,
 *      registered users can switch from one server to another without any
 *      further effort needed on their part.
 *
 *      SUBSCRIBING/PUBLISHING:  This voevent_client_demo program implements
 *      the basic subscriber service.  It does not implement the publisher function.
 *      However, the GCN/TAN server/broker program does except publications from subscribers.
 *      If a registered subscriber writes a VOEvent back to GCN/TAN (on port 8098),
 *      it will be accepted and distributed to the other client/subscribers
 *      connected at the time.  Publications from anonymous clients/subscribers
 *      will not be accepted. 
 *
 *      SPEED:  The VOEvent socket distribution method is very fast.  It takes
 *      3-10 milliseconds to get the packet from the GCN/TAN voevent server computer
 *      to your computer.
 *
 *      ACK/NACK:  For each VOEvent message the client receives, the client should
 *      send back an "ack" or "nack".  An ack if the message is complete and valid,
 *      or a nack if not complete or does not validate.  (This demo client does not
 *      implement validation, so only "acks" are sent back to the server
 *      for everything received.)
 *
 *      ALIVENESS CHECKING:  This program tests to see if it has received anything
 *      in the last 200 sec (since imalives come every 60 sec).
 *      If nothing was received within the last 200 sec, it breaks the connection and
 *      automatically tries to reconnect to the server.
 *
 *      DOES NOT DO:  This demo client program does not implement the following functions:
 *      a) Validating the XML VOEvent messages,
 *      b) Parsing the XML VOEvent messages,
 *      c) Authentication of the sender (GCN/TAN in this case),
 *      d) Publishing your own VOEvents back to GCN/TAN server/broker.
 *
 *  OPERATIONAL CONSIDERATIONS:
 *      ONLY ONE PROGRAM AT A TIME ON ONE COMPUTER:  If you choose to connect
 *      to GCN/TAN as a vetted site, you can only have one client running
 *      on the registered machine per server.  A second connection will esentially
 *      overwrite/replace the first connection.  GCN/TAN recognizes incoming connections
 *      by their IP Number.  So two clients with the same IP Number (ie same computer)
 *      will be recognized by both entries in the sites configuration file.
 *      The first client will get nothing, because its connection will be
 *      overwritten/replaced by the second).  And if there is only one entry
 *      in the config file that has your computer's IP Number, then the second
 *      connection will again overwrite the first connection, and only the second
 *      will get events.  If you need two clients on one machine, then have the 
 *      second connection connect to one of the other GCN/TAN servers.  Each client
 *      will get the one config's set of VOEvents.
 *      Anonymous sites can have multiple clients running on the same machine;
 *      they will each receive all the VOEvents produced by GCN/TAN.
 *
 *      STANDARDOUT COPY:  This program duplicates almost all of its output
 *      to the logfile also to the standardout.  This duplication to the stdout
 *      allows the first-time/interactive user to "see" what is going on with the
 *      operations of the program.  After a while, you will probably want to
 *      (a) invoke the program with the stdout redirected to /dev/null and
 *      run it in the background:
 *          voevent_client_demo ip_addr portnum >& /dev/null &
 *      or (b) start commenting-out some of the stdout printf's that you no longer
 *      want to see.
 *
 *      ERRORS:  All the "bad situations" within the operations of this demo program
 *      are marked in the print statements with the prefix "ERR:" to the error message.
 *      This prefix allows the user to quickly search the logfile for instances of bad activity.
 *
 *  FILES:
 *      ./voevent_client_demo.log // A logfile of all packets received and program actions & errors.
 *
 *  LIST OF ROUTINES:
 *      main()                    // The main processing routine
 *      brokenpipe()              // Wrap things up if the socket connection breaks
 *      establish_server_conn()   // Establish a connection to the server
 *      burn_off()                // Burn off the stuff in the input read buffer
 *      fixedgmtime()             // Fixed GMTime() wrapper (ie convert a year_value of 112 --> 12)
 *      ut_ctime()                // Convert TimeStruct into yy/mm/dd hh:mm:ss string
 *      build_imalive_response()  // Build an Imalive Response Message to receiving an imalive message
 *      build_voevent_response()  // Build an VOEvent Response Message to receiving a voevent message
 *      extract_ivorn()           // Extract Ivorn from voevent message
 *
 *  CHANGE BEFORE COMPILE:
 *      There are 4 program variables which must be given values specific to your site's setup
 *      before compiling.  See lines ~186 thru ~191.
 *
 *  COMPILATION:
 *      For LINUX platforms: 
 *          cc voevent_client_demo.c -o voevent_client_demo
 *          (If you run on a 64-bit platform, you may need to add a -m32 to your compile options.)
 *
 *  MISC NOTES:
 *      1) This file looks best with a tabstop=4 setting.
 *
 *      2) This code was purposefully written in a simple brute-force style
 *      so that it is easier to read no matter what the experience level of the end-user.
 *      Also, by not using the full set of features within the C-language,
 *      it is easier to transliterate to other languages (eg Python, Fortran, whatever).
 *      It is all in a single file to make public distribution and installation easier.
 *
 *  SEE ALSO:
 *      xml_socket_demo.c      // The VOEvent XML messages, but uses the GCN protocol
 *      socket_demo.c          // The original 160-byte binary GCN packet protocol
 *
 *  AUTHOR:
 *      Scott Barthelmy         NASA-GSFC                   Code 661.0
 *      Teresa Sheets           NASA-GSFC                   Code 587.0
 */

// Please note that most of these include_files are the ones for LINUX.
// On other machines with different operating systems (or even different
// versions of unix), these include_files may have different names.
// If, when you compile voevent_client_demo.c on your local platform, you find
// that one or more of these files are "not found", then you will have
// to explore your system's include_files to find the contents that are missing
// during the compilation process.  Unfortunately, there are too many
// combinations of op_sys's for me to incorporate the appropriate include_files.
#include <stdio.h>              // Standard i/o header file
#include <signal.h>             // For the brokenpipe signal catching
#include <string.h>             // For strcpy(), strcat(), etc
#include <sys/ioctl.h>          // For the ioctl() call to make the connection non-blocking
#include <unistd.h>             // For read(), sync(), close(), ...
#include <time.h>               // For the time() and ut_ctime() calls
#include <errno.h>              // For the error handling
#include <stdlib.h>             // For the system() and exit() calls
#include <arpa/inet.h>          // For the socket structures and calls


#define TRUE    1               // Common sense logic macros
#define FALSE   0

#define BUF_SZ  30000           // Size of the xml read-in buffer


char    *version = "voevent_client_demo     Ver: 1.00   02 Mar 2012";



// **************************************************************************************
// THESE NEXT 4 ITEMS NEED TO BE ASSIGNED VALUES APPROPRIATE TO THE LOCAL USER:

char  ack_subivo[100] = "ivo://voevent_subscriber_name#";         // The ivorn of the subscriber (something that IDs you)
char  ack_ipaddr[50]  = "\"subscibers_ip_addr:123.123.123.123\""; // Your IP number where this client is running
char  ack_contact[100] = "\"yourname@your.email.address\"";       // Your contact email address
// The ack_result_mesg may be filled with whatever message you would like to send back to the server.
// You could modify this code to create a different response for different (good vs bad) conditions. 
char  ack_result_mesg[100] = "Message received.";                 // Whatever response information you want to send back
// **************************************************************************************



// A few global variables for the voevent_client_demo program:
FILE    *lg;                          // Logfile stream pointer
int     errno;                        // Error return code number
time_t  tloc;                         // Seconds since machine epoch
char    on = 1;                       // Used to allow re-connects before FIN_WAIT clears
struct sockaddr_in serv_addr;         // Structure of server info
int     ssd;                          // Server Socket Descriptor

// Declarations of functions used in the program:
void    brokenpipe();                 // Clean up after getting a brokenpipe signal
void    establish_server_conn();      // Establish a connection to the server
void    burn_off();                   // Burn off the input read buffer, try to get back in sync
char    *ut_ctime(struct tm *);       // Convert TimeStruct into yy/mm/dd hh:mm:ss string 
char    *build_voevent_response();    // Build client specific VOEvent Message Response  
char    *build_iamalive_response();   // Build client specific Iamalive Response
char    *extract_ivorn(char *);       // Extract ivorn from buffer


/*---------------------------------------------------------------------------*/
int
main(argc, argv)
int            argc;                // Number of cmdline arguments
char           *argv[];             // Pointers to all the cmdline arguments
{
int            bytes1,bytes2,bytes3;// Number of bytes read from the socket
int            wbytes1,wbytes2;     // Number of bytes written to the socket
int            got_voe=0;           // Flag indicating received a VO message (imalive or VOEvent)
long           pktlen;              // Number of bytes to read from or write to the socket (a 4-byte long)
long           netpktlen;           // Network byte order number of bytes read from or written to the socket
char           xmlbuf[BUF_SZ];      // The packet data buffer
struct sigvec  vec;                 // Signal vector structure
time_t         nowtime;             // The current system time
time_t         last_nowtime;        // The time of the last packet received
struct tm      *tim, *fixedgmtime();// Ptr to struct of date/time stuff
char           *ackbuf;             // Pointer to the regular response (from build_voevent_response())
char           *ackima;             // Pointer to the imalive response (from build_iamalive_response())
char           *p;                  // Pointer to printf() format string to build print messages


if(argc != 3)
    {
    printf("USAGE:  voevent_client_demo  <server_ip_address>  <port_num>\n");
    exit(1);
    }

bzero((char *)xmlbuf,sizeof(xmlbuf));   // Clears the buffer
time(&last_nowtime);                    // Init this so it doesn't trip on the first loop
tim = fixedgmtime(&nowtime);            // Fill up the structure

// Open the logfile:
if((lg = fopen("voevent_client_demo.log", "a")) == NULL)  // Open for appending
    {
    printf("Failed to open logfile.  Exiting.\n");
    exit(2);
    }
// Write the banner and timestamp to the logfile:
fprintf(lg,"\n===================== New Session ========================\n");
fprintf(lg,"Start Time: %s    Program: %s\n",ut_ctime(tim),version);
fprintf(lg,"host_ip=%s, port=%d\n",argv[1],atoi(argv[2]));

// Set up the signal catcher (so brokenpipe() is called when a socket broken):
vec.sv_handler = brokenpipe;
vec.sv_mask = sigmask(SIGPIPE);
vec.sv_flags = 0;
if(sigvec(SIGPIPE, &vec, (struct sigvec *) NULL) == -1)
    {
    printf(    "ERR: Sigvec() did not work when setting up brokenpipe.\n");
    fprintf(lg,"ERR: Sigvec() did not work when setting up brokenpipe.\n");
    }

// Set up the client connection to the server:
bzero((char *) &serv_addr, sizeof(serv_addr));    // Be sure to start out with a clean structure (paranoid)
serv_addr.sin_family = AF_INET;                   // This will be an internet connection
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);   // The server's name (1st arg on the cmdline)
serv_addr.sin_port = htons(atoi(argv[2]));        // Port number on the server (converted to network byte order)
establish_server_conn();                          // Establish a connection to the server


// An infinite loop for reading the socket (the voevents & the imalives), 
// and handling the reconnection if there is a break.
// Also, near the end of this loop is your site/instrument-specific code (if you want).
while(1)
  {
  if(ssd <= 0) printf("Not Connected...\n");      // A busy light
  else         printf("Checking for data.   (or  Possibly checking for connection.)\n");
  time(&nowtime);                                 // Get the current system time
  tim = fixedgmtime(&nowtime);                    // Fill up the structure
  got_voe = 0;                                    // Initialize the got-something flag

  // If socket is connected, read the socket for any xml_message;
  // if the socket does not have a message, continue to loop.
  if(ssd <= 0)                                    // Not connected
      {
      // If the sock descriptor is zero or less, the socket connection has been broken.
      // Try to re-establish the socket.
      printf(    "%s Detected disconnect.  Attempting to reconnect.\n",ut_ctime(tim));
      fprintf(lg,"%s Detected disconnect.  Attempting to reconnect.\n",ut_ctime(tim));
      establish_server_conn();                    // Re-establish a connection to the server
      }
  else                                            // Connected
      {
      // Two read() calls to rcv: (1) a 4-byte length, (2) then the xml message. 
      if((bytes1=read(ssd,(char *)&netpktlen,sizeof(netpktlen))) > 0)
          {
          pktlen = ntohl(netpktlen);              // Convert it from network byte order to local host byte order
          printf("%s 1st read: bytes=%d, pktlen=%ld=0x%lX  netpktlen=%ld=0x%lX,  errno=%d=%s.\n",
                                         ut_ctime(tim),bytes1,
                                         pktlen,(unsigned long)pktlen, netpktlen,(unsigned long)netpktlen,
                                         errno, strerror(errno));
          if((pktlen < 0) || (pktlen > BUF_SZ))
              {
              printf(    "ERR: Pktlen(=%ld) was out of range (<0 or >%d); we must be out of sync.\n",pktlen,BUF_SZ);
              fprintf(lg,"ERR: Pktlen(=%ld) was out of range (<0 or >%d); we must be out of sync.\n",pktlen,BUF_SZ);
              burn_off(ssd,xmlbuf);               // Burn off the input read buffer, try to get back in sync
              continue;
              }

          if(bytes1 == 4)                         // The 1st read was successful
             { 
             //usleep(500000);                    // Sleep 0.5 sec (give time for the xml message to show up)
             last_nowtime = nowtime;              // Record the time we got this packet
             sync();                              // Paranoia to improve the timely availability of the 2nd part
             bzero((char *)xmlbuf,sizeof(xmlbuf));// Clear the buffer
             if((bytes2=read(ssd,(char *)xmlbuf,pktlen)) < pktlen)   // Read the VOEvent XML message (the 2nd read)
                {                                 // Read too little
                if(bytes2 == -1)
                   {
                   printf(    "%s  ERR: 2nd read() returned -1.  errno=%d=%s.\n",ut_ctime(tim),errno,strerror(errno));
                   fprintf(lg,"%s  ERR: 2nd read() returned -1.  errno=%d=%s.\n",ut_ctime(tim),errno,strerror(errno));
                   if(close(ssd))
                       {
                       p = "%s  ERR: close() failed (for the 2nd read problem). errno=%d=%s.\n";
                       printf(    p,ut_ctime(tim),errno,strerror(errno));
                       fprintf(lg,p,ut_ctime(tim),errno,strerror(errno));
                       }
                   ssd = -1;  
                   burn_off(ssd,xmlbuf);          // Burn off the input read buffer, try to get back in sync
                   continue;
                   }
                else
                   {
                   p = "%s  WARN: 2nd read: Did not read enough on the first attempt. bytes=%d pktlen=%ld.\n";
                   printf(    p,ut_ctime(tim),bytes2,pktlen);
                   fprintf(lg,p,ut_ctime(tim),bytes2,pktlen);
                   usleep(500000);                // Sleep 0.5 sec (give time for the remainder of the xml message to show up)
                   if((bytes3=read(ssd,(char *)xmlbuf+bytes2,pktlen-bytes2)) < (pktlen-bytes2)) // Try to read more
                       {
                       p = "%s  ERR: 3rd read: Did not read enough on the second attempt: bytes3=%d(!=pktlen-bytes=%ld), errno=%d=%s.  Closing.\n";
                       printf(    p,ut_ctime(tim),bytes3,pktlen-bytes2,errno,strerror(errno));
                       fprintf(lg,p,ut_ctime(tim),bytes3,pktlen-bytes2,errno,strerror(errno));
                       if(close(ssd))
                           {
                           p = "%s  ERR: close() failed (for the 3rd read problem). errno=%d=%s.\n";
                           printf(    p,ut_ctime(tim),errno,strerror(errno));
                           fprintf(lg,p,ut_ctime(tim),errno,strerror(errno));
                           }
                       ssd = -1;  
                       burn_off(ssd,xmlbuf);      // Burn off the input read buffer, try to get back in sync
                       continue;
                       }
                   else if(bytes3 == (pktlen-bytes2))
                       {
                       printf(    "OK: Got the remainder during the 3rd read attempt.\n");
                       fprintf(lg,"OK: Got the remainder during the 3rd read attempt.\n");
                       got_voe = 1;               // Set flag that now we have a whole mesg to use
                       }
                   }
                }
            else if(bytes2 > pktlen)              // Read too much
                {
                p = "%s  ERR: 2nd read: really bad situation -- read too much. bytes=%d pktlen=%ld.\n";
                printf(    p,ut_ctime(tim),bytes2,pktlen);
                fprintf(lg,p,ut_ctime(tim),bytes2,pktlen);
                }
            else                                  // Read just the right amount
                {
                printf(    "%s  2nd read: Reading bytes from sd=%d...%d bytes.\n",ut_ctime(tim),ssd,bytes2);
                fprintf(lg,"%s  2nd read: Reading bytes from sd=%d...%d bytes.\n",ut_ctime(tim),ssd,bytes2);
                printf(    "%s  Got a new xml message from VOEvent server.\n",ut_ctime(tim));
                fprintf(lg,"%s  Got a new xml message from VOEvent server.\n",ut_ctime(tim));
                got_voe = 1;
                }

            if(got_voe)                           // The single full read (or the two partial reads) yielded a voevent 
                {
                // Print the xml messages and send back the ack (sorry, no checking, so no nack capability).
                printf(    "%s\n",xmlbuf);
                fprintf(lg,"%s\n",xmlbuf);
                // If we got an imalive, then reply with the special imalive ack:
                if((strstr(xmlbuf,"iamalive") || strstr(xmlbuf,"imalive")))   // Allow both spellings of imalive
                   { 
                   // The "ack" for an iamalive is different than an ack for a regular voevent:
                   ackima = build_iamalive_response();
                   printf("\nResponse:\n%s\n",ackima); 
                   pktlen = strlen(ackima);
                   netpktlen = htonl(pktlen);     // Convert from local host order to network byte order
                   printf("pktlen=%ld=0x%lX\n",pktlen,(unsigned long)pktlen);

                   if((wbytes1=write(ssd,(char *)&netpktlen,4)) != 4)
                      {
                      p = "%s ERR: Iamalive response pktlen write: Bytes written=%d not equal to 4.\n";
                      printf(    p,ut_ctime(tim),wbytes1);
                      fprintf(lg,p,ut_ctime(tim),wbytes1);
                      }
                   else
                      {
                      printf(    "%s Ack: Iamalive response pktlen written. wbytes1=%d\n",ut_ctime(tim),wbytes1);
                      fprintf(lg,"%s Ack: Iamalive response pktlen written. wbytes1=%d\n",ut_ctime(tim),wbytes1);
                      }
                   if((wbytes2=write(ssd,(char *)ackima,pktlen)) != pktlen)
                      {
                      p = "%s ERR: Iamalive response ack write:  Bytes written=%d not equal to %ld.\n";
                      printf(    p,ut_ctime(tim),wbytes2,pktlen);
                      fprintf(lg,p,ut_ctime(tim),wbytes2,pktlen);
                      }
                   else
                      {
                      printf(    "%s Ack: Iamalive response ack written. wbytes2=%d\n",ut_ctime(tim),wbytes2);
                      fprintf(lg,"%s Ack: Iamalive response ack written. wbytes2=%d\n",ut_ctime(tim),wbytes2);
                      }
                   }
                // Else reply with the regular ack:
                else
                   {
                   // If this demo program implemented validation, it would go here.
                   ackbuf = build_voevent_response(xmlbuf);
                   printf("\nResponse:\n%s\n",ackbuf); 
                   pktlen = strlen(ackbuf);
                   netpktlen = htonl(pktlen);
                   printf("pktlen=%ld=0x%lX\n",pktlen,(unsigned long)pktlen);
 
                   if((wbytes1=write(ssd,(char *)&netpktlen,4)) != 4)
                      {
                      p = "%s ERR: VOEvent response pktlen write: Bytes written=%d not equal to 4.\n";
                      printf(    p,ut_ctime(tim),wbytes1);
                      fprintf(lg,p,ut_ctime(tim),wbytes1);
                      }
                   else
                      {
                      printf(    "%s Ack: VOEvent response pktlen written. wbytes1=%d\n",ut_ctime(tim),wbytes1);
                      fprintf(lg,"%s Ack: VOEvent response pktlen written. wbytes1=%d\n",ut_ctime(tim),wbytes1);
                      }
                   if((wbytes2=write(ssd,(char *)ackbuf,pktlen)) != pktlen)
                      {
                      p = "%s ERR: VOEvent response ack write:  Bytes written=%d not equal to %ld.\n";
                      printf(    p,ut_ctime(tim),wbytes2,pktlen);
                      fprintf(lg,p,ut_ctime(tim),wbytes2,pktlen);
                      }
                   else
                      {
                      printf(    "%s Ack: VOEvent response ack written. wbytes2=%d\n",ut_ctime(tim),wbytes2);
                      fprintf(lg,"%s Ack: VOEvent response ack written. wbytes2=%d\n",ut_ctime(tim),wbytes2);
                      }

                   // THIS IS THE LOCATION WERE YOU CAN PARSE AND USE THE VOEVENT MESSAGE.

                   }
                } //end of got_voe
             } //end of the read-something
          } //end of the start of the reading sequence
      else if((bytes1 == 0) && (errno != EWOULDBLOCK))
          // Since the read is non-blocking, the read will return with the error EWOULDBLOCK(or EAGAIN), 
          // but you want to continue checking for data.  If the error is NOT EWOULDBLOCK, then there is a problem.
          {                                       // The connection is broken
          p = "%s (bytes==0 && errno!=EWOULDBLOCK)  errno=%d=%s ssd=%d.\n";
          printf(    p,ut_ctime(tim),errno,strerror(errno),ssd);
          fprintf(lg,p,ut_ctime(tim),errno,strerror(errno),ssd);
          brokenpipe();                           // Handle any failure that we did not expect
          }
      else if((bytes1 == 0)) 
          // If read returns no bytes and not even an EWOULDBLOCK, then there is a problem.
          {                                       // The connection is broken
          printf(    "%s (bytes==0)  errno=%d=%s ssd=%d.\n",ut_ctime(tim),errno,strerror(errno),ssd);
          fprintf(lg,"%s (bytes==0)  errno=%d=%s ssd=%d.\n",ut_ctime(tim),errno,strerror(errno),ssd);
          brokenpipe();                           // Handle any failure that we did not expect
          }
      } //end of else connected

  // Check to see if regularly receiving imalive from TAN server (reset connection if not):
  if((nowtime - last_nowtime) > 200)              // 3 times the imalive interval(60sec) plus 20 sec
      {
      printf(    "WARN: It has been greater than 200 sec since the last (=%ld).\n", nowtime-last_nowtime);
      fprintf(lg,"WARN: It has been greater than 200 sec since the last (=%ld).\n", nowtime-last_nowtime);
      last_nowtime = nowtime;                     // Reset it so we don't get a million error statements
      brokenpipe();                               // Close connection & cause the program to re-establish the connection
      }

  bzero((char *)xmlbuf,sizeof(xmlbuf));           // Clear the incoming VOEvent XML buffer

  // This sleep() is ONLY for this demo program.  It should not be included
  // in your site's application code as it most definitely adds up to 1 sec
  // to the response time to the socket/packet servicing.
  sleep(1);                                       // Give the CPU a rest
  }  //end while(1) loop

close(ssd);
exit(0);
}

/*---------------------------------------------------------------------------*/
void
brokenpipe()                   /* clean up after getting a brokenpipe signal */
{
printf(    "brokenpipe(): If this is the last line, then the shutdown() to GCN/TAN hung.\n");
fprintf(lg,"brokenpipe(): If this is the last line, then the shutdown() to GCN/TAN hung.\n");
if(shutdown(ssd,2) == -1)
    {
    perror("brokenpipe(): shutdown(): ");
    printf(    "ERR: brokenpipe(): The shutdown did NOT work. errno=%d=%s\n",errno,strerror(errno));
    fprintf(lg,"ERR: brokenpipe(): The shutdown did NOT work. errno=%d=%s\n",errno,strerror(errno));
    }
else
    {
    printf(    "brokenpipe(): The shutdown worked OK.\n");
    fprintf(lg,"brokenpipe(): The shutdown worked OK.\n");
    }
printf(    "brokenpipe(): After the shutdown call; the shutdown didn't hang.\n");
fprintf(lg,"brokenpipe(): After the shutdown call; the shutdown didn't hang.\n");
if(close(ssd))
    {
    printf(    "ERR: close() problem in brokenpipe(), errno=%d=%s\n",errno,strerror(errno));
    fprintf(lg,"ERR: close() problem in brokenpipe(), errno=%d=%s\n",errno,strerror(errno));
    }
ssd = -1;
}

/*---------------------------------------------------------------------------*/
void
establish_server_conn()              /* Establish a connection to the server */
{
if((ssd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
    {
    printf(    "ERR: client: can NOT open stream socket. errno=%d=%s.\n",errno,strerror(errno));
    fprintf(lg,"ERR: client: can NOT open stream socket. errno=%d=%s.\n",errno,strerror(errno));
    }
if(connect(ssd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0) 
    {
    printf(    "ERR: client: can NOT connect to server. errno=%d=%s.\n",errno,strerror(errno));
    fprintf(lg,"ERR: client: can NOT connect to server. errno=%d=%s.\n",errno,strerror(errno));
    // Reset ssd = -1 so that the loop will keep trying to connect
    ssd = -1; 
    }
else
    {
    printf(    "Client connected to server. ssd=%d\n",ssd);
    fprintf(lg,"Client connected to server. ssd=%d\n",ssd);
    // Make the connection non-blocking:
    if(ioctl(ssd, FIONBIO, &on) < 0)
      {
      printf(    "ERR: establish_server_conn(): ioctl(): errno=%d=%s.\n",errno,strerror(errno));
      fprintf(lg,"ERR: establish_server_conn(): ioctl(): errno=%d=%s.\n",errno,strerror(errno));
      }
    }
}

/*---------------------------------------------------------------------------*/
// The assumption is: If we called this routine, we got some spurious value
// for 'pktlen' (for the amount of stuff to read), so we must be out of sync
// with the basic sequence of (a) 4-byte value of N-byte in the voevent message, and 
// (b) those N bytes of the message.  This will clear out the input buffer
// (hopefully faster than new stuff can arrive) so that it will get back into
// that two-part read sequence again.
// This (simple) technique does not work on some machines/op_sys.
void
burn_off(sd,buf)  /* Burn off the input read buffer, try to get back in sync */
int  sd;     // The file dscriptor on which to read()
char *buf;   // Ptr ot a buffer to put the read-in stuff
{
int  flag;   // Flag to control the while-loop
int  nbytes; // How many bytes did we read

flag = 1;
while(flag)
    {
    nbytes = read(sd,(char *)buf,BUF_SZ);
    printf(    "Burned off %d bytes.\n",nbytes);
    fprintf(lg,"Burned off %d bytes.\n",nbytes);
    if(nbytes == 0)                    // If nothing left, then exit this loop
        {
        printf(    "Nothing left to read; will exit this burn-off loop.\n");
        fprintf(lg,"Nothing left to read; will exit this burn-off loop.\n");
        flag = 0;
        }
    if(flag++ > 10)                    // Safety measure to prevent infinite loop
        {
        printf(    "Exceeded 10-cnt in the burn-off loop; will exit this burn-off loop.\n");
        fprintf(lg,"Exceeded 10-cnt in the burn-off loop; will exit this burn-off loop.\n");
        flag = 0;
        }
    }
}

/*---------------------------------------------------------------------------*/
// Drop the 'year' field back down to a 2-digit quantity.
struct tm *
fixedgmtime(nowtime)                               /* FIXED GMTIME() wrapper */
time_t  *nowtime;
{
static struct tm    fixed;
struct tm           *tm;

tm = gmtime(nowtime);
bcopy((char *)tm,(char *)(&fixed),sizeof(struct tm));
fixed.tm_year -= 100;                  // eg converts 112 to 12
return((struct tm *)(&fixed));
}

/*---------------------------------------------------------------------------*/
char *
ut_ctime(struct tm *tim) /* convert TimeStruct into yy/mm/dd hh:mm:ss string */
{
static char    ymdhms[50];    // Permanent place to build the string

//  Day dd Mmm yy hh:mm:ss UT    e.g. Wed 15 Feb 12 19:22:21 UT
strftime(ymdhms,42,"%a %d %h %y %H:%M:%S UT",tim);  // Put the time in a nice format

return(ymdhms);
}

/*---------------------------------------------------------------------------*/
// The "ack" response to an iamalive message is different than to a regular VOEvent.
// This is because 'iamalives' are considered to be 'transport' messages,
// and not real/regular VOEvent messages.
// See http://www.ivoa.net/Documents/Notes/VOEventTransport/
char *
build_iamalive_response()                         /* Build Iamalive Response */
{
time_t          nowtime;               // The current system time
struct tm       *tim,*fixedgmtime();   // Ptr to structure of current UT values
static char     ack_buf[10000];        // Buffer to hold the complete iamalive message response

time(&nowtime);                        // Get the system time for the notice timestamp
tim = fixedgmtime(&nowtime);

bzero((char *)ack_buf,sizeof(ack_buf));// Clears the buffer (paranoia)

sprintf(ack_buf,"<?xml version='1.0' encoding='UTF-8'?>\n<trn:Transport role=\"iamalive\" version=\"1.0\"\nxmlns:trn=\"http://telescope-networks.org/schema/Transport/v1.1\"\nxmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\nxsi:schemaLocation=\"http://telescope-networks.org/schema/Transport/v1.1\nhttp://telescope-networks.org/schema/Transport-v1.1.xsd\">\n<Origin>ivo://nasa.gsfc.gcn/GCN#</Origin>\n<Response>%s</Response>\n<TimeStamp>20%02d-%02d-%02dT%02d:%02d:%02d</TimeStamp>\n<Meta>\n<Param name=\"IPAddr\" value=%s />\n<Param name=\"Contact\" value=%s />\n</Meta>\n</trn:Transport>",
       ack_subivo, tim->tm_year, tim->tm_mon+1, tim->tm_mday, tim->tm_hour, tim->tm_min, tim->tm_sec,
       ack_ipaddr, ack_contact);

return(ack_buf);
}

/*---------------------------------------------------------------------------*/
char *
build_voevent_response(char *buf)          /* Build VOEvent Message Response */
{
time_t          nowtime;                // The current system time
struct tm       *tim,*fixedgmtime();    // Ptr to structure of current UT values
static char     ack_buf[10000];         // Buffer to hold the complete voevent message response
char            *ack_ivorn;             // Origin is the IVORN of the just received VOEvent message 

ack_ivorn = extract_ivorn(buf); 
//printf("ack_ivorn=%s\n",ack_ivorn);

time(&nowtime);                         // Get the system time for the notice timestamp
tim = fixedgmtime(&nowtime);

bzero((char *)ack_buf,sizeof(ack_buf)); // Clears the buffer (paranoia)

sprintf(ack_buf,"<?xml version='1.0' encoding='UTF-8'?>\n<trn:Transport role=\"ack\" version=\"1.0\"\nxmlns:trn=\"http://telescope-networks.org/schema/Transport/v1.1\"\nxmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\nxsi:schemaLocation=\"http://telescope-networks.org/schema/Transport/v1.1\nhttp://telescope-networks.org/schema/Transport-v1.1.xsd\">\n<Origin>%s</Origin>\n<Response>%s</Response>\n<TimeStamp>20%02d-%02d-%02dT%02d:%02d:%02d</TimeStamp>\n<Meta>\n<Param name=\"IPAddr\" value=%s />\n<Param name=\"Contact\" value=%s/>\n<Result>%s</Result>\n</Meta>\n</trn:Transport>",
       ack_ivorn, ack_subivo, tim->tm_year, tim->tm_mon+1, tim->tm_mday, tim->tm_hour, tim->tm_min, tim->tm_sec,
       ack_ipaddr, ack_contact, ack_result_mesg);

return(ack_buf);
}

/*---------------------------------------------------------------------------*/
// The ivorn in the VOEvent Message is contained between double quotes after the phrase ivorn=
char *
extract_ivorn(char *buf)               /* Extract Ivorn from voevent message */
{
static char     ivornbuf[500];         // Buffer to hold the complete voevent message response
char            *p;                    // Temporary pointer 
int             i;                     // Loop counter
time_t          nowtime;               // The current system time
struct tm       *tim,*fixedgmtime();   // Ptr to structure of current UT values

time(&nowtime);                        // Get the system time for the notice timestamp
tim = fixedgmtime(&nowtime);

for(i=0;i<500;i++) ivornbuf[i] = 0;    // Clears the buffer (paranoia)

//printf("extract_ivorn(): buf=\n%s\n",buf);
p = strstr(buf,"ivorn="); 
if(p == NULL)
    {
    printf(    "%s  ERR: extract_ivorn(): Could not find Ivorn in the VOEvent Message.\n",ut_ctime(tim));
    fprintf(lg,"%s  ERR: extract_ivorn(): Could not find Ivorn in the VOEvent Message.\n",ut_ctime(tim));
    ivornbuf[0]='\0';
    }
else
    {
    strncpy(ivornbuf,p+7,499);         // Use ptr arith to get to the right place
    p = strstr(ivornbuf,"\'");         // Check for either single or double quote mark
    if(p == NULL)
        p = strstr(ivornbuf,"\"");
    if(p == NULL) 
        ivornbuf[499]='\0';
    else
        *p = '\0';
    }

return(ivornbuf);
}
