/*  xml_sock_demo.c -- to demostrate an XML socket connection for a GCN site
 *
 *  USAGE:
 *      xml_sock_demo  <port_num>
 *
 *          Where "port_num" is the port number for your site.
 *
 *  DESCRIPTION:
 *      INTRO:  This standalone C program shows all the neccessary parts
 *      that a site would use to become a GCN site connected via
 *      the XML_socket method.  These parts can be cut and spliced
 *      into your site 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).
 *
 *      CLIENT-SERVER:  The site (in this case the computer/control_program
 *      for the instrument that will be making the follow-up observations)
 *      is acting as a socket "server".  The GCN machine here at GSFC
 *      acts as a socket "client".  This is a reversal of what might be
 *      considered the common sense notion of who is "serving" whom,
 *      but it is necessary for the case of multiple sites.  The GCN machine
 *      needs to be able to handle the case when one or more of
 *      the GCN sites "goes off-line".  To do this GCN needs to be
 *      the "client" end.  If your "server" end is running, the GCN "client"
 *      will connect to it and send the various XML_packets (imalives and
 *      burst types).  The GCN program makes continuous periodic attempts
 *      to connect to all socket sites listed in the GCN "sites.cfg" file.
 *      If your facility uses firewalls, you may need to ask your IT Security people
 *      to put a hole in the wall for the GCN computer to come in through
 *      to establish the socket connection.
 *
 *      PACKET TYPES:  This version of the program has been set up to handle all
 *      the types of packets that the GCN system produces.  However, each site
 *      chooses which sources of GRB and Transient information it wants sent to it.
 *
 *      SPEED:  The XML_socket distribution method is very fast.  It takes
 *      2-5 milliseconds to get the packet from the GCN computer to your computer.
 *
 *      NO WRITE-BACK:  Unlike the regular socket connection method, this xml_socket method
 *      does not require the echoing back to GCN of all packets received.
 *
 *      ALIVENESS CHECKING:  (a) This program test to see if it has received
 *      anything in the last 80 sec (since imalives come every 60 sec).
 *      It resets the server-end of the link and is then ready for when GCN
 *      will next try to reconnect.
 *      (b) Also, the program reports to the operator via e-mail
 *      when no packets have been received for more than 600 seconds.
 *      It also sends e-mail when the packets resume.
 *
 *      PORT NUMBERS:  The port number is unique for each site and must be chosen &
 *      agreed to by both ends before any activity can take place.  A block of acceptable
 *      port numbers was choosen so as to not conflict with any known commercial
 *      Internet socket activity.
 *
 *      CONFIG SETUP:  Also, before any connections are possible,
 *      a small amount of data identifying the site (name, Long, Lat,
 *      filter criteria, IPN, port, etc) must be added to the "sites.cfg" file
 *      at the GCN end.  See  http://gcn.gsfc.nasa.gov/gcn/invitation.html
 *
 *  OPERATIONAL CONSIDERATIONS:
 *      RESTARTING TOO QUICKLY:  It is a common phenomenon when running
 *      this program that when it is terminated (for whatever reason)
 *      and restarted too quickly, that it will complain about
 *      "server bind()" errors.  It varies from site to site, but too quickly
 *      seems to be within ~30 sec.  So if you wait at least 60 sec
 *      before restarting this program, then you should avoid this problem.
 *      Basically, it is a result of the operating system on your machine
 *      "remembering" for a few tens of seconds who/what was assigned to each port.
 *      It is possible to adjust this time period, but it is op-sys/machine
 *      dependent and I thought it better to leave it out of this
 *      "general purpose demo program".
 *
 *      CONNECTION TIME TO GCN:  The GCN goes through a series of attempts
 *      to connect to each xml_socket site.  The time series is:
 *      T_zero, T+4min, T+8, T+16, T+32, T+64, T+120, +=120, +=120, ...
 *      T_zero is (a) when the GCN program first starts running, or
 *      (b) when a socket site disconnects.  So, depending on where GCN is
 *      within this time series, you may have to wait up to 2 hours
 *      for a connection to be established.
 *
 *      ONLY ONE PROGRAM AT A TIME:  Another scenario which produces a situation
 *      where connection is not made comes from having two xml_sock_demo
 *      programs running simultaneously.  This can easily happen
 *      when running xml_sock_demo in the background, and then invoking
 *      it a second time.  It can also happen in a much more subtle and
 *      irksome way.  If the xml_sock_demo gets "wedged", halted, paused, or
 *      some other similar defunct type mode, then it will sometimes not show up
 *      in the process table.  Not seeing it present in the process table
 *      will logically cause people to conclude that it is not present, and
 *      then they will restart it.  But it is in fact still present -- at least
 *      to the point of still holding on to the socket connection and port number.
 *      Thus it will be competing for packets with the second invokation, and
 *      both will loose.  It looks to the user that the second program
 *      can never connect.  This can be very perplexing to the user/operator.
 *      The solution to this is making sure you use all the appropriate options
 *      to the system utility that tells you about entries (active AND defunct)
 *      in the process table.  Once you see that the defunct copy is still
 *      in the table, then you can use a "kill with extreme prejudice" command
 *      (if using UNIX: kill -9 <pid>) to totally remove it from the process list.
 *
 *      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 invoke the program with the stdout redirected to /dev/null and
 *      run it in the background:
 *          xml_sock_demo 5000 >& /dev/null &
 *
 *      NET PACKING FORMAT:  The storage/usage of the IP Number and the Port
 *      are also platform-dependent.  On your site's platform, you may need to make
 *      calls to htonl() and htons() to handle these dependencies.
 *
 *      EMAIL ADDRESS CHANGES:  Also, don't forget to change the e-mail address
 *      that is hardwired into the e-mail command near the end
 *      of the e_mail_alert() routine from my address to your address(es)
 *      (assuming you still want this feature).
 *
 *      ERRORS:  All the "bad situations" within the operations of this demo program
 *      are marked with print statements with the prefix "ERR:" to the error
 *      messsage.  This prefix allows the user quickly search the logfile
 *      for instances of bad activity.
 *
 *  FILES:
 *      xml_sock_demo.log   // A logfile of all packets received & program actions.
 *                          // This file is created, opened, and written to
 *                          // by the xml_sock_demo program.  It provides a
 *                          // permanent record of what packets were received.
 *      e_me_xtmp           // A temp-file to produce the body of the e-mail.
 *
 *
 *  LIST OF ROUTINES:
 *      brokenpipe()        // Wrap things up if the socket connection breaks
 *      main()              // The main processing routine
 *      server()            // Set up the server end of a connection (ie the customer end)
 *      serr()              // Specialized error/status reporting routine
 *      chk_imalive()       // Check and report an absence of Imalive/any packets rcvd
 *      e_mail_alert()      // Send e-mail when no packets
 *
 *  COMPILATION:
 *      For LINUX platforms: 
 *          cc -DLINUXPC xml_sock_demo.c -lm -o xml_sock_demo
 *      or for non-LINUX platforms: 
 *          cc xml_sock_demo.c -lm -o xml_sock_demo
 *          It has been a while since this program was compiled and run
 *          on a non-LINUX platform, so buyer beware.
 *
 *  MISC NOTES:
 *      1) This file looks best with a tabstop=4 setting.
 *
 *      2) This code was purposefully written in a 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 Fortran, whatever).
 *      All the macro defines are directly included in this C source-code
 *      file (instead of using #include statements) so that _all_ the
 *      relavent code is in a _single_ file.  This makes public distribution
 *      and installation much easier.
 *
 *      3) Inside the e_mail_alert() routine there is a command to send an email
 *      to the "opertor(s)".  It is currently set up
 *      with the email addresses for the GCN Operators (myself).
 *      When you set this up for your own testing/operations,
 *      change this hardwired address to your own address(es).
 *
 *  AUTHOR:
 *      Scott Barthelmy         NASA-GSFC                   Code 661.0
 *
 *  MODS:
 *      Cloned from socket_demo.                                13Jun09
 *      Added explicit/forced server-resetting if 65sec timeout.26Jun09
 */


// Please note that most of these include_files are the ones
// for SunOS Version 4.1.3 1 and LINUX.
// On other machines with different operating systems (and even different
// versions of SunOS), these include_files may have different names.
// If, when you compile xml_sock_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 <sys/types.h>          // File typedefs header file
#include <sys/socket.h>         // Socket structure header file
#include <signal.h>
#include <unistd.h>
#include <math.h>
#ifndef LINUXPC
#include <sys/filio.h>
#endif
#include <string.h>             // For strcpy(), strcat(), etc
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <netdb.h>
#include <time.h>
#include <sys/time.h>
#include <errno.h>
#include <strings.h>
#include <stdlib.h>             // For the system() and exit() calls

char    *version = "xml_sock_demo     Ver: 1.01   26 Jun 09";

#define TRUE    1
#define FALSE   0



// A few global variables for the xml_sock_demo program:
int     inetsd;             // Socket descriptor
FILE    *lg;                // Logfile stream pointer
int     errno;              // Error return code number
time_t  tloc;               // Seconds since machine epoch

void    brokenpipe();       // Clean up after getting a brokenpipe signal
int     server();           // Set up the server end of a connection (ie the customer end)
int     serr();             // Specialized error/status reporting routine
void    chk_imalive();      // Check and report an absence of Imalive/any packets rcvd
void    e_mail_alert();     // Send e-mail when no packets


/*---------------------------------------------------------------------------*/
int
main(argc, argv)
int             argc;           // Number of cmdline arguments
char            *argv[];        // Pointers to all the cmdline arguments
{
int             port;           // Port number of the socket to connect to
int             bytes;          // Number of bytes read from the socket
char            hostname[64];   // Machine host name
char            xmlbuf[30000];  // The packet data buffer
int             i;              // General loop variable
struct sigvec   vec;            // Signal vector structure
time_t          nowtime;        // The current system time
time_t          last_nowtime;   // The time of the last packet received


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

port = atoi(argv[1]);       // Get the cmdline value for the port number

if((lg = fopen("xml_sock_demo.log", "a")) == NULL)  // Open for appending
    {
    printf("Failed to open logfile.  Exiting.\n");
    exit(2);
    }
fprintf(lg,"\n===================== New Session ========================\n");
fprintf(lg,"%s\n",version);
fprintf(lg,"port=%d\n",port);

// The following sets up the signal catcher that is used to detect when
// the socket has been 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");
    }

bzero((char *)xmlbuf,sizeof(xmlbuf));   // Clears the buffer
time(&last_nowtime);                    // Init this so it doesn't trip on the first loop

// Set up the server to allow/wait for the incoming connection from GCN:
//   hostname is not needed for an INET connection,
//   port number is defined/initialized above, and
//   AF_INET is for an inet connection.
// The return value is the socket descriptor.
inetsd = server(hostname, port, AF_INET);


// An infinite loop for reading the socket, 
// and handling the reconnection if there is a break.
// Also in this loop is your site/instrument-specific code (if you want).
while(1)
    {
    time(&nowtime);                 // Get the current system time
    if((nowtime - last_nowtime) > 80)
        {
        printf(    "WARN: It has been greater than 60sec since the last (=%ld).\n",
                                                    nowtime - last_nowtime);
        fprintf(lg,"WARN: It has been greater than 60sec since the last (=%ld).\n",
                                                    nowtime - last_nowtime);
        last_nowtime = nowtime;     // Reset it so we don't get a million error statements
        // Cause the program to init & re-establish the server-end of the connection:
        inetsd = -1;
        }


    if(inetsd <= 0)
        {
        // If the sock descriptor is zero or less, the socket connection has been
        // broken and the call to server() will try to re-establish the socket.
        inetsd = server(hostname, port, AF_INET);
        }
    else
        {
        // If socket is connected, read the socket for packet; if the socket
        // does not have a packet, continue to loop.
        if((bytes=read(inetsd,(char *)xmlbuf,sizeof(xmlbuf))) > 0)
            {
            printf("\nINFO: bytes=%d\n",bytes); //debug

            last_nowtime = nowtime;                 // Record the time we got this packet

            // Unlike the regular GCN socket method, do NOT echo the packet back to GCN.
            // There is no loop-back protocol for the xml_socket method.

            // The next 6 lines just print the xml messages to the screen and to the logfile.
            // You can do something else with them at this point (eg pass the buffer
            // to your XML_parser/go-look-at-this-target_action).
            i = 0;
            while(xmlbuf[i])
                {
                printf(    "%c",xmlbuf[i]);
                fprintf(lg,"%c",xmlbuf[i]);
                i++;
                }
            if(i != bytes)
                {
                printf(    "ERR: Num_written(=%d) not equal to num_read(=%d).\n",i,bytes);
                fprintf(lg,"ERR: Num_written(=%d) not equal to num_read(=%d).\n",i,bytes);
                }

            bzero((char *)xmlbuf,sizeof(xmlbuf));   // Clears the buffer (paranoia)
            }
        else if((bytes == 0) && (errno != EWOULDBLOCK))
            {                                       // The connection is broken
            printf(    "bytes==0 && errno!=EWOULDBLOCK (errno=%d).\n",errno);
            fprintf(lg,"bytes==0 && errno!=EWOULDBLOCK (errno=%d).\n",errno);
            brokenpipe();
            }
        }

    chk_imalive(0,nowtime);         // See if the 10-min time limit has been exceeded

    // 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
    }
}

/*---------------------------------------------------------------------------*/
void
brokenpipe()                   /* clean up after getting a brokenpipe signal */
{
printf(    "brokenpipe(): If this is the last line, then the shutdown() to GCN hung.\n");
fprintf(lg,"brokenpipe(): If this is the last line, then the shutdown() to GCN hung.\n");
if(shutdown(inetsd,2) == -1)
    {
    perror("brokenpipe(): shutdown(): ");
    printf(    "ERR: brokenpipe(): The shutdown did NOT work. errno=%d\n",errno);
    fprintf(lg,"ERR: brokenpipe(): The shutdown did NOT work. errno=%d\n",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(inetsd))
    {
    perror("brokenpipe(): close(): ");
    printf(    "ERR: close() problem in brokenpipe(), errno=%d\n",errno);
    fprintf(lg,"ERR: close() problem in brokenpipe(), errno=%d\n",errno);
    }
inetsd = -1;
}

/*--------------------------------------------------------------------------*/
int
server(hostname, port, type)
char                *hostname; // Machine host name
int                 port;      // Port number
unsigned short      type;      // Type of socket connection
{
int                 sock;      // The connected sock descriptor
int                 sd=-1;     // The offerred sock descriptor
int                 temp;      // Dummy variable
int                 saddrlen;  // Socket address length + 2
char                on=1;      // Flag for nonblocking I/O
struct sockaddr     saddr;     // Socket structure for UNIX
struct sockaddr     *psaddr;   // Pointer to sin
struct sockaddr_in  sin;       // Socket structure for inet


temp = 0;
// If the socket is for inet connection, then set up the sin structure.
if(type == AF_INET)
    {
    bzero((char *) &sin, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = htonl(INADDR_ANY);
#   ifdef LINUXPC
    sin.sin_port = htons(port);
#   else
    sin.sin_port = (unsigned short)port;
#   endif
    psaddr = (struct sockaddr *) &sin;
    saddrlen = sizeof(sin);
    }
else
    {
    // If the socket is for UNIX connection, then set up the saddr structure.
    saddr.sa_family = AF_UNIX;
    strcpy(saddr.sa_data, hostname);
    psaddr = &saddr;
    saddrlen = strlen(saddr.sa_data) + 2;
    }
 
printf("server(): type=%d  (int)type=%d\n",type,(int)type);  // Debug

// Initiate the socket connection.
if((sd = socket((int)type, SOCK_STREAM, IPPROTO_TCP)) < 0)
    {
    printf("server(): socket() errno=%d.\n",errno);
    return(serr(sd,"server(): socket."));
    }

// Bind the name to the socket.
if(bind(sd, psaddr, saddrlen) == -1)
    {
    printf("server(): bind() errno=%d.\n",errno);
    return(serr(sd,"server(): bind."));
    }

// Listen for the socket connection from the GCN machine.
if(listen(sd, 5))
    {
    printf("server(): listen() errno=%d.\n",errno);
    return(serr(sd,"server(): listen."));
    }

// Accept the socket connection from the GCN machine (the client).
if((sock = accept(sd, &saddr, &temp)) < 0)
    {
    printf("server(): accept() errno=%d.\n",errno);
    return(serr(sd,"server(): accept."));
    }
 
// Now that we have the 'sock' descriptor, we can close the 'sd' descriptor from socket().
if(close(sd))
    {
    printf("server(): close() errno=%d.\n",errno);
    // No real need to return() here, we have 'sock', so continue on.
    }

// Make the connection nonblocking I/O.
if(ioctl(sock, FIONBIO, &on) < 0)
    {
    printf("server(): ioctl() errno=%d.\n",errno);
    return(serr(sock,"server(): ioctl."));
    }

printf(    "INFO: server(): the server is up.");
fprintf(lg,"INFO: server(): the server is up.");
return(sock);
}

/*---------------------------------------------------------------------------*/
int
serr(fds, call)             /* special error reporting and socket connection */
int      fds;
char     *call;
{
if(fds > 0)
    if(close(fds))
        {
        perror("serr(): close(): ");
        printf("close() problem in serr(), errno=%d.\n",errno);
        }
printf(    "%s\n",call);
fprintf(lg,"%s\n",call);
return(-1);
}

/*---------------------------------------------------------------------------*/
// This technique of trying to "notice" that imalives/anything have not arrived
// for a long period of time works only some of the time.  It depends on what
// caused the interruption.  Some forms of broken socket connections hang
// in the "elseif" shutdown() system call, and so the program never actually
// gets to the point of calling this routine to check the time and
// send a message.
void
chk_imalive(which,ctloc)              /* CHecK for IMALIVE pkt arrival gaps */
int             which;              // Is this an imalive timestamp or delta check call
time_t          ctloc;              // Imalive timestamp or current time [sec since epoch]
{
static time_t   last_ctloc;         // Timestamp of last imalive received
static int      imalive_state = 0;  // State of the checking machine
char            buf[32];            // Tmp place for date string modifications
time_t          nowtime;            // The system machine time this instant
char            *ctime();           // Convert the sec-since-epoch to ASCII

switch(imalive_state)
    {
    case 0:
        // Do something ONLY when we get that first Imalive/any timestamp.
        if(which)                   // Ctloc is time of most recent imalive
            {
            last_ctloc = ctloc;
            imalive_state = 1;      // Go to testing deltas
            }
        break;
    case 1:
        // Now that we have an Imalive/any timestamp, check it against the
        // current time, and update the timestamp when new ones come in.
        if(which)                   // Ctloc is time of most recent imalive
            last_ctloc = ctloc;
        else if((ctloc - last_ctloc) > 600)
            {
            time(&nowtime);         // Get system time for mesg timestamp
            strcpy(buf,ctime((time_t *)&nowtime));
            buf[24] = '\0';         // Overwrite the newline with a null
            printf(    "ERR: %s : No packets for >600sec.\n",buf);
            fprintf(lg,"ERR: %s : No packets for >600sec.\n",buf);
            e_mail_alert(1);
            imalive_state = 2;
            }
        break;
    case 2:
        // Waiting for a resumption of imalives/any so we can go back to testing
        // and e-mailing if they should stop for a 2nd (n-th) time.
        if(which)                   // Ctloc is time of the resumed imalive
            {
            last_ctloc = ctloc;
            time(&nowtime);         // Get system time for mesg timestamp
            strcpy(buf,ctime((time_t *)&nowtime));
            buf[24] = '\0';         // Overwrite the newline with a null
            printf(    "OK: %s : Packets have resumed.\n",buf);
            fprintf(lg,"OK: %s : Packets have resumed.\n",buf);
            e_mail_alert(0);
            imalive_state = 1;      // Go back to testing
            }
        break;
    default:
        time(&nowtime);             // Get system time for mesg timestamp
        strcpy(buf,ctime((time_t *)&nowtime));
        buf[24] = '\0';             // Overwrite the newline with a null
        printf(    "ERR: %s : Bad imalive_state(=%d)\n",buf,imalive_state);
        fprintf(lg,"ERR: %s : Bad imalive_state(=%d)\n",buf,imalive_state);
        imalive_state = 0;          // Reset to the initial state
        break;
    }
}

/*---------------------------------------------------------------------------*/
void
e_mail_alert(which)             /* send an E_MAIL ALERT if no pkts in 600sec */
int     which;              // End or re-starting of imalives
{
int         rtn;            // Return value from the system() call
FILE        *etmp;          // The temp email scratch file stream pointer
time_t      nowtime;        // The Sun machine time this instant
char        *ctime();       // Convert the sec-since-epoch to ASCII
char        buf[32];        // Tmp place for date string modifications
static char cmd_str[256];   // Place to build the system() cmd string

time(&nowtime);             // Get the system time for the notice timestamp
strcpy(buf,ctime((time_t *)&nowtime));
buf[24] = '\0';             // Overwrite the newline with a null

if((etmp=fopen("e_me_xtmp", "w")) == NULL)
    {
    printf(    "ERR: %s : Can't open scratch e_mail_alert file.\n",buf);
    fprintf(lg,"ERR: %s : Can't open scratch e_mail_alert file.\n",buf);
    return;
    }

if(which)
    fprintf(etmp,"TITLE:        xml_sock_demo GCN NO XML PACKETS ALERT\n");
else
    fprintf(etmp,"TITLE:        xml_sock_demo GCN XML PACKETS RESUMED\n");
fprintf(etmp,"NOTICE_DATE:  %s \n", buf);

if(fclose(etmp))            // Close the email file so the filesys is updated
    {
    printf(    "ERR: %s : Problem closing scratch email_me file.\n",buf);
    fprintf(lg,"ERR: %s : Problem closing scratch email_me file.\n",buf);
    }

cmd_str[0] = 0;             // Clear string buffer before building new string
if(which)
    strcat(cmd_str,"mail -s GCN_NO_XML_PKT_ALERT ");
else
    strcat(cmd_str,"mail -s GCN_XML_PKT_RESUMED ");
// NOTICE TO GCN SOCKET SITES:  Please don't forget to change this
// e-mail address to your address(es).
strcat(cmd_str,"scott@lheamail");
strcat(cmd_str," < ");
strcat(cmd_str,"e_me_xtmp");
strcat(cmd_str," &");
printf(    "%s\n",cmd_str);
fprintf(lg,"%s\n",cmd_str);
rtn = system(cmd_str);
printf(    "%sSystem() rtn=%d.\n",rtn?"ERR: ":"",rtn);
fprintf(lg,"%sSystem() rtn=%d.\n",rtn?"ERR: ":"",rtn);
}
