/*
 *   Filename:     fragment.c
 *
 *   Description:  Functions related to fragmenting data.
 *                 Care was taken in this package to place NO restrictions
 *                 on the fragment size limit or the message size limit
 *                 (other than what system memory can fit).  Any halfword
 *                 or fullword restrictions must be implemented and
 *                 enforced by the caller.
 *                 
 *                 Written to compile under K&R ANSI-C
 *
 *
 *   Contents:
 *     init_Fragmentation       Initializes fragmentation
 *     send_Whole               Sends messages whole and entire
 *     send_Fragments           Sends messages as fragments as two vectors
 *     count_Fragments          Returns fragment count for a message
 *
 *
 *====================================================================
 */


#include <stdio.h>
#include <errno.h>

#include <rpc/rpc.h>
#include <sys/socket.h>
#ifndef LINUX
#include <unistd.h>
#endif  /* #ifndef LINUX */

#include "frag_pac.h"

/* Fragmentation declarations */
static FILE *errfp = stderr;
static int frag_bytes = DEF_FRAGMENTATION_BYTES;
static int frag_data_bytes = DEF_FRAGMENTATION_BYTES - SIZEOF_FRAG_HDR;
extern FILE *enet_file, *error_file;

int init_Fragmentation( FILE *fptr, int f_bytes );
static int count_Fragments( int frag_b, int msg_b );
int send_Fragments
( int out_socket, unsigned short frag_options, unsigned char *mptr, int mbytes );
int send_Whole( int out_socket, unsigned char *mptr, int mbytes );

/* NOTE: The PRINT_ERRORS preprocessor variable was established to
 * ensure that realtime software running on the Harris of the TDWR
 * would never print error messages that could fill up a log file.
 * If software is to run "quietly", the PRINT_ERRORS flag must be
 * undefined.
 */


/*
 *
 *   Function    : init_Fragmentation
 *
 *   Description : Initializes fragmentation parameters of this package.
 *                 This should be called once or whenever the max expected
 *                 message size or fragmentation length limit changes.
 *                 If this is not called, the software will make assumptions
 *                 on fragmentation lengths with default (or the last) values.
 *
 *   Imports     : fptr, f_bytes
 *
 *   Exports     : 
 *
 *   Returns     : 0 if no errors, < 0 on errors
 *
 */

int init_Fragmentation( FILE *fptr, int f_bytes )
{

  /* Save the error pointer */
  if(fptr != (FILE *)NULL) errfp = fptr;
  
  /* Make sure that at least some data will fit beyond fragment header */
  if( (f_bytes>(int)SIZEOF_FRAG_HDR) && (f_bytes<=(int)MAX_FRAG_BYTES) )
    {
      frag_bytes = f_bytes;
      frag_data_bytes = f_bytes - SIZEOF_FRAG_HDR;
    }
  else
    {
#ifdef PRINT_ERRORS
      fprintf(errfp,"(init_Fragmentation) Requested fragment length, %d bytes, is out of range\n", f_bytes);
      fflush( errfp );
#endif
      return( -1 );
    }
  
  return( 0 );
}


/*
 *
 *   Function    : send_Whole
 *
 *   Description : Sends a message intact, to a socket
 *
 *   Imports     : out_socket, mptr, mbytes
 *
 *   Exports     : 
 *
 *   Returns     : <0 error; otherwise, number of bytes sent
 *
 */
int send_Whole( int out_socket, unsigned char *mptr, int mbytes )
{
	int msg_bytes_sent = 0;

	/* Send the data and handle returned status */
	if( (msg_bytes_sent=write( out_socket, mptr, mbytes)) < 0 )
	{
		/* Be tolerant of nonexistent clients to given them time to startup and bind */
		if( errno!=ECONNREFUSED )
		{
#ifdef PRINT_ERRORS
			fprintf(errfp,"(send_Whole) Bad status sending %d bytes, errno=%d\n",
				mbytes, errno);
			fflush( errfp );
#endif
			return( -1 );
		}
	}

	/* Handle unexpected byte transfers */
	else if( msg_bytes_sent != mbytes )
	{
#ifdef PRINT_ERRORS
		fprintf(errfp,"(send_Whole) Tried sending %d bytes, only sent %d\n",
			mbytes, msg_bytes_sent );
		fflush( errfp );
#endif
	}

	return( msg_bytes_sent );
}


/*
 *
 *   Function    : send_Fragments
 *
 *   Description : Sends a message as vectors, to a socket
 *
 *   Imports     : out_socket, frag_options, mptr, mbytes
 *
 *   Exports     : 
 *
 *   Returns     : <0 error; otherwise, number of bytes sent
 *
 */
int send_Fragments
( int out_socket, unsigned short frag_options, unsigned char *mptr, int mbytes )
{

  static FRAG_HDR fhdr = { 0L, 0, 0, 0, 0, 0, NULL_OPTION };
  static struct iovec iov[] = { { (caddr_t)&fhdr, SIZEOF_FRAG_HDR }, { 0, 0} };
  int msg_bytes_sent = 0;
  int bytes_remaining = mbytes;
  int data_bytes;
  int writev_bytes;
  
#define IOV_CNT ( sizeof(iov)/sizeof(struct iovec) )
  /* Fill the initial fragmentation header, these will not change among fragments */
  if( (fhdr.frag_count=count_Fragments( frag_bytes, mbytes)) <= 0 )
    {
#ifdef PRINT_ERRORS
      fprintf(errfp,"(send_Fragments) Bad argument(s), f_bytes=%d, m_bytes=%d\n", frag_bytes, mbytes);
      fflush( errfp );
#endif
      return( -1 );
    }
  fhdr.msg_bytes  = mbytes;
  fhdr.frag_nmbr  = 0;
  fhdr.options    = frag_options;
  
  /* Loop through the fragments */
  while( bytes_remaining > 0 )
    {
      /* Compute the pointer and length of the next fragment */
      data_bytes = fhdr.data_bytes = iov[1].iov_len =
	bytes_remaining>frag_data_bytes ? frag_data_bytes : bytes_remaining;
      
      /* Update the fragment header members */
      fhdr.frag_seqn++;
      fhdr.frag_nmbr++;
      fhdr.frag_offset = msg_bytes_sent;
      
      /* Define the data vector pointer */
      iov[1].iov_base = (caddr_t)&mptr[msg_bytes_sent];
      
      /* Send the fragment header and data fragment as vectors */
      if( (writev_bytes=writev( out_socket, iov, IOV_CNT )) < 0 )
	{
	  /* Be tolerant of nonexistent clients giving them time to startup and bind */
	  if( errno!=ECONNREFUSED )
	    {
#ifdef PRINT_ERRORS
	      fprintf(errfp,"(send_Fragments) Bad status sending fragment %d (%d+%d bytes), errno=%d\n",
		      fhdr.frag_nmbr, iov[0].iov_len, iov[1].iov_len, errno);
	      fflush( errfp );
#endif
	      return( -2 );
	    }
	}
      else if( writev_bytes != (int)(iov[0].iov_len+iov[1].iov_len))
	{
#ifdef PRINT_ERRORS
	  fprintf(errfp,"(send_Fragments) Tried sending fragment %d (%d+%d bytes), only sent %d\n",
		  fhdr.frag_nmbr, iov[0].iov_len, iov[1].iov_len, writev_bytes);
	  fflush( errfp );
#endif
	  msg_bytes_sent = (writev_bytes>(int)iov[0].iov_len) ? 
	    msg_bytes_sent+writev_bytes-iov[0].iov_len : msg_bytes_sent;
	  break;
	}
      /* Update the running counters */
      msg_bytes_sent += data_bytes;
      bytes_remaining -= data_bytes;
    }
  
#undef IOV_CNT
  
  return( msg_bytes_sent );
}


/*
 *
 *   Function    : count_Fragments
 *
 *   Description : Returns the fragment count associated with a given
 *                 fragment size and message data byte count.
 *
 *   Imports     : frag_b, msg_b
 *
 *   Exports     : 
 *
 *   Returns     : computed fragment count, consider 0 to be an error
 *
 */

static int count_Fragments( int frag_b, int msg_b )
{
  int fragments = 0;
  
  /* Check fragment and data length limits (bytes) */
  if( msg_b > 0 && msg_b <= MAX_MSG_BYTES &&
      (frag_b > (int)SIZEOF_FRAG_HDR) && frag_b <= MAX_FRAG_BYTES )
    {
      /* Also reject too many fragments */
      if( (fragments=(msg_b+frag_b-SIZEOF_FRAG_HDR-1) / (frag_b-SIZEOF_FRAG_HDR)) > MAX_FRAGMENTS)
	fragments = 0;
    }
  
  return( fragments );
}
