/* **********************************************************************
 * *                                                                    *
 * *   NETWORK TRANSMISSION FOR TCPIP/SOCKET TRANSPORT                  *
 * *                                                                    *
 * **********************************************************************
 * File: libs/dsp/OpenSocket.C
 *
 *              COPYRIGHT (c) 2002, 2003, 2004, 2006 BY
 *            VAISALA INC., 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 <fcntl.h>
#include <netdb.h>
#include <signal.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>
#ifndef HPUX
#include <zlib.h>
#endif  /* #ifndef HPUX */

#include "sigtypes.h"
#include "dsp.h"
#include "setup.h"
#include "sigversion.h"

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

#include "dsp_def.h"

/* Store socket for OOB I/O */
static int iSocket_c;

static void sigUrgHandler
( int isignal_a );

/* =======================================================
 * Opens the socket connection to a server DSP.  If the port
 * is busy, it will fail, and not try again.  Returns the most
 * important error.  Will optionally signal if additional info
 * might be helpful.
 */
MESSAGE DspOpenSocket
(Dsp *pDsp_a,
 struct dsp_refl_cal *pZcal_a )  /* Returns the remote zcal info */
{
  MESSAGE istatus ;
  int ierror, iflag, ifamily, iaddrsize ;
  struct sockaddr_in servaddr_in ;
  struct sockaddr *pservaddr ;
  struct hostent *hostptr ;
  struct in_addr *paddress ;

  /* Zero cal array if not used */
  memset( pZcal_a, 0, sizeof(*pZcal_a) );

  ifamily = AF_INET ;
  /* Convert the server host name to an internet address.  Either hostname
   * or IP address can be specified.
   */
  hostptr = gethostbyname(dspdrv_setup_c.sdevice) ;
  if (hostptr != NULL)
    {
      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(dspdrv_setup_c.iPortNumber) ;
      
      pservaddr = (struct sockaddr *)(void *)&servaddr_in ;
      iaddrsize = sizeof( servaddr_in ) ;
    }
  else
    {
      return iMessageMod_va( USER_HOST_RESOLVE, 1, dspdrv_setup_c.sdevice ) ;
    }

  pDsp_a->idsplu = socket(ifamily,SOCK_STREAM,0) ;
  if (pDsp_a->idsplu != (UINT4)-1)
    {
      /* We now have a valid opened socket, it must be closed if we exit.
       * Set the TCP_NODELAY option, if internet.
       */
      if (ifamily == AF_INET)
	{
	  iflag = TRUE ;
	  ierror = setsockopt( pDsp_a->idsplu, IPPROTO_TCP, TCP_NODELAY,
			       (char *)&iflag, sizeof(iflag) ) ;
	  if (ierror < 0)
	    sig_signal( errno_to_message_va( errno, 1, "setsockopt()") ) ;
	}

      ierror = connect(pDsp_a->idsplu, pservaddr, iaddrsize) ;
      if (ierror >= 0)
	{
	  char sBuffer[160]; SINT4 iSize;
	  char *sPair, *sPairState ;
	  /* Now read to see if there is any error in DspExport.
	   */
	  iSize = ReadSocket( pDsp_a->idsplu, sBuffer, 159, &istatus ) ;
	  if (istatus!=SS_NORMAL) goto ERROR;

	  sBuffer[iSize]=0 ;  /* Make sure it is null terminated */
	  lServerCompressed_c=FALSE ;

	  /* Process the string for name/value pairs */
	  sPair = strtok_r( sBuffer, ",", &sPairState ) ;
	  while( sPair )
	    {
	      char *sName, *sNameState, *sValue ;
	      sName  = strtok_r( sPair, "=", &sNameState ) ;
	      sValue = strtok_r( NULL, "=", &sNameState ) ;
	      if (sName&&sValue)
		{
		  /* Check to see if the server supports compression */
		  if ( 0 == strcmp( sName, "CanCompress" ) )
		    {
#ifndef HPUX          /* HP cannot do the compression */
		      char *pEnd ;
		      lServerCompressed_c = (0 != strtol( sValue, &pEnd, 10 )) ;
#endif  /* #ifndef HPUX */
		    }
		  else if ( 0 == strcmp( sName, "Version" ) )
		    {
		      char *pEnd ;
		      FLT8 fVersion;
		      fVersion = strtod( sValue, &pEnd ) ;
		      /* Skip version check if string not found */
		      if (pEnd==sValue) fVersion = 8.05;
		      if (fVersion<8.04)  /* Change to 8.04 when we ship */
			{
			  char sIden[80] ;
			  
			  sprintf( sIden, "Wanted at least 8.05 Found:%s", sValue ) ;
			  istatus = iMessageMod_va( DSP_VERSION_MISMATCH, 1, sIden ) ;
			  sig_signal( istatus);
			  goto ERROR ;
			}
		    }
		}
	      sPair = strtok_r( NULL, ",", &sPairState ) ;
	    }   /* while( sPair ) */
	  
	  /* Write connection info to the server */
	  strcpy( sBuffer, "INFO|" ) ;
	  if (BYTE_ORDER==LITTLE_ENDIAN)
	    strcat( sBuffer, "ByteOrder=LittleEndian" ) ;
	  else
	    strcat( sBuffer, "ByteOrder=BigEndian" ) ;
	  if (lServerCompressed_c)
	    strcat( sBuffer, ",WillCompress=1" ) ;
	  else
	    strcat( sBuffer, ",WillCompress=0" ) ;
	  
	  /* Write version number */
	  strcat( sBuffer, ",Version=" ) ;
	  strcat( sBuffer, IRIS_VERSION ) ;
	  
	  istatus = WriteSocketString( pDsp_a->idsplu, sBuffer, FALSE ) ;
	  if (istatus!=SS_NORMAL) goto ERROR;

	  /* Read back the INFO acknowledge */
	  istatus = ReadSocketAck( pDsp_a->idsplu, FALSE, DSP_TIMEOUT_MS ) ;
	  if (istatus!=SS_NORMAL) goto ERROR;
	  
	  /* Ask for the server's setup information */
	  istatus = WriteSocketString( pDsp_a->idsplu, "SETU|", FALSE ) ;
	  if (istatus!=SS_NORMAL) goto ERROR;

	  {
	    struct lim_memory SetupAscii;
	    struct dsp_manual_setup SetupBinary;
	    /* Read back the setup structure */
	    SetupAscii.sPointer = ReadSocketPacketTcp( pDsp_a->idsplu, &SetupAscii.iSize, 
						       FALSE, DSP_TIMEOUT_MS, NULL );
	    istatus = CheckSocketAck( SetupAscii.sPointer, SetupAscii.iSize );
	    if (istatus==SS_NORMAL)
	      {
		struct lim_memory Setup;
		Setup = SetupAscii;
		Setup.sPointer+=4;  /* Skip the "Ack|" */
		Setup.iSize   -=4;
		/* Convert to binary structure */
		istatus = lim_loadMemory( &Setup, "dsp_manual_setup", &SetupBinary,
					  setup_limits );
	      }
	    free( SetupAscii.sPointer );
	    if (istatus!=SS_NORMAL) goto ERROR;
	    {
	      /* Copy into local storage.  We replace the whole setup structure
	       * except the interface specs.
	       */
	      UINT1 ldsp_available, iInterface; 
	      char sDevice[40]; UINT4 iPortNumber;
	      ldsp_available = dspdrv_setup_c.ldsp_available;
	      iInterface     = dspdrv_setup_c.interface;
	      memcpy( sDevice, dspdrv_setup_c.sdevice, 40 );
	      iPortNumber    = dspdrv_setup_c.iPortNumber;
	      
	      dspdrv_setup_c = SetupBinary;

	      dspdrv_setup_c.ldsp_available = ldsp_available;
	      dspdrv_setup_c.interface      = iInterface;
	      memcpy( dspdrv_setup_c.sdevice, sDevice, 40 );
	      dspdrv_setup_c.iPortNumber    = iPortNumber;
	    }
	  }

	  /* Ask for the server's zcal information */
	  istatus = WriteSocketString( pDsp_a->idsplu, "RCAL|", FALSE ) ;
	  if (istatus!=SS_NORMAL) goto ERROR;

	  {
	    struct lim_memory SetupAscii;
	    /* Read back the setup structure */
	    SetupAscii.sPointer = ReadSocketPacketTcp( pDsp_a->idsplu, &SetupAscii.iSize, 
						       FALSE, DSP_TIMEOUT_MS, NULL );
	    istatus = CheckSocketAck( SetupAscii.sPointer, SetupAscii.iSize );
	    if (istatus==SS_NORMAL)
	      {
		struct lim_memory Setup;
		Setup = SetupAscii;
		Setup.sPointer+=4;  /* Skip the "Ack|" */
		Setup.iSize   -=4;
		/* Convert to binary structure */
		istatus = lim_loadMemory( &Setup, "dsp_refl_cal", pZcal_a, setup_limits );
	      }
	    free( SetupAscii.sPointer );
	    if (istatus!=SS_NORMAL) goto ERROR;
	  }

	  /* Now setup the OOB heartbeat response */
	  iSocket_c = pDsp_a->idsplu;
	  signal( SIGURG, sigUrgHandler );
	  ierror = fcntl( iSocket_c, F_SETOWN, getpid() );
	  if (ierror==-1) sig_signal( errno_to_message_va( errno, 1, "fcntl(F_SETOWN)" ) );

	  return SS_NORMAL ;

	}
      else
	{
	  MESSAGE istatus2 ; int ierrno ;
	  ierrno = errno ;
	  istatus2 = errno_to_message_va( errno, 1, "DspOpenSocket()" ) ;
	  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, dspdrv_setup_c.sdevice ) ;
	      break ;

	    case ETIMEDOUT:
	    case ENETUNREACH:
	    case ENETDOWN:
	      /* Network cannot get to the other machine */
	      istatus = iMessageMod_va( USER_HOST_UNREACHABLE, 1, dspdrv_setup_c.sdevice ) ;
	      break ;

	    default:
	      istatus = istatus2 ;
	      break ;

	    }
	}
    ERROR:

      /* Close the socket, if there was an error before returning */

      ierror = close( pDsp_a->idsplu ) ;
      if (ierror == -1)
	sig_signal( errno_to_message_va( errno, 1, "DspOpenSocket()") ) ;
    }
  else
    {
      istatus = errno_to_message_va( errno, 1, "DspOpenSocket()") ;
    }

  return istatus ;

}
/* =============================================
 * Signal handler for the SIGURG signal.
 */
static void sigUrgHandler
( int isignal_a )
{

  int iCount;  char iData;
  iCount = recv( iSocket_c, &iData, 1, MSG_OOB );
  if (iCount<0)
    {
      if (errno != EWOULDBLOCK)
	sig_signal( errno_to_message_va( errno, 1, "recv(OOB)") ) ;
    }
  /* sig_signal_va( USER_TEXTMSG, 1, "Got OOB Heartbeat" ); */
  
  /* Send a reply back */
  send( iSocket_c, "Y", 1, MSG_OOB );

  return;  /* May interrupt blocked calls */
}

/* =======================================
 * Read from the socket.  Parse the received packet, return data and
 * status returned.  Returns size read, -1 on error.
 */
int ReadSocket
( int     iSocket_a,
  char    *pData_a,     /* Data goes here */
  SINT4   iMaxSize_a,   /* Size of data buffer goes here */
  MESSAGE *pStatus_a )   /* Detailed status returned here */
{
  char *sBuffer ; SINT4 iSize, iSizeReturn ;
  sBuffer = ReadSocketPacketTcp( iSocket_a, &iSize, FALSE, DSP_TIMEOUT_MS, NULL );

  if ((sBuffer)&&(iSize>=3))  /* Size needed for string compare later */
    {
      const char *pData ;
      /* Now parse the error string.  Don't use strchr because the
       * buffer might not be null terminated. */
      pData = (char *)memchr( sBuffer, '|', iSize ) ;
      if (0 == strncmp( sBuffer, "Ack", 3 ) )
	{
	  *pStatus_a = SS_NORMAL ;
	  if (pData)
	    {
	      pData++ ;
	      iSizeReturn = iSize - (pData - sBuffer) ;
	      if (iSizeReturn > iMaxSize_a) iSizeReturn=iMaxSize_a ;
	      memcpy( pData_a, pData, iSizeReturn ) ;
	    }
	  else
	    {
	      iSizeReturn = 0 ;
	    }
	}
#ifndef HPUX
      /* Acknowledge compressed */
      else if (0 == strncmp( sBuffer, "AkC", 3 ) )
	{
	  *pStatus_a = SS_NORMAL ;
	  if (pData)
	    {
	      uLongf iUncompressedSize ; SINT4 iSizeRead ; int ierror ;
	      pData++ ;
	      iSizeRead = iSize - (pData - sBuffer) ;
	      iUncompressedSize = iMaxSize_a ;
	      ierror = uncompress( (Bytef*)pData_a, &iUncompressedSize, (Bytef*)pData,
				   iSizeRead ) ;
	      if (ierror==Z_OK)
		{
		  iSizeReturn = iUncompressedSize ;
		}
	      else
		{
		  char sString[80] ;
		  sprintf( sString, "uncompress() returned %d", ierror ) ;
		  *pStatus_a = iMessageMod_va( DSP_OPEN_ERROR, 1, sString ) ;
		}
	    }
	  else
	    {
	      iSizeReturn = 0 ;
	    }
	}
#endif  /* #ifndef HPUX */
      else
	{
	  if (pData)
	    {
	      pData++ ;
	      *pStatus_a = iMessageMod_va( DSP_OPEN_ERROR, 1, pData ) ;
	    }
	  else
	    {
	      /* Buffer has no "|" */
	      *pStatus_a = USER_DEVTIMEOUT ;
	    }
	  iSizeReturn = -1 ;
	}
    }
  else
    {
      /* No data read */
      *pStatus_a = USER_DEVTIMEOUT ;
      iSizeReturn = -1 ;
    }
  if( sBuffer ) free( sBuffer ) ;
  return( iSizeReturn ) ;
}
