/*
 * CSc 645 Programming Assignment #4 - Spring 1995.
 *
 * sender - send network data with error detection & recovery using 
 * the Alternating Bit Protocol.
 *
 * This is part 2 of the implementation, which uses signal processing to
 * identify when a timeout has occurred. Such timeouts take place when a
 * packet has been lost in transit between the sender and the receiver.
 *
 * Written by James Warhol.
 *
 * Compile sender with:   cc -o sender   sender.c   chksum.c common.c
 *
 * Compile receiver with: cc -o receiver receiver.c chksum.c common.c
 *
 * Execute with: execute sender receiver net    infile outfile
 *                  or
 *               execute sender receiver noerrs infile outfile
 *
 * Note that the routines from /Users/murphy/645/hw4/error.c have been
 * slightly modified and included in common.c.
 *
 */

#include <errno.h>			/* defines file open error codes */
#include <fcntl.h>			/* defines open flags */
#include <stdio.h>			/* standard file i/o definitions */
#include <sys/signal.h>			/* signal handling definitions */
#include <sys/time.h>			/* time processing definitions */

#include "defs.h"			/* defines network parameters */
#include "common.h"			/* defines common parameters */

#define	TIMEOUT_LIMIT	80000		/* microseconds until timeout occurs */

/* define variables */

packet msgbuf;				/* message packet */

int i, j;				/* scratch variables */
int blocks_read;			/* number of data blocks read so far */
int errno;				/* error number for i/o errors */
int ifn_id;				/* input file descriptor */
int sender_seq_no;			/* sequence number of current packet */
int st;					/* i/o system request return status */
int timeout;				/* TRUE if timeout has occurred */
struct itimerval timer;			/* timer control value */
int wc;					/* word count for read */

/* define function prototypes */

void initialize(int argc, char *argv[]); /* initialize sender program */
void on_timeout();			/* timeout event processor */
int send_packet();			/* send packet across network */
void turn_off_timer();			/* turn off timer signal */
void turn_on_timer();			/* turn on timer signal */

/* main program - sender */

void main(int argc, char *argv[])
{

   initialize(argc, argv);		/* initialize sender program */

   /* loop repeatedly reading DATASIZE bytes from the input file into
      the packet buffer; send packet across network */

   log("sender: beginning main loop."); 

   while ((wc=read(ifn_id, msgbuf.data, DATASIZE)) > 0) {
      nnlog("sender: %d data bytes read from input file; block #%d.", 
         wc, ++blocks_read);

      /* set data byte count in packet header and pad data bytes with spaces */

      msgbuf.mdr.length = (short) wc;	/* number of data bytes */
      for (i = wc; i < DATASIZE; i++) msgbuf.data[i] = ' ';

      if (send_packet() != TRUE)	/* if unable to send packet */
         error("sender: an uncorrectable data transmission error occurred.");
   }

   if (wc < 0) error("sender: unexpected read error in main.");
   log("sender: end of file encountered on input file.");

   /* close the input file and terminate sender */

   if (close(ifn_id) != 0) 
      error("sender: an error occurred while closing input file.");
   log("sender: input file closed; terminating sender.");

   exit(0);

}

/*
 * void initialize(int argc, char *argv[]);
 *
 * initialize initializes processing for the sender program.
 * Specifically, this means the input file name is fetched from
 * the command line, the file is opened, and the author's initials
 * (jrw) are placed in the packet header.
 *
 * The sender_sequence_no, blocks_read and timer values are also 
 * initialized. The TIMEOUT_LIMIT value is printed. initialize also 
 * pauses for 2 seconds to ensure communications pipes have been 
 * properly set up.
 *
 */

void initialize(int argc, char *argv[])
{

   /* make sure input file name is specified on command line */

   if (argc != 2) error("sender: you must specify an input file name.");

   /* open input file and check return status */

   slog("sender: opening input file %s.", argv[1]);
   ifn_id = open(argv[1], O_RDONLY, 0);
   if (ifn_id < 0) {
      if (errno == ENOENT) 
         error("sender: the input file does not exist.");
      else
         error("sender: an unexpected error occurred when opening "
            "the input file."); 
   }

   /* set initials in packet header */

   msgbuf.mdr.initials[0] = 'j';
   msgbuf.mdr.initials[1] = 'r';
   msgbuf.mdr.initials[2] = 'w';

   /* initialize sender sequence number and number of data blocks read count */

   sender_seq_no = 0;
   blocks_read = 0;

   /* initialize timeout flag & timer control values; identify function to
      be executed in the event of a SIGALRM (alarm clock) signal */

   timeout = FALSE;
   timer.it_interval.tv_sec = 0;
   timer.it_interval.tv_usec = 0;
   timer.it_value.tv_sec = 0;
   timer.it_value.tv_usec = 0;

   signal(SIGALRM, on_timeout);

   /* print out TIMEOUT_LIMIT value for posterity */

   nlog("sender: using timeout limit of %d microseconds.", TIMEOUT_LIMIT);

   /* pause 2 seconds to give execute utility time to set up I/O pipes */

   sleep(2);

   return;

}

/*
 * void on_timeout();
 *
 * on_timeout is called by the signal handler whenever a timeout has
 * occurred (if no ACK packet has been received within TIMEOUT_LIMIT 
 * microseconds). on_timeout may be called at any time during execution,
 * interrupting processing. Upon return from on_timeout, execution 
 * continues where it left off.
 *
 */

void on_timeout()
{
   timeout = TRUE;
   return;
}

/*
 * int send_packet();
 *
 * send_packet sends the current packet across the network after setting
 * the sequence number and calculating the Internet checksum.
 *
 * send_packet returns a value of TRUE if the packet was sent and 
 * received without errors. send_packet returns a value of FALSE if
 * no ackwledgement was ever received for the packet after attempting
 * to send it NUMTRIES times.
 *
 */

int send_packet()
{

   int tryloop;				/* loop counter for write attempts */

   packet ack_packet;			/* acknowledgement packet */

   msgbuf.mdr.seq_no = (short) sender_seq_no;	/* set seq. no. in header */

   /* calculate checksum and place in outgoing packet header */

   msgbuf.mdr.checksum = 0;       /* clear checksum initially */
   msgbuf.mdr.checksum = (short) in_cksum(&msgbuf,sizeof(msgbuf));

   /* send this data packet over the network and wait for a response. If
      no ACK packet has been received within TIMEOUT_LIMIT microseconds,
      resend the packet. Do this for a maximum of NUMTRIES times, after
      which a failure status is returned */

   for (tryloop = 0; tryloop < NUMTRIES; tryloop++) {

      /* write packet to network (via standard output) */

      if (tryloop == 0)
         nlog("sender: sending data packet (seq_no=%d).", msgbuf.mdr.seq_no);
      else
         nlog("sender: resending data packet (seq_no=%d).", msgbuf.mdr.seq_no);
      st = write(1, &msgbuf, BUFSIZE);
      if (st < 0) error("sender: unexpected write error in send_packet.");

      timeout = FALSE;				/* clear and restart timer */
      turn_on_timer();

      /* loop until valid ACK has arrived or timeout has occurred */

      for (;;) {
         if (timeout) {
            log("sender: timeout has occurred waiting for ACK from receiver.");
            break;			/* timeout; resend packet */
         }

         st = read(0, &ack_packet, sizeof(ack_packet));
         if (st == 0 || (st < 0 && errno == EWOULDBLOCK)) continue;

         if (st < 0 )		/* unexpected read error has occurred */
            error("sender: unexpected network read error.");

         /* something has been received across the network; turn timer off;
            nothing else is likely to arrive, and data packet will be resent */

         turn_off_timer();

         if (st != sizeof(ack_packet)) error("sender: incorrect ACK length.");

         /* validate that checksum and initials are correct; if not,
            treat it as if a timeout occurred; if ok, check ack_no */

         if (valid_packet(&ack_packet, "sender") == FALSE) break;

         /* if valid ACK but not for desired data packet, resend data */

         if (sender_seq_no == ack_packet.mdr.ack_no) {
            nlog("sender: ACK received for wrong seq_no (%d); packet lost.",
               ack_packet.mdr.ack_no);
            break;
         }

         /* ACK packet is valid and is for expected message - send is good */

         nnlog("sender: ACK (ack_no=%d) received; data packet (seq_no"
            "=%d) arrived ok.", ack_packet.mdr.ack_no, sender_seq_no);
         sender_seq_no = ack_packet.mdr.ack_no; /* update seq. no. */
         return TRUE;		/* packet sent succesfully */
      }
   }

   /* no correct data has been received; send has failed completely */

   nlog("sender: all attempts to send packet have failed (seq_no=%d).", 
      msgbuf.mdr.seq_no);
   return FALSE;			/* packet not sent properly */

}

/*
 * void turn_off_timer();
 *
 * turn_off_timer turns off the timer signal interrupt. This function is
 * called typically after an acknowledgement packet has been received,
 * thereby eliminating the possibility of a timeout.
 *
 */

void turn_off_timer()
{
   timer.it_value.tv_usec = 0;
   setitimer(ITIMER_REAL, &timer, 0);
   return;
}

/*
 * void turn_on_timer();
 *
 * turn_on_timer activates the alarm clock signal (SIGALRM) to expire in
 * TIMEOUT_LIMIT microseconds. If this time period has expired before
 * the timer has been turned off, function timeout() is called to
 * initiate packet send timeout processing.
 *
 */

void turn_on_timer()
{
   timer.it_value.tv_usec = TIMEOUT_LIMIT;
   setitimer(ITIMER_REAL, &timer, 0);
   return;
}
