/* **********************************************************************
 * *                                                                    *
 * *   NETWORK TRANSMISSION FOR TCPIP/SOCKET TRANSPORT                  *
 * *                                                                    *
 * **********************************************************************
 * File: libs/user/SocketIo.C
 *
 *                 COPYRIGHT (c) 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 <ctype.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#ifndef LINUX
#include <string.h>
#endif  /* #ifndef LINUX */
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>

#include "sigtypes.h"

#include "dsp_lib.h"
#include "user_lib.h"

#define SOCKET_VERBOSE (FALSE)

#define NO_SIZE (-1)
static void PrintDiagnostic
( const char *sPrefix_a,
  const char *pData1_a, 
  SINT4 iSize1_a,
  SINT4 iSize2_a );
/* ==============================================================
 * Reads one packet from the socket.  Returns buffer pointer,
 * and size.  Buffer must be freed.  Returns NULL on error.
 */
char *ReadSocketPacket
( int iSocket_a,
  SINT4 *pBufferSize_a,
  UINT1 lVerbose_a,
  UINT4 *pFromAddress_a,  /* From address returned in network byte order */
  SINT4 iTimeoutMS_a,     /* Timeout in milliseconds, 0=no timeout */
  UINT1 *pTimedOut_a      /* Flag to indicate that timeout happened (optional)*/
  )
{
  char sSize[10], *sBuffer, *sEnd ;
  int iRecvSize, iFromSize; SINT4 iSize, iBufferPos ;
  char sFrom[24], sMessage[80];
  struct pollfd pollList[1] ; int iCount;

  iFromSize = 24;
  if (pTimedOut_a) *pTimedOut_a = FALSE;

  /* Detect no response timeout */
  if (iTimeoutMS_a)
    {
      pollList[0].fd      = iSocket_a ;
      pollList[0].events  = POLLIN|POLLERR ;
      pollList[0].revents = 0 ;
      
    AGAIN1:
      iCount = poll( pollList, 1, iTimeoutMS_a ) ;
      if (iCount < 0)
	{
	  if (errno==EINTR) goto AGAIN1;
	  sig_signal( errno_to_message_va( errno, 1, "poll1()" ) );
	  *pBufferSize_a = 0 ;
	  *pFromAddress_a = 0;
	  return NULL ;      
	}
      if (iCount == 0)
	{
	  if (pTimedOut_a) *pTimedOut_a = TRUE;
	  *pBufferSize_a = 0 ;
	  *pFromAddress_a = 0;
	  return NULL ;      
	}
    }

  /* Read size */
  iRecvSize = recvfrom( iSocket_a, sSize, 8, 0, (struct sockaddr*)sFrom, &iFromSize ) ;
  if (iRecvSize < 0) sig_signal( errno_to_message_va( errno, 1, "recvfrom1()" ) );
  sSize[8] = 0 ;
  if( iRecvSize < 8 ) 
    {
      *pBufferSize_a = 0 ;
      *pFromAddress_a = 0;
      return NULL ;
    }
  
  iSize = strtol( sSize, &sEnd, 10 ) ;
  if (sEnd==sSize)
    {
      if (lVerbose_a)
	{
	  sprintf( sMessage, "Read1 Size parse '%s'.\n", sSize ) ;
	  sig_signal_va( USER_TEXTMSG, 1, sMessage );
	}
      *pBufferSize_a = 0 ;
      *pFromAddress_a = 0;
      return NULL ;
    }
  
  if (iSize <= 0)
    {
      if (lVerbose_a)
	{
	  sprintf( sMessage, "Read1 Invalid size %d.\n", iSize ) ;
	  sig_signal_va( USER_TEXTMSG, 1, sMessage );
	}
      *pBufferSize_a = 0 ;
      *pFromAddress_a = 0;
      return NULL ;
    }
  
  sBuffer = (char*)malloc( iSize ) ;
  if (!sBuffer)
    {
      perror( "malloc" ) ;
      *pBufferSize_a = 0 ;
      *pFromAddress_a = 0;
      return NULL ;
    }
  
  iBufferPos = 0 ;
  while( iBufferPos < iSize )
    {
      iFromSize = 24;

      /* Detect no response timeout */
      if (iTimeoutMS_a)
	{
	  pollList[0].fd      = iSocket_a ;
	  pollList[0].events  = POLLIN|POLLERR ;
	  pollList[0].revents = 0 ;
	  
	AGAIN2:
	  iCount = poll( pollList, 1, iTimeoutMS_a ) ;
	  if (iCount < 0)
	    {
	      if (errno==EINTR) goto AGAIN2;
	      sig_signal( errno_to_message_va( errno, 1, "poll2()" ) );
	      free( sBuffer ) ;
	      *pBufferSize_a = 0 ;
	      *pFromAddress_a = 0;
	      return NULL ;      
	    }
	  if (iCount == 0)
	    {
	      if (pTimedOut_a) *pTimedOut_a = TRUE;
	      free( sBuffer ) ;
	      *pBufferSize_a = 0 ;
	      *pFromAddress_a = 0;
	      return NULL ;      
	    }
	}
      
      iRecvSize = recvfrom( iSocket_a, &sBuffer[iBufferPos], iSize-iBufferPos, 0, 
			    (struct sockaddr*)sFrom, &iFromSize ) ;
      if( iRecvSize <= 0 ) 
	{
	  sig_signal( errno_to_message_va( errno, 1, "recvfrom2()" ) );
	  free( sBuffer ) ;
	  *pBufferSize_a = 0 ;
	  *pFromAddress_a = 0;
	  return NULL ;
	}
      iBufferPos += iRecvSize ;
    }
  if (lVerbose_a) PrintDiagnostic( "ReadSocketPacket", sBuffer, iSize, NO_SIZE );
  
  *pBufferSize_a=iSize ;
  switch ( ((struct sockaddr*)sFrom)->sa_family )
    {
#ifdef AF_INET6
    case AF_INET6:
      memcpy( pFromAddress_a, ((struct sockaddr_in6*)sFrom)->sin6_addr.s6_addr, 4 );
      break;
#endif  /* #ifdef AF_INET6 */
    default:
      *pFromAddress_a = ((struct sockaddr_in*)sFrom)->sin_addr.s_addr;
      break;

    }
  return sBuffer ;
  
}
/* =======================================
 * Read from the socket looking only for the Ack. 
 * status returned.
 */
MESSAGE ReadSocketAck
( int iSocket_a,
  UINT1 lVerbose_a,
  SINT4 iTimeoutMS_a )     /* Timeout in milliseconds, 0=no timeout */
{
  MESSAGE iStatus ; char *sBuffer ; SINT4 iSize ; UINT4 iFrom;
  sBuffer = ReadSocketPacket( iSocket_a, &iSize, FALSE, &iFrom, iTimeoutMS_a, NULL );

  /* Diagnostics printed here for more info about which call */
  if (lVerbose_a) PrintDiagnostic( "ReadSocketAck", sBuffer, iSize, NO_SIZE );

  iStatus = CheckSocketAck( sBuffer, iSize );
  if (sBuffer) free(sBuffer) ;
  return iStatus ;
}
/* ==================================================
 * Check the socket buffer to see if it is a valid "Ack" response.
 */
MESSAGE CheckSocketAck
( const char *sBuffer_a,
  SINT4 iSize_a )
{
  MESSAGE iStatus;
  if ((sBuffer_a)&&(iSize_a>=3))  /* Size needed for string compare later */
    {
      /* Now parse the error string.  Don't use strchr because the
       * buffer might not be null terminated.
       */
      if (0 == strncmp( sBuffer_a, "Ack", 3 ) )
	{
	  iStatus = SS_NORMAL ;
	}
      else
	{
	  const char *pData ;
	  pData = (char *)memchr( sBuffer_a, '|', iSize_a ) ;
	  if (pData)
	    {
	      pData++ ;
	      iStatus = iMessageMod_va( DSP_OPEN_ERROR, 1, pData ) ;
	    }
	  else
	    {
	      /* Buffer has no "|" */
	      iStatus = USER_DEVTIMEOUT ;
	    }
	}
    }
  else
    {
      /* No data read */
      iStatus = USER_DEVTIMEOUT ;
    }
  return iStatus;
}
/* =======================================
 * Write a block of data to the socket.
 */
MESSAGE WriteSocketData1
(int iSocket_a,
 const char *pData_a,
 SINT4 iSize_a,
 UINT1 lVerbose_a )
{
  MESSAGE iStatus ;
  int ierror ;
  char sSize[20] ;

  if (lVerbose_a) PrintDiagnostic( "WriteSocketData1", pData_a, iSize_a, NO_SIZE );

  sprintf( sSize, "%08d", iSize_a ) ;
  ierror = send( iSocket_a, sSize, 8, 0 ) ;
  if (ierror==-1) 
    {
      iStatus = errno_to_message_va( errno, 1, "WriteSocketData1()") ;
      return iStatus ;
    }

  ierror = send( iSocket_a, pData_a, iSize_a, 0 ) ;
  if (ierror==-1) 
    {
      iStatus = errno_to_message_va( errno, 1, "WriteSocketData1()") ;
      return iStatus ;
    }

  return SS_NORMAL ;
}

/* =========================================
 * Write a diagnostic string to stderr.
 */
static void PrintDiagnostic
( const char *sPrefix_a,
  const char *pData1_a, 
  SINT4 iSize1_a,
  SINT4 iSize2_a )
{
  char sMessage[100];
  SINT4 iIndex, iSize ;
  /* Print out the first characters */
  SINT4 iPrintSize = MIN( 50, iSize1_a ) ;

  if (iSize2_a==NO_SIZE)
    sprintf( sMessage, "%s %d bytes:", sPrefix_a, iSize1_a ) ;
  else
    sprintf( sMessage, "%s %d/%d bytes:", sPrefix_a, iSize1_a, iSize2_a ) ;
  iSize = strlen(sMessage);
  for (iIndex=0 ; iIndex<iPrintSize ; iIndex++)
    {
      char iChar=pData1_a[iIndex] ;
      /* All non-printable characters print as '.' */
      if (isprint(iChar))
	sMessage[iSize++] = iChar;
      else
	sMessage[iSize++] = '.';
    }
  sMessage[iSize++]=0;
  sig_signal_va( USER_TEXTMSG, 1, sMessage );
}
/* =======================================
 * Write a string to the socket.
 */
MESSAGE WriteSocketString
(int iSocket_a,
 const char *sString_a,
 UINT1 lVerbose_a )
{
  MESSAGE iStatus ;
  SINT4 iLength ;
  iLength = strlen(sString_a) ;

  /* Be sure to include the trailing null */
  iStatus = WriteSocketData1( iSocket_a, sString_a, iLength+1, lVerbose_a ) ;
  return iStatus ;
}
/* =======================================
 * Write 2 blocks of data to the socket.
 */
MESSAGE WriteSocketData2
(int iSocket_a,
 const char *pData1_a,
 SINT4 iSize1_a,
 const char *pData2_a,
 SINT4 iSize2_a,
 UINT1 lVerbose_a )
{
  MESSAGE iStatus ;
  int ierror ;
  char sSize[20] ;

  if (lVerbose_a) PrintDiagnostic( "WriteSocketData2", pData1_a, iSize1_a, iSize2_a );

  sprintf( sSize, "%08d", iSize1_a+iSize2_a ) ;
  ierror = send( iSocket_a, sSize, 8, 0 ) ;
  if (ierror==-1) 
    {
      iStatus = errno_to_message_va( errno, 1, "WriteSocketData2()") ;
      return iStatus ;
    }

  ierror = send( iSocket_a, pData1_a, iSize1_a, 0 ) ;
  if (ierror==-1) 
    {
      iStatus = errno_to_message_va( errno, 1, "WriteSocketData2()") ;
      return iStatus ;
    }

  ierror = send( iSocket_a, pData2_a, iSize2_a, 0 ) ;
  if (ierror==-1) 
    {
      iStatus = errno_to_message_va( errno, 1, "WriteSocketData2()") ;
      return iStatus ;
    }

  return SS_NORMAL ;
}
