/* **********************************************************************
 * *                                                                    *
 * *                 Routines to Help With Antenna I/O                  *
 * *                                                                    *
 * **********************************************************************
 * File: libs/antenna/ant_iosubs.c
 *
 *         COPYRIGHT (c) 1989, 1991, 1999, 2002, 2003, 2004 BY
 *         SIGMET INCORPORATED, WESTFORD MASSACHUSETTS, U.S.A.
 * 
 * THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND  COPIED
 * ONLY  IN  ACCORDANCE WITH  THE  TERMS  OF  SUCH  LICENSE  AND WITH THE
 * INCLUSION OF THE ABOVE COPYRIGHT NOTICE.  THIS SOFTWARE  OR  ANY OTHER
 * COPIES  THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY
 * OTHER PERSON.  NO TITLE TO AND OWNERSHIP OF  THE  SOFTWARE  IS  HEREBY
 * TRANSFERED.
 */
#ifdef HPUX
#include <arpa/nameser.h>   /* Defines BYTE_ORDER on HP */
#endif  /* #ifdef HPUX */

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

#ifndef LINUX
#include <string.h>
#endif  /* #ifndef LINUX */

#include <unistd.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <sys/poll.h>

#ifndef HPUX
#include <zlib.h>
#endif  /* #ifndef HPUX */

#define INCLUDE_STDIO_PROTOTYPES 1
#include "sigtypes.h"
#include "event_flags.h"
#include "user_lib.h"
#include "antenna_lib.h"
#include "antenna_int.h"
#include "setup.h"
#include "ant_global.h"

static SINT4 iSocketReadAndKeepalive
( int iFD_a, UINT1 *pReadStr_a, SINT4 imaxch_a ) ;

/* ------------------------------
 * Get more characters to process.
 * History:
 *
 * Entry Parameters
 *   iFD_a    : File descriptor for I/O
 *   str      : byte string which may contain some chars left over from last time
 *   iptr_a   : location in STR of next char to be processed by calling routine
 *   iend_a   : first invalid location in STR
 *
 * Exit Parameters
 *   str    : string with at least one more character added to it
 *   iptr_a : always set to 0
 *   iend_a : first invalid location in STR
 */
void get_more_chars
( int iFD_a, UINT1 str[], UINT4* iptr_a, UINT4* iend_a,
  SINT4 imaxch_a     /* Max # characters to add */
  )
{
  SINT4 icnt, ii ;

  /* If there are any characters left over from last time, move them
   * down to the start of the buffer if they are not there already.
   */
  if( *iptr_a > 0 )
    {
      if( *iptr_a < *iend_a )
	{
	  for( ii = *iptr_a; ii<*iend_a; ii++)
	    str[ii-*iptr_a] = str[ii];
            
	  *iend_a -= *iptr_a;
	}
      else
	{
	  *iend_a = 0;
	}
    
      *iptr_a = 0;
    }

  /* Non-negative file descriptor means normal serial line reads, or
   * reads from a remote socket.
   */
  if( iFD_a >= 0 ) 
    {
      
      switch( antGlobal_c->setup.iInterfaceType)
	{
	case ANTIFACE_ANTEXPORT:
	  icnt = iSocketReadAndKeepalive( iFD_a, &str[*iend_a], imaxch_a ) ;
	  *iend_a += icnt ;
	  break;
	  
	default:
	case ANTIFACE_SERIAL:
	  icnt = read( iFD_a, &str[*iend_a], imaxch_a ) ;
	  if( icnt <= 0 ) { sig_signal( errno_to_message(errno) ) ; exit(EXIT_FAILURE);}
	  if( icnt > 0 ) *iend_a += icnt ;
	  break;
	}
    }
  /* Otherwise, we are communicating with the simulator.
   */
  else 
    {
      SINT4 iUsed, iSize; UINT1 lSim;
      lSim = lAntSimulator();
      if (lSim)
	iUsed = bfifo_used( &antGlobal_c->vdat.ToSimulator );
      else
	iUsed = bfifo_used( &antGlobal_c->vdat.FromSimulator );
      while( iUsed <= 0) {
	if( lSim ) {
	  sig_microSleep( 50000 ) ;
	  iUsed = bfifo_used( &antGlobal_c->vdat.ToSimulator );
	}	else {
	  sig_microSleep( 20000 ) ;
	  iUsed = bfifo_used( &antGlobal_c->vdat.FromSimulator );
	}
      }
      iSize = MIN( iUsed, imaxch_a );
      if (lSim)
	icnt = bfifo_read_array( &antGlobal_c->vdat.ToSimulator, &str[*iend_a], iSize );
      else
	icnt = bfifo_read_array( &antGlobal_c->vdat.FromSimulator, &str[*iend_a], iSize );
      if (icnt > 0) *iend_a += icnt;
    }
}

/* ------------------------------
 * Checks a character packet to be sure that the last character is FF,
 * and all but the first have the MSB clear
 */
UINT2 lcheck_packet( UINT1 str_a[], SINT4 ilength_a )
{
  UINT1 lOkay = TRUE ;

  if( ilength_a <= 0 ) lOkay = FALSE ;
  else {
    if( str_a[ilength_a-1] != ANT_SYNC_ENDREC ) lOkay = FALSE ;
    else {
      SINT4 iptr ;
      for( iptr=1 ; iptr < (ilength_a - 1) ; iptr++ )
	if( 0 != (str_a[iptr] & 0x80) ) { lOkay = FALSE ; break ; }
    }
  }
  return( lOkay ) ;
}

/* ------------------------------
 * Checks a SA character packet to be sure that the first two
 * characters are '@8' and the last two characters are cr and lf.
 */
UINT2 lcheck_packet_sa
( UINT1 str_a[],
 SINT4 ilength_a )
{
  if (ilength_a<4) return FALSE;
  
  if (str_a[0] != '@') return FALSE;
  if (str_a[1] != '8') return FALSE;
  if (str_a[ilength_a-1] != '\n' ) return FALSE;
  if (str_a[ilength_a-2] != '\r' ) return FALSE;
  return TRUE;
}

/* ------------------------------
 * Cover function for write.
 */
size_t WriteInt
( int iFD_a, const UINT1 *pData_a, size_t iSize_a, const char *sType_a,
  UINT4 iDestination_a )
{
  if (iFD_a!=-1)
    {
      if( ANTIFACE_NETWORK == antGlobal_c->setup.iInterfaceType )
	return AntWriteNetwork( iFD_a, pData_a, iSize_a, sType_a, iDestination_a );
      else
	return write( iFD_a, pData_a, iSize_a );
    }
  else
    {
      SINT4 iFree;
      UINT1 lSim;
      lSim = lAntSimulator();
      if (lSim)
	iFree = bfifo_free( &antGlobal_c->vdat.FromSimulator );
      else
	iFree = bfifo_free( &antGlobal_c->vdat.ToSimulator );

      if (iFree >= iSize_a)
	{
	  if (lSim)
	    return bfifo_write_array( &antGlobal_c->vdat.FromSimulator, pData_a, iSize_a );
	  else
	    {
	      return bfifo_write_array( &antGlobal_c->vdat.ToSimulator, pData_a, iSize_a );
	    }
	}
      else
	{
	  return 0;
	}
    }
}

/* ------------------------------
 * Write the packet to the network with the appropriate header for an
 * antenna packet.
 */
SINT4 AntWriteNetwork
( int iFD_a, const UINT1 *pData_a, SINT4 iSize_a, const char *sType_a,
  UINT4 iDestination_a )  /* Destination address in network order */
{
  char sHeader[16]; struct iovec iov[2]; int iSize;

  /* Header consists of:
   *  4 char size
   *  8 char type string
   *  4 byte destination, network order
   */
  memset( sHeader, 0, sizeof(sHeader) );
  sprintf( sHeader, "%04d", iSize_a );
  strncpy( sHeader+4, sType_a, 8 );
  memcpy(  sHeader+12, &iDestination_a, 4 );
  iov[0].iov_base = sHeader;
  iov[0].iov_len = 16;
  iov[1].iov_base = (UINT1*)pData_a;
  iov[1].iov_len = iSize_a;
  iSize = writev( iFD_a, iov, 2 );

  if (iSize==(iSize_a+16)) return iSize_a;
  else                     return 0;
}

/* ------------------------------
 * Extract the time stamp from an antenna record.
 */
UINT4 iParseTimeStamp( const UINT1 *str_a, UINT2 iformat_a )
{
  UINT4 iStamp = 0 ;
  switch( iformat_a ) {
  case ANTFMT_RCV03:
    iStamp = (((UINT4)str_a[31]) << 7) | (UINT4)str_a[30] ;
    break ;
  case ANTFMT_RCV02:
  case ANTFMT_RCV04:
  case ANTFMT_RCV05:
    iStamp = (((UINT4)str_a[14]) << 7) | (UINT4)str_a[13] ;
    break ;
  }
  return( iStamp ) ;
}

/* ------------------------------
 * Open a socket connection to AntExport running on a remote antenna
 * library.
 */
MESSAGE iAntOpenSocket( int *pFD_a )
{
  MESSAGE istatus = SS_NORMAL ;
  int ierror, iflag, ifamily, iaddrsize ;
  struct sockaddr *pservaddr ;
  struct hostent *hostptr ;
  struct in_addr *paddress ;

  ifamily = AF_INET ;
  /* Convert the server host name to an internet address.  Either
   * hostname or IP address can be specified.
   */
  hostptr = gethostbyname( (char *)antGlobal_c->setup.sAntExportHost ) ;
  if( hostptr != NULL ) {
    struct sockaddr_in servaddr_in ;  

    memset( &servaddr_in, 0, sizeof(servaddr_in)) ;
    servaddr_in.sin_family = ifamily ;
    paddress = (struct in_addr *)hostptr->h_addr_list[0] ;
    servaddr_in.sin_addr = *paddress ;
    servaddr_in.sin_port = htons( antGlobal_c->setup.iAntExportPortNum ) ;
      
    pservaddr = (struct sockaddr *)(void *)&servaddr_in ;
    iaddrsize = sizeof( servaddr_in ) ;

  } else {
    return iMessageMod_va( USER_HOST_RESOLVE, 1, antGlobal_c->setup.sAntExportHost ) ;
  }

  *pFD_a = socket( ifamily, SOCK_STREAM, 0 ) ;

  if( *pFD_a == (UINT4)-1 ) {
    istatus = errno_to_message_va( errno, 1, "iAntOpenSocket()" ) ;

  } else {
    /* We now have a valid opened socket, it must be closed if we exit
     * with an error.  Set the TCP_NODELAY option, if internet.
     */
    if (ifamily == AF_INET) {
      iflag = TRUE ;
      ierror = setsockopt( *pFD_a, IPPROTO_TCP, TCP_NODELAY,
			   (char *)&iflag, sizeof(iflag) ) ;
      if (ierror < 0)
	sig_signal( errno_to_message_va( errno, 1, "iAntOpenSocket()" ) ) ;
    }

    ierror = connect( *pFD_a, pservaddr, iaddrsize ) ;

    if( ierror >= 0 ) {
      char *sBuffer; SINT4 iSize ; UINT4 iFrom;
      /* Now read to see if AntExport was reachable, and whether it
       * powered up the radar-side antenna library successfully.
       */
      sBuffer = ReadSocketPacket( *pFD_a, &iSize, FALSE, &iFrom, 0, NULL );
      if( iSize < 0 )
	{
	  istatus = errno_to_message_va( errno, 1, "iAntOpenSocket()" ) ;
	}
      else if (iSize==0)
	{
	  istatus = ANT_REMOTEREJECT ;
	}
      else
	{
	  if(      0 == memcmp( sBuffer, "Ack", 4 ) ) 
	    istatus = SS_NORMAL ;
	  else if( 0 == memcmp( sBuffer, "Nak", 4 ) ) 
	    istatus = ANT_REMOTEPWRUP ;
	  else
	    istatus = ANT_REMOTEPROTO ;
	}
      free(sBuffer);

    } else {
      MESSAGE istatus2 ; int ierrno ;
      ierrno = errno ;
      istatus2 = errno_to_message_va( errno, 1, "iAntOpenSocket()" ) ;
      switch(ierrno) {
      case ENOENT:  /* Happens with AF_UNIX before bind call creates file */
      case ECONNREFUSED:
	/* Server is not running on the other machine */
	istatus = iMessageMod_va( USER_HOST_NOLISTEN, 1,
				  antGlobal_c->setup.sAntExportHost ) ;
	break ;

      case ETIMEDOUT:
      case ENETUNREACH:
      case ENETDOWN:
	/* Network cannot get to the other machine */
	istatus = iMessageMod_va( USER_HOST_UNREACHABLE, 1,
				  antGlobal_c->setup.sAntExportHost ) ;
	break ;

      default:
	istatus = istatus2 ;
	break ;
      }
    }
    /* Close the socket, if there was an error before returning.
     */
    if( istatus != SS_NORMAL ) {
      ierror = close( *pFD_a ) ;
      if (ierror == -1)
	sig_signal( errno_to_message_va( errno, 1, "iAntOpenSocket()" ) ) ;
    }
  }
  return( istatus ) ;
}

/* ------------------------------
 * Routine to handle socket read and write traffic within a single
 * process.  We assume that we're invoked frequently by a top level
 * caller that is interested in the read data.
 */
#define POLLWAKEUPMS  200	/* Polling wakeup interval */
static SINT4 iSocketReadAndKeepalive
( int iFD_a, UINT1 *pReadStr_a, SINT4 iMaxCh_a )
{
  static  char *sBuffer=NULL;
  static SINT4 iBufferSize=0;
  static SINT4 iBufferOffset=0;
  
  SINT4 iUpdateLen, iReadLen ;
  char *pUpdate = NULL ;

  /* If there is anything queued up from a previous socket read,
   * return it right away.
   */
  if (iBufferSize>0)
    {
      iReadLen = iBufferSize-iBufferOffset;
      if (iReadLen>iMaxCh_a)
	{
	  iReadLen = iMaxCh_a;
	  memcpy( pReadStr_a, sBuffer+iBufferOffset, iReadLen );
	  iBufferOffset += iMaxCh_a;
	}
      else
	{
	  memcpy( pReadStr_a, sBuffer+iBufferOffset, iReadLen );
	  free( sBuffer );
	  sBuffer = NULL; iBufferSize=0;
	}
      return iReadLen;
    }

  for(;;) {
    struct pollfd pollList[1] ; int iPollVal ;

    /* Setup default polling request for read input available.
     */
    pollList[0].fd      = iFD_a ;
    pollList[0].events  = POLLIN ;
    pollList[0].revents = 0 ;

    /* If we do not have anything yet to send, then check memory
     * to see if any new updates are ready.  Add a poll output check
     * if we have something ready to go.
     */
    if( pUpdate == NULL ) 
      iUpdateLen = iAntGetUpdatePackets( ANTGET_RCV_MASK, ANTGET_RETURN, &pUpdate ) ;
    
    if ( pUpdate ) pollList[0].events |= POLLOUT ;

    /* Poll the socket to determine if we can read or write.
     */
    iPollVal = poll( pollList, 1, POLLWAKEUPMS ) ;

    if( iPollVal < 0 ) {
      if( errno != EINTR ) {
	sig_signal( errno_to_message_va( errno, 1, "iSocketReadAndKeepalive()" ) ) ;
	exit( EXIT_FAILURE ) ;
      }
    } else if( iPollVal == 1 ) {
      /* One returned events structure has been filled in for us.
       * Check the bits to determine what we can do.
       */
      if( pollList[0].revents & ( POLLHUP | POLLERR) ) {
	/* Errors (or closes) exit the process for good.
	 */
	exit( EXIT_FAILURE ) ;
      }
      else if( pollList[0].revents & POLLIN ) 
	{
	  UINT4 iFrom;
	  /* Data is available to be read from the remote antenna library.
	   */
	  sBuffer = ReadSocketPacket( iFD_a, &iBufferSize, FALSE, &iFrom, 0, NULL );
	  if( iBufferSize <= 0 ) exit( EXIT_FAILURE ) ; /*Socket closed at other end*/

	  /* Clip read size to the buffer, and store the remainder for next call */
	  if (iBufferSize>iMaxCh_a)
	    {
	      iReadLen = iMaxCh_a;
	      memcpy( pReadStr_a, sBuffer, iReadLen );
	      iBufferOffset = iMaxCh_a;
	    }
	  else
	    {
	      iReadLen = iBufferSize;
	      memcpy( pReadStr_a, sBuffer, iReadLen );
	      free( sBuffer );
	      sBuffer = NULL; iBufferSize=0;
	    }

	  break ;
	}
      else if( pollList[0].revents & POLLOUT )
	{
	  /* Data can be written to the remote antenna library.  Note
	   * that we only get here when we do, indeed, have something
	   * ready to write.
	   */
	  MESSAGE iStatus;
	  iStatus = WriteSocketData1( iFD_a, pUpdate, iUpdateLen, FALSE );
	  if (iStatus!=SS_NORMAL)
	    {
	      sig_signal( iStatus ) ; exit( EXIT_FAILURE ) ;
	    }
	free( pUpdate ) ; pUpdate = NULL ;
      }
    }
  }
  return( iReadLen ) ;
}

/* ------------------------------
 * Write a text string to the common log file.  Prefix the current
 * time and other information to help identify who made this entry.
 * This routine may be called from client applications outside the
 * antenna driver to insert log messages where needed/desired.
 */
void antWriteLog( const char *sLogText_a )
{
  if( antGlobal_c->setup.lLogToFile ) {
    char sLogLine[ ANTLOGCOL_MAX ] ; struct ymds_time Ymds ;
    SINT4 iLength = antFormatLogEntry( sLogLine, sLogText_a, &Ymds ) ;

    lock_sem( ANTENNA_LOGFILE_SEM ) ;
    bfifo_write_array( &antGlobal_c->vdat.LogFIFO, (UINT1 *)sLogLine, iLength ) ;
    antGlobal_c->vdat.LogYMDS = Ymds ;
    unlock_sem( ANTENNA_LOGFILE_SEM ) ;

    sig_setef( ANTENNA_LOGFILE_EF ) ;
  }
}

/* Support routine to format a single line of text for a log file
 * entry.  Optionally return the current time as it was assigned to
 * this text.
 */
SINT4 antFormatLogEntry
( char sLogLine_a[ ANTLOGCOL_MAX ], const char *sLogText_a,
  volatile struct ymds_time *pLineTime_a )
{
  char sCleanText[ ANTLOGCOL_MAX ], sTimeBuf[10] ;
  SINT4 iLength ; struct ymds_time Ymds ;

  /* Get the current time so that we can fill in the prefix, then
   * clean any control codes from the user's text and format the
   * complete log line.
   */
  GetTimeYmds( &Ymds, TIME_FLG_UTC ) ;
  shhmmss_r( Ymds.isec, sTimeBuf ) ;

  strncpy_filter( sCleanText, sLogText_a, ANTLOGCOL_MAX ) ;

  iLength = snprintf
    ( sLogLine_a, ANTLOGCOL_MAX, "%-4s %2.2d-%s-%2.2d %s.%03d %s\n",
      sAntClientType( antClient_c.iClientType ),
      Ymds.iday, sMonth3(Ymds.imon), (Ymds.iyear % 100),
      sTimeBuf, MS_FROM_MILLS(Ymds.imills), 
      sCleanText ) ;

#if (ANTLOGCOL_YMDS != 5) || (ANTLOGCOL_TEXT != 28)
#error Please manually maintain error log columns
#endif 

  /* Return length of the line, and optionally the time that was
   * assigned.
   */
  if( pLineTime_a ) *pLineTime_a = Ymds ;
  return( iLength ) ;
}

/* ------------------------------
 * Return the time of the most recent entry in the log file.
 */
void antGetLatestLogTime( volatile struct ymds_time *pTime_a )
{
  lock_sem( ANTENNA_LOGFILE_SEM ) ;       /* Need to lock so we don't */
  *pTime_a = antGlobal_c->vdat.LogYMDS ;  /*   see a partially written */
  unlock_sem( ANTENNA_LOGFILE_SEM ) ;     /*   time structure. */
}

/* ------------------------------
 * Routine to log an array of bytes in human readable format.  This is
 * typically used to make a record of binary I/O packets.
 */
void antWriteLogBinary
( const char *sPrefix_a, const UINT1 *str_a, SINT4 iLength_a )
{
  if( antGlobal_c->setup.lLogToFile ) {
    char sText[ ANTLOGCOL_MAX ] ; SINT4 ii ;
    char *sPtr, *sEnd = &sText[ ANTLOGCOL_MAX-1 ] ;

    strncpy( sText, sPrefix_a, ANTLOGCOL_MAX ) ; *sEnd = '\0' ;
    sPtr = &sText[ strlen(sText) ] ;
    for( ii=0 ; (ii < iLength_a) && (sPtr < (sEnd-3)) ; ii++ ) {
      sprintf( sPtr, "%2.2X", str_a[ii] ) ; sPtr += 2 ;
    }
    antWriteLog( sText ) ;
  }
}

/* ------------------------------
 * Generic signal handler for antenna library processes writes errors
 * to the log queue.
 */
MESSAGE antSignalHandler
( MESSAGE iStatus_a, UINT4 iCount_a, const UINT4 iSigArgs_a[] )
{
  char sMessage[ MESSAGE_SIZE ] ;
  message_to_text_r( iStatus_a, iCount_a, iSigArgs_a, sMessage );
  antWriteLog( sMessage ) ;
  return( SS_NORMAL ) ;
}
