/* ***************************************
 * *                                     *
 * *   RVP Export Socket Server Daemon   *
 * *                                     *
 * ***************************************
 * File: utils/dsp/DspExport.C
 *
 *           COPYRIGHT (c) 2002, 2003, 2004, 2005, 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 <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <locale.h>
#include <netinet/in.h>
#ifndef LINUX
#include <signal.h>
#endif  /* #ifndef LINUX */

#define INCLUDE_STDIO_PROTOTYPES 1
#include <stdio.h>

#include <stdlib.h>
#include <string.h>

#define INCLUDE_SYSSOCKET_PROTOTYPES 1
#include <sys/socket.h>

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

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

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

#define DEBUG_ON 0

struct command_line
{
  struct sig_logging_spec Logging;
  UINT2 iPortNumber; 
  UINT1 lDaemon ;
  UINT1 lVersion803;
  UINT1 lHeartBeat;
  char  ipad_end[3];
};

struct state_struct
{
  pid_t iActivePid;     /* PID of the child which is currently active */
};
struct child_state
{
  UINT1 lClientSwapped;   /* The client is byte swapped from us */
  UINT1 lCompress;        /* We will do compressing */
  UINT1 lIO_Open;         /* We are open for I/O */
  UINT1 lRecentActivity;  /* Data recently arrived */
  int   iSocket;          /* Our socket FD */
};

/* Local function prototypes */
static void ProcessCommandLine
( int iArgc_a, 
  char *sArgv_a[],
  struct command_line *pCommand_a );

static void Usage( void );

static void ParentHandler
( pid_t iChildPid_a, 
  int iConnectionNumber_a );

static int ConnectionTester
( const struct sockaddr_in *pCliAddr_a );

static void ConnectionHandler
( int socket_a,
  const struct sockaddr_in *pCliAddr_a,
  int iConnectionNumber_a );

static void ProcessOnePacket
( int socket_a,
  char *sone_packet_a,
  SINT4 iSize_a,
  Dsp *pDsp_a,
  struct child_state *pState_a );

static MESSAGE ProcessOpenPacket
(int iSocket_a,
 Dsp *pDsp_a,
 struct child_state *pState_a );

static MESSAGE ProcessReadPacket
(int iSocket_a,
 char *pData_a,
 Dsp *pDsp_a,
 const struct child_state *pState_a );

static MESSAGE ProcessRdavPacket
(int iSocket_a,
 char *pData_a,
 Dsp *pDsp_a,
 const struct child_state *pState_a );

static MESSAGE ProcessRkffPacket
( int iSocket_a,
  char *pData_a,
  Dsp *pDsp_a );

static MESSAGE ProcessSetupPacket
(int iSocket_a,
 Dsp *pDsp_a );

static MESSAGE ProcessRcalPacket
(int iSocket_a,
 Dsp *pDsp_a );

static MESSAGE ProcessTimePacket
(int iSocket_a,
 const char* pData_a,
 Dsp *pDsp_a );

static MESSAGE ProcessWcalPacket
(int iSocket_a,
 char* pData_a,
 int  iSize_a,
 Dsp *pDsp_a );

static void SIGCHLD_handler
( int isignal_a );

static void sigUrgHandler
( int isignal_a );

static MESSAGE SignalHandler
(MESSAGE iStatus_a,
 UINT4 iCount_a,
 const UINT4 iSigArgs_a[] );

static void ExitHandler(void);

/* Memory initialized by parent before the fork */
static struct command_line Command_c;
SINT4  iStateSize_c, iStateId_c;

/* Shared memory mapped between parent and children */
volatile struct state_struct *pState_c;

int main( int iArgc_a, char** sArgv_a )
{
  MESSAGE iStatus; struct sigaction action; int ierror; UINT2 lCreated;

  setlocale( LC_ALL, "" );

  /* Process the command line arguments, and continue running as a
   * daemon if requested.
   */
  ProcessCommandLine( iArgc_a, sArgv_a, &Command_c );
  if( Command_c.lDaemon ) sig_daemon_init() ;

  /* Create the shared memory
   */
  iStatus = sig_shmem_map( sizeof(struct state_struct), "DspExportMemory", TRUE,
			   (void**)&pState_c, &iStateSize_c, &iStateId_c, &lCreated );
  if (iStatus!=SS_NORMAL)
    {
      sig_signal(iStatus);
      exit(EXIT_FAILURE);  /* Stop on a shared memory creation error */
    }

  atexit( ExitHandler );

  /* Stop if it was already there! */
  if (!lCreated) sig_signal_va( USER_SHMEM_FOUND, 1, "DspExportMemory" );

  /* Clear the state */
  memset( (void*)pState_c, 0, sizeof(*pState_c) );

  /* Open the logging file, if any */
  if (Command_c.Logging.lLogToFile)
    {
      char *sptr;
      char sLogPath[PATHNAME_SIZE];
      sptr = getenv( "IRIS_LOG" );
      if (sptr)
	{
	  strncpy( sLogPath, sptr, PATHNAME_SIZE-20 );
	  sLogPath[PATHNAME_SIZE-20] = 0;
	}
      else
	strcpy( sLogPath, "/tmp/" );
      strcat( sLogPath, "DspExport.log" );
      if (Command_c.Logging.lAppendToLog)
	Command_c.Logging.iLogFile = fopen( sLogPath, "a" );
      else
	Command_c.Logging.iLogFile = fopen( sLogPath, "w" );
      if (NULL == Command_c.Logging.iLogFile)
	{
	  fprintf( stderr, "Can not open log file: '%s'\n", sLogPath );
	  exit(EXIT_FAILURE);
	}
    }

  /* Starting here we can use the Lprintf function */
  sig_establish( SignalHandler, "DspExport" );

  if (Command_c.Logging.lVerbose)
    {
      char sMessage[80];
      struct full_time_spec Time;
      GetTimeFull( &Time, TIME_FLG_LOCAL );

      sprintf( sMessage, "Verbose Logging On, TZ=%s\n", Time.sRecordTZName );
      LoggingPrintf( &Command_c.Logging, sMessage);
    }

  /* Possibly set priority here.... */
  //  istatus = sig_setpriority( pISetup->misc.ipri_network );
  //  if ( istatus != SS_NORMAL) sig_signal( istatus );
 
  /* This will return only on an error while attempting to register the port.
   * In that case, wait 1 minute and try again.
   */

  /* Set up a signal handler for the SIGCHLD signal
   */
  action.sa_handler = SIGCHLD_handler;
  sigemptyset( &action.sa_mask );
  action.sa_flags = SA_RESTART;
  ierror = sigaction( SIGCHLD, &action, NULL );
  if (ierror == -1) sig_signal( errno_to_message_va( errno, 1, "sigaction()" ) ) ;

  while(TRUE)
    {
      SocketListenBlock( Command_c.iPortNumber, ConnectionTester, ConnectionHandler, 
		    ParentHandler );
      sig_microSleep( 60000000 );
    }
}

/* ======================================================================
 * Function to process the command line arguments.
 */
static void ProcessCommandLine
(int iArgc_a, 
 char *sArgv_a[],
 struct command_line *pCommand_a )
{
  int iArg; char *pEnd;

  memset( pCommand_a, 0, sizeof(*pCommand_a) );
  pCommand_a->Logging.lLogToTerm=TRUE;
  pCommand_a->iPortNumber = PORT_DSPLIB_SERVER;
  
  /* Parse command line options
   */
  for( iArg=1 ; iArg<iArgc_a ; iArg++ )
    {
      const char *sArg = sArgv_a[iArg];
      /* fprintf( stderr, "Processing %d: '%s'\n", iArg, sArg ); */
      
      if ( (0 == strncmp( sArg, "-?", 2 )) ||
	   (0 == strncmp( sArg, "-hel", 4 )) )
	{
	  Usage();
	}
      else if ( 0 == strncmp( sArg, "-hea", 4 ))
	{
	  pCommand_a->lHeartBeat = TRUE;
	}
      else if ( 0 == strncmp( sArg, "-log:", 5 ))
	{
	  if ( 0 == strcmp( "both", &sArg[5] ) ) 
	    {pCommand_a->Logging.lLogToFile=TRUE;  pCommand_a->Logging.lLogToTerm=TRUE;}
	  else if ( 0 == strcmp( "file", &sArg[5] ) ) 
	    {pCommand_a->Logging.lLogToFile=TRUE;  pCommand_a->Logging.lLogToTerm=FALSE;}
	  else if ( 0 == strcmp( "term", &sArg[5] ) )
	    {pCommand_a->Logging.lLogToFile=FALSE; pCommand_a->Logging.lLogToTerm=TRUE;}
	  else if ( 0 == strcmp( "none", &sArg[5] ) )
	    {pCommand_a->Logging.lLogToFile=FALSE; pCommand_a->Logging.lLogToTerm=FALSE;}
	}
      else if (0 == strncmp( sArg, "-port:", 6 ))
	{
	  SINT4 iPortNumber = strtol( &sArg[6], &pEnd, 10 ) ;
	  if( (iPortNumber < MIN_USER_PORT) || (iPortNumber > 65535) ) {
	    printf( "Port number %d invalid.\n", iPortNumber ) ;
	    exit(EXIT_FAILURE) ;
	  }
	  pCommand_a->iPortNumber = iPortNumber ;
	}
      else if (0 == strncmp( sArg, "-d", 2 ))
	{
	  pCommand_a->lDaemon = TRUE;
	}
      else if (0 == strncmp( sArg, "-v", 2 ))
	{
	  pCommand_a->Logging.lVerbose = TRUE;
	}
      else if (0 == strncmp( sArg, "-803", 2 ))
	{
	  pCommand_a->lVersion803 = TRUE;
	}
    }
}

/* ==================================================
 */
static void Usage( void )
{
  fprintf( stderr, "DspExport options:\n" );
  fprintf( stderr, "  -daemon        -- Run as detached daemon process\n" );
  fprintf( stderr, "  -heartbeat     -- Use OOB heartbeat to detect dead clients\n" );
  fprintf( stderr, "  -help (-?)     -- Print this list\n" );
  fprintf( stderr, "  -log:<option>  -- log options: term, file, both, none\n" );
  fprintf( stderr, "  -port:<number> -- Specify listen port (default %d)\n",
	   PORT_DSPLIB_SERVER );
  fprintf( stderr, "  -verbose       -- Verbose logging\n" );
  fprintf( stderr, "  -803           -- Use old version 8.03 protocol\n" );
  fprintf( stderr, "   (all other arguments ignored)\n" );
  exit(EXIT_FAILURE) ;
}

/* ===========================================
 * Handle operations for the parent each time a connection is established.
 * Should return quickly.
 */
static void ParentHandler
( pid_t iChildPid_a, 
  int iConnectionNumber_a )
{
  return;
}

/* ==============================================================
 * Test to see if we want to accept another connection.  Returns
 * connection number, zero if it is rejected.
 */
static int ConnectionTester
( const struct sockaddr_in *pCliAddr_a )
{
  return 1;
}

/* Child only static storage */
static struct child_state ChildState_c;

/* ==============================================================
 * Handle the communication from one other system.  This routine will
 * return when the communication is disconnected.
 */
static void ConnectionHandler
( int iSocket_a,            /* The socket number */
  const struct sockaddr_in *pCliAddr_a,
  int iConnectionNumber_a )
{
  char *sBuffer;
  SINT4 iSize, iHeartBeatMs; UINT1 lTimedOut;
  MESSAGE iStatus;
  Dsp *pDsp;
  char sString[80], sReply[170];
  UINT4 iClientAddr=ntohl(pCliAddr_a->sin_addr.s_addr);

  sig_establish( SignalHandler, "DspExportChild" );

  sprintf( sString, "Connection request from:%d.%d.%d.%d", 
	   (iClientAddr>>24),  255&(iClientAddr>>16), 
	   255&(iClientAddr>>8), 255&(iClientAddr) );
  sig_signal_va( USER_TEXTMSG, 1, sString );
		      
  /* Initialize the child state */
  memset( &ChildState_c, 0, sizeof(ChildState_c) );
  ChildState_c.iSocket = iSocket_a;

  /* Respond immediately with the connection status */
  if (Command_c.lVersion803)
    {
      if (0!=pState_c->iActivePid)
	{
	  iStatus = WriteSocketString( iSocket_a, "Nak|Busy", Command_c.Logging.lVerbose );
	  if (iStatus!=SS_NORMAL) sig_signal(iStatus);
	  return;
	}
      pState_c->iActivePid = getpid();
      ChildState_c.lIO_Open=TRUE;
    }

  if (ChildState_c.lIO_Open)
    pDsp = DspOpenForIo( &iStatus );
  else
    pDsp = DspOpen( &iStatus );

  if (pDsp)
    {
      SINT4 iDspType = idsp_type(pDsp);
      strcpy( sReply, "Ack|" );
#ifdef HPUX
      strcat( sReply, "CanCompress=0" );
#else
      strcat( sReply, "CanCompress=1" );
#endif  /* #ifdef HPUX */
      switch(iDspType)
	{
	case DSP_TYPE_RVP6:
	  strcat( sReply, ",Model=RVP6" );
	  break;
	case DSP_TYPE_RVP6_2:
	  strcat( sReply, ",Model=RVP6-2" );
	  break ;
	case DSP_TYPE_RVP7:
	  strcat( sReply, ",Model=RVP7" );
	  break ;
	case DSP_TYPE_RVP8:
	  strcat( sReply, ",Model=RVP8" );
	  break ;
	default:
	  strcat( sReply, ",Model=Unknown" );
	  break ;	  
	}
      strcat( sReply, ",Version=" );
      strcat( sReply, IRIS_VERSION );

      iStatus = WriteSocketString( iSocket_a, sReply, Command_c.Logging.lVerbose );
    }
  else
    {
      char sMessage[160];
      sprintf( sReply, "Nak|%s", message_to_text_r( iStatus, 0, NULL, sMessage ) );
      iStatus = WriteSocketString( iSocket_a, sReply, Command_c.Logging.lVerbose );
      if (iStatus!=SS_NORMAL) sig_signal(iStatus);
      return;
    }
  if (iStatus!=SS_NORMAL) sig_signal(iStatus);


  if (Command_c.lHeartBeat)
    {
      int iError;
      iHeartBeatMs = 30*1000;
      ChildState_c.lRecentActivity = TRUE;
      signal( SIGURG, sigUrgHandler );
      iError = fcntl( iSocket_a, F_SETOWN, getpid() );
      if (iError==-1) sig_signal( errno_to_message_va( errno, 1, "fcntl(F_SETOWN)") ) ;
    }
  else
    iHeartBeatMs = 0;

  lTimedOut=FALSE;
  iSize = 1;
  /* Read until there is an error */
  while ((iSize>0)||lTimedOut)
    {
      /* Do a blocking read, with timeout */
      sBuffer = ReadSocketPacketTcp( iSocket_a, &iSize, Command_c.Logging.lVerbose,
				  iHeartBeatMs, &lTimedOut );

      if (sBuffer)
	{
	  ProcessOnePacket( iSocket_a, sBuffer, iSize, pDsp, &ChildState_c );
	  free(sBuffer);
	  ChildState_c.lRecentActivity = TRUE;
	}
      else if (lTimedOut&&Command_c.lHeartBeat)
	{
	  if (ChildState_c.lRecentActivity)
	    {
	      int iError;
	      sig_signal_va( USER_TEXTMSG, 1, "Sending OOB Heartbeat" );
	      iError = send( iSocket_a, "X", 1, MSG_OOB );
	      if (iError==-1) 
		{
		  sig_signal( errno_to_message_va( errno, 1, "send(OOB)") ) ;
		  lTimedOut=FALSE;  /* Force out of loop above */
		}
	      ChildState_c.lRecentActivity = FALSE;
	    }
	  else
	    {
	      sig_signal_va( USER_TEXTMSG, 1, "Disconnecting due to timeout" );
	      lTimedOut=FALSE;  /* Force out of loop above */
	    }
	}
    }
  iStatus = DspClose( pDsp );
  if (iStatus!=SS_NORMAL) sig_signal(iStatus);
  return;

}

/* =============================================
 * Signal handler for the child death signal
 */
static void SIGCHLD_handler
( int isignal_a )
{
  char sString[80]; int status; pid_t iPid;

  /* Use the wait3 system call with the WNOHANG option.
   */
  while( (iPid=wait3( &status, WNOHANG, 0 )) > 0)
    {
      UINT1 lActive;
      lActive = (iPid==pState_c->iActivePid);
      sprintf( sString, "Disconnecting pid=%d, Active:%d\n", iPid, lActive );
      LoggingPrintf( &Command_c.Logging, sString );
      if (lActive) pState_c->iActivePid=0;
    }
}

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

  int iCount;  char iData;
  iCount = recv( ChildState_c.iSocket, &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" );
  ChildState_c.lRecentActivity = TRUE;

  return;  /* May interrupt blocked calls */
}

/* ================================================
 * Process a single packet of data.  The syntax of this string is
 * pretty simple:
 * 
 * Incomming to server:
 * <command>|<data>
 * Where: <command> is an upper case string without imbedded NULLs or '|'s.
 *
 * Outgoing from server:
 * Ack| or Nak|<data>
 * Where: | is required even if no data.  For "Nak", the data consists of
 * a string explaining the error.
 */
static void ProcessOnePacket
( int iSocket_a,
  char *sone_packet_a,  /* Data is null terminated */
  SINT4 iSize_a,        /* Size of data in bytes */
  Dsp *pDsp_a,
  struct child_state *pState_a )
{
  char sString[80]; MESSAGE iStatus;

  /* Size needed for string compare later */
  if (iSize_a>=4)
    {
      char *pData;
      SINT4 iDataSize;

      if (DEBUG_ON && Command_c.Logging.lVerbose) 
	{
	  SINT4 iIndex, iLength;
	  SINT4 iPrintSize = MIN( 50, iSize_a );

	  /* Print out the first characters */
	  sprintf( sString, "ProcessOnePacket, %d bytes:'", iSize_a );
	  iLength = strlen(sString);
	  for (iIndex=0; iIndex<iPrintSize; iIndex++)
	    {
	      char iChar=sone_packet_a[iIndex];
	      /* All non-printable characters go at '.' */
	      if (isprint(iChar))
		sString[iLength++]= iChar;
	      else
		sString[iLength++]= '.';
	    }
	  sString[iLength++]='\'';
	  sString[iLength++]='\0';
	  sig_signal_va( USER_TEXTMSG, 1, sString );
	}

      /* Now parse received string.  Break it into command and data sections.
       * Use memchr because data may not be null terminated. */
      pData = (char *)memchr( sone_packet_a, '|', iSize_a );
      
      /* Calculate the size of the data section
       */
      if (pData)
	{
	  pData++;
	  iDataSize = iSize_a - (pData - sone_packet_a);
	}
      else
	{
	  iDataSize = 0;
	}
      
      /* Process the commands */
      if ( strncmp( sone_packet_a, "INFO", 4 ) == 0 )
	{
	  if (pData)
	    {
	      char *sPair, *sPairState;

	      /* Process the string for name/value pairs */
	      sPair = strtok_r( pData, ",", &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 byte order is the same */
		      if ( strcmp( sName, "ByteOrder" ) == 0 )
			{
			  if ( strcmp( sValue, "LittleEndian" ) == 0 )
			    {
			      pState_a->lClientSwapped = (BYTE_ORDER!=LITTLE_ENDIAN);
			    }
			  else if ( strcmp( sValue, "BigEndian" ) == 0 )
			    {
			      pState_a->lClientSwapped = (BYTE_ORDER!=BIG_ENDIAN);

			    }
			}

		      /* Check to see if the client will be compressing.
		       * Only compress if both sides want to.
		       */
		      else if ( strcmp( sName, "WillCompress" ) == 0 )
			{
			  char *pEnd;
			  pState_a->lCompress = (0 != strtol( sValue, &pEnd, 10 ));
			}
		    }
		  sPair = strtok_r( NULL, ",", &sPairState );
		}
	    }
	  iStatus = WriteSocketString( iSocket_a, "Ack|", Command_c.Logging.lVerbose );
	}
      else if ( strncmp( sone_packet_a, "OPEN", 4 ) == 0 )
	{
	  iStatus = ProcessOpenPacket( iSocket_a, pDsp_a, pState_a );
	}
      else if ( strncmp( sone_packet_a, "SETU", 4 ) == 0 )
	{
	  iStatus = ProcessSetupPacket( iSocket_a, pDsp_a );
	}
      else if ( strncmp( sone_packet_a, "RCAL", 4 ) == 0 )
	{
	  iStatus = ProcessRcalPacket( iSocket_a, pDsp_a );
	}
      else if ( strncmp( sone_packet_a, "TIME", 4 ) == 0 )
	{
	  iStatus = ProcessTimePacket( iSocket_a, pData, pDsp_a );
	}
      /* All commands after here require that we are open for I/O */
      else if (!pState_a->lIO_Open)
	{
	  iStatus = WriteSocketString( iSocket_a, "Nak|Open for Info only",
				       Command_c.Logging.lVerbose );
	}
      else if ( strncmp( sone_packet_a, "RDAV", 4 ) == 0 )
	{
	  iStatus = ProcessRdavPacket( iSocket_a, pData, pDsp_a, pState_a );
	}
      else if ( strncmp( sone_packet_a, "READ", 4 ) == 0 )
	{
	  iStatus = ProcessReadPacket( iSocket_a, pData, pDsp_a, pState_a );
	}
      else if ( strncmp( sone_packet_a, "WRIT", 4 ) == 0 )
	{
	  if (pState_a->lClientSwapped) SwapU2Array( (UINT2*)pData, iDataSize/2 );
	  iStatus = dsp_write( pDsp_a, pData, iDataSize/2 ) ;
	  if (iStatus==SS_NORMAL)
	    iStatus = WriteSocketString( iSocket_a, "Ack|", Command_c.Logging.lVerbose );
	  else
	    {
	      char sReply[170], sMessage[160];
	      sprintf( sReply, "Nak|%s", message_to_text_r( iStatus, 0, NULL, sMessage ) );
	      iStatus = WriteSocketString( iSocket_a, sReply, Command_c.Logging.lVerbose );
	    }
	}
      else if ( strncmp( sone_packet_a, "STAT", 4 ) == 0 )
	{
	  char sReply[170];
	  UINT2 iStat;
	  iStatus = dspr_status( pDsp_a, &iStat );
	  if (iStatus==SS_NORMAL)
	    sprintf( sReply, "Ack|%d", iStat );
	  else
	    {
	      char sMessage[160];
	      sprintf( sReply, "Nak|%s", message_to_text_r( iStatus, 0, NULL, sMessage ) );
	    }
	  iStatus = WriteSocketString( iSocket_a, sReply, Command_c.Logging.lVerbose );
	}
      else if ( strncmp( sone_packet_a, "RKFF", 4 ) == 0 )
	{
	  iStatus = ProcessRkffPacket( iSocket_a, pData, pDsp_a );
	}
      else if ( strncmp( sone_packet_a, "WCAL", 4 ) == 0 )
	{
	  iStatus = ProcessWcalPacket( iSocket_a, pData, iDataSize, pDsp_a );
	}
      else
	{
	  iStatus = WriteSocketString( iSocket_a, "Nak|Unrecognized command",
				       Command_c.Logging.lVerbose );
	}
      
    }
  else
    {
      iStatus = WriteSocketString
	( iSocket_a, "Nak|Command to short", Command_c.Logging.lVerbose );
    }

  if (iStatus!=SS_NORMAL) sig_signal(iStatus);
}

/* ================================================================
 * Process the open packet.  This means we wish to open for I/O.
 */
static MESSAGE ProcessOpenPacket
( int iSocket_a,
  Dsp *pDsp_a,
  struct child_state *pState_a )
{
  MESSAGE iStatus, iStatus2;
  char sReply[170];

  if (pState_a->lIO_Open)
    iStatus = SS_NORMAL;
  else
    {
      if (pState_c->iActivePid)
	iStatus = DSP_EXPORT_BUSY;
      else
	iStatus = DspAuthorizeIo( pDsp_a );
    }

  if (iStatus == SS_NORMAL)
    {
      pState_c->iActivePid=getpid();
      pState_a->lIO_Open=TRUE;
      strcpy( sReply, "Ack|" );
      iStatus2 = WriteSocketString( iSocket_a, sReply, Command_c.Logging.lVerbose );
    }
  else
    {
      char sMessage[160];
      sprintf( sReply, "Nak|%s", message_to_text_r( iStatus, 0, NULL, sMessage ) );
      iStatus2 = WriteSocketString( iSocket_a, sReply, Command_c.Logging.lVerbose );
    }
  return iStatus2;
}

/* ================================================================
 * Process the read packet.
 */
static MESSAGE ProcessReadPacket
(int iSocket_a,
 char *pData_a,
 Dsp *pDsp_a,
 const struct child_state *pState_a )
{
  MESSAGE iStatus;
  char *pBuffer, *pEnd;
  char sReply[170];
  SINT4 iReadSize;
  
  if (!pData_a)
    {
      iStatus = WriteSocketString( iSocket_a, "Nak|Read size missing", 
				   Command_c.Logging.lVerbose );
      return iStatus;
    }

  iReadSize = strtol( pData_a, &pEnd, 10 );
  if (iReadSize<=0)
    {
      iStatus = WriteSocketString( iSocket_a, "Nak|Read zero size", 
				   Command_c.Logging.lVerbose );
      return iStatus;
    }

  pBuffer = (char*)malloc( iReadSize );
  if (pBuffer)
    {
      SINT4 iActSize;
      iStatus = dsp_read( pDsp_a, pBuffer, iReadSize/2, &iActSize );
      if (iStatus==SS_NORMAL)
	{
	  if (pState_a->lClientSwapped) SwapU2Array( (UINT2*)pBuffer, iActSize );
#ifndef HPUX 
	  if ((pState_a->lCompress)&&(iActSize>100))
	    {
	      char *pCompBuffer;
	      uLongf iCompBufferSize; int ierror;
	      iCompBufferSize = 13 + 2*iActSize + ((2*iActSize)/1000);
	      pCompBuffer = (char*)malloc( iCompBufferSize );
	      if (pCompBuffer)
		{
		  ierror = compress( (Bytef*)pCompBuffer, &iCompBufferSize, (Bytef*)pBuffer,
				     2*iActSize );
		  if (ierror==Z_OK)
		    {
		      /* fprintf( stderr, "Compressed %d to %d\n", 2*iActSize, 
			 iCompBufferSize ); */

		      /* "AkC" means acknowledge compressed */
		      iStatus = WriteSocketData2( iSocket_a, "AkC|", 4, pCompBuffer, 
						  iCompBufferSize, Command_c.Logging.lVerbose );
		    }
		  else
		    {
		      sprintf( sReply, "compress() returned %d", ierror );
		      iStatus = WriteSocketString
			( iSocket_a, sReply, Command_c.Logging.lVerbose );
		    }
		  free( pCompBuffer ) ;
		}
	      else
		{
		  iStatus = WriteSocketString( iSocket_a, "Nak|Malloc2 failed",
					       Command_c.Logging.lVerbose );
		}
	    }
	  else
#endif  /* #ifndef HPUX */
	    {
	      iStatus = WriteSocketData2( iSocket_a, "Ack|", 4, pBuffer, 
					  2*iActSize, Command_c.Logging.lVerbose );
	    }
	}
      else
	{
	  char sMessage[160];
	  sprintf( sReply, "Nak|%s", 
		   message_to_text_r( iStatus, 0, NULL, sMessage ) );
	  iStatus = WriteSocketString( iSocket_a, sReply, Command_c.Logging.lVerbose );
	}
      free( pBuffer );
    }
  else
    iStatus = WriteSocketString( iSocket_a, "Nak|Malloc failed",
				 Command_c.Logging.lVerbose );
  return iStatus;
}
/* ================================================================
 * Process the setup packet.  This will respond with an Ack with
 * the whole setup structure in ASCII.
 */
static MESSAGE ProcessSetupPacket
(int iSocket_a,
 Dsp *pDsp_a )
{
  MESSAGE iStatus, iStatus2;
  char *sSetupFile = NULL;
  SINT4 iSize, iChannel;
  const struct dsp_manual_setup *pSetup;
  char sFileName[FILENAME_SIZE];
  char sPath[PATHNAME_SIZE];
  UINT1 lTempFile = FALSE, lAckWasSent=FALSE;
  
  /* Get directory in which to place the temporary file */

  char *sDir = getenv( "IRIS_TEMP" ) ;
  if( NULL == sDir )
    {
      iStatus = iMessageMod_va( USER_NO_ENV, 1, "IRIS_TEMP" );
      goto END;
    }
  
  pSetup = DspGetSetupPointer( pDsp_a );
  
  sprintf( sFileName, "setup_dsp.DspExport%d", getpid() );

  /* Write setup info to a temporary file */
  iStatus = lim_saveFileDir( NULL, "IRIS_TEMP", sFileName, "dsp_manual_setup", pSetup, 
			     setup_limits );
  if (iStatus!=SS_NORMAL) goto END;

  /* Starting here, we must be sure to delete the temporary file */
  lTempFile = TRUE;

  strcpy( sPath, sDir );
  strcat( sPath, sFileName );
  iStatus = imapopen( sPath, FALSE, (void**)&sSetupFile, &iSize, &iChannel );
  if (iStatus!=SS_NORMAL) goto END;
  /* Starting here, we must be sure to close the temporary file */

  iStatus = WriteSocketData2( iSocket_a, "Ack|", 4, sSetupFile, iSize,
			       Command_c.Logging.lVerbose );
  /* Starting here, we do not send a Nak on error, because the Ack was already sent.*/
  if (iStatus==SS_NORMAL) lAckWasSent = TRUE;

 END:
  /* Close the file */
  if (sSetupFile)
    {
      iStatus2 = imapclose( sSetupFile, iSize, iChannel );
      if (iStatus2!=SS_NORMAL) sig_signal(iStatus2);
    }

  /* Delete the file */
  if (lTempFile)
    {
      iStatus2 = sig_delete_file( sPath );
      if (iStatus2!=SS_NORMAL) sig_signal(iStatus2);
    }

  if (!lAckWasSent)
    {
      char sReply[170], sMessage[160];
      sprintf( sReply, "Nak|%s", 
	       message_to_text_r( iStatus, 0, NULL, sMessage ) );
      WriteSocketString( iSocket_a, sReply, Command_c.Logging.lVerbose );
    }

  return iStatus;
}

/* ================================================================
 * Process the RCAL packet.  This will respond with an Ack with
 * the whole zcal structure in ASCII.
 */
static MESSAGE ProcessRcalPacket
(int iSocket_a,
 Dsp *pDsp_a )
{
  MESSAGE iStatus;
  const struct dsp_refl_cal *pData;
  
  pData = DspGetZcalPointer( pDsp_a );
  
  iStatus = DspSendZcalSocket( pDsp_a, "Ack|", pData, "DspExport", iSocket_a );

  if (iStatus!=SS_NORMAL)
    {
      char sReply[170], sMessage[160];
      sprintf( sReply, "Nak|%s", 
	       message_to_text_r( iStatus, 0, NULL, sMessage ) );
      WriteSocketString( iSocket_a, sReply, Command_c.Logging.lVerbose );
    }

  return iStatus;
}
/* ================================================================
 * Process the time packet.  This will respond with an Ack with
 * the whole setup structure in ASCII.
 */
static MESSAGE ProcessTimePacket
(int iSocket_a,
 const char *pData_a,
 Dsp *pDsp_a )
{
  MESSAGE iStatus, iStatus2;
  char *sFile = NULL, *pEnd;
  SINT4 iSize, iChannel; int iOptions;
  struct full_time_spec Time;
  char sFileName[FILENAME_SIZE];
  char sPath[PATHNAME_SIZE];
  UINT1 lTempFile = FALSE, lAckWasSent=FALSE;
  
  /* Get directory in which to place the temporary file */

  char *sDir = getenv( "IRIS_TEMP" ) ;
  if( NULL == sDir )
    {
      iStatus = iMessageMod_va( USER_NO_ENV, 1, "IRIS_TEMP" );
      goto END;
    }

  if (!pData_a)
    {
      iStatus = WriteSocketString( iSocket_a, "Nak|Time flag missing", 
				   Command_c.Logging.lVerbose );
      goto END;
    }

  iOptions = strtol( pData_a, &pEnd, 10 );
  if (pData_a==pEnd)
    {
      iStatus = WriteSocketString( iSocket_a, "Nak|Time flag format", 
				   Command_c.Logging.lVerbose );
      goto END;
    }

  iStatus = DspGetTimeFull( pDsp_a, &Time, iOptions );
  if (iStatus!=SS_NORMAL) goto END;

  sprintf( sFileName, "time.DspExport%d", getpid() );

  /* Write setup info to a temporary file */
  iStatus = lim_saveFileDir( NULL, "IRIS_TEMP", sFileName, "full_time_spec", &Time, 
			     setup_limits );
  if (iStatus!=SS_NORMAL) goto END;

  /* Starting here, we must be sure to delete the temporary file */
  lTempFile = TRUE;

  strcpy( sPath, sDir );
  strcat( sPath, sFileName );
  iStatus = imapopen( sPath, FALSE, (void**)&sFile, &iSize, &iChannel );
  if (iStatus!=SS_NORMAL) goto END;
  /* Starting here, we must be sure to close the temporary file */

  iStatus = WriteSocketData2( iSocket_a, "Ack|", 4, sFile, iSize,
			      Command_c.Logging.lVerbose );
  /* Starting here, we do not send a Nak on error, because the Ack was already sent.*/
  if (iStatus==SS_NORMAL) lAckWasSent = TRUE;

 END:
  /* Close the file */
  if (sFile)
    {
      iStatus2 = imapclose( sFile, iSize, iChannel );
      if (iStatus2!=SS_NORMAL) sig_signal(iStatus2);
    }

  /* Delete the file */
  if (lTempFile)
    {
      iStatus2 = sig_delete_file( sPath );
      if (iStatus2!=SS_NORMAL) sig_signal(iStatus2);
    }

  if (!lAckWasSent)
    {
      char sReply[170], sMessage[160];
      sprintf( sReply, "Nak|%s", 
	       message_to_text_r( iStatus, 0, NULL, sMessage ) );
      WriteSocketString( iSocket_a, sReply, Command_c.Logging.lVerbose );
    }

  return iStatus;
}

/* ================================================================
 * Process the WCAL packet. 
 */
static MESSAGE ProcessWcalPacket
(int iSocket_a,
 char* pData_a,
 int  iSize_a,
 Dsp *pDsp_a )
{
  MESSAGE iStatus;
  char sReply[160];
  struct lim_memory SetupAscii;
  struct dsp_refl_cal zCal;

  /* Convert to binary structure */
  SetupAscii.sPointer = pData_a;
  SetupAscii.iSize = iSize_a;
  iStatus = lim_loadMemory( &SetupAscii, "dsp_refl_cal", &zCal, setup_limits );

  if (iStatus==SS_NORMAL) iStatus = DspSetZcal( pDsp_a, &zCal );
  
  if (iStatus==SS_NORMAL)
    sprintf( sReply, "Ack|" );
  else
    {
      char sMessage[160];
      sprintf( sReply, "Nak|%s", message_to_text_r( iStatus, 0, NULL, sMessage ) );
    }
  iStatus = WriteSocketString( iSocket_a, sReply, Command_c.Logging.lVerbose );
  
  return iStatus;
}

/* ================================================================
 * Process the read available data packet.
 */
static MESSAGE ProcessRdavPacket
( int iSocket_a,
  char *pData_a,
  Dsp *pDsp_a,
  const struct child_state *pState_a )
{
  MESSAGE iStatus;
  char *pBuffer, *pEnd, *sMaxSize, *sDmaSize, *sParseState;
  SINT4 iMaxSize, iDmaSize;
  
  if (!pData_a)
    {
      iStatus = WriteSocketString( iSocket_a, "Nak|Rdav size missing", 
				   Command_c.Logging.lVerbose );
      return iStatus;
    }

  sMaxSize = strtok_r( pData_a, "|", &sParseState );
  sDmaSize = strtok_r(    NULL, "|", &sParseState );

  if (sMaxSize)
    iMaxSize = strtol( sMaxSize, &pEnd, 10 );
  else
    iMaxSize = 0;
  if (sDmaSize)
    iDmaSize = strtol( sDmaSize, &pEnd, 10 );
  else
    iDmaSize = 0;

  if ((iMaxSize<=0)||(iDmaSize<=0))
    {
      iStatus = WriteSocketString
	( iSocket_a, "Nak|Rdav size error", Command_c.Logging.lVerbose );
      return iStatus;
    }

  pBuffer = (char*)malloc( iMaxSize );
  if (pBuffer)
    {
      SINT4 iActSize;
      iStatus = DspReadAvail( pDsp_a, pBuffer, iMaxSize/2, iDmaSize/2, &iActSize );
      if (iStatus==SS_NORMAL)
	{
	  if (pState_a->lClientSwapped) SwapU2Array( (UINT2*)pBuffer, iActSize );
	  iStatus = WriteSocketData2( iSocket_a, "Ack|", 4, pBuffer, 
				      2*iActSize, Command_c.Logging.lVerbose );
	}
      else
	{
	  char sReply[170], sMessage[160];
	  sprintf( sReply, "Nak|%s", 
		   message_to_text_r( iStatus, 0, NULL, sMessage ) );
	  iStatus = WriteSocketString( iSocket_a, sReply, Command_c.Logging.lVerbose );
	}
      free( pBuffer );
    }
  else
    iStatus = WriteSocketString( iSocket_a, "Nak|Malloc failed",
				 Command_c.Logging.lVerbose );
  return iStatus;
}
/* ================================================================
 * Process the Reset kernel FIFO packet.
 */
static MESSAGE ProcessRkffPacket
( int iSocket_a,
  char *pData_a,
  Dsp *pDsp_a )
{
  MESSAGE iStatus;
  char *pEnd, *sOperation, *sParseState;
  SINT4 iOperation;
  
  if (!pData_a)
    {
      iStatus = WriteSocketString( iSocket_a, "Nak|RKFF operation missing", 
				   Command_c.Logging.lVerbose );
      return iStatus;
    }

  sOperation = strtok_r( pData_a, "|", &sParseState );

  if (sOperation)
    iOperation = strtol( sOperation, &pEnd, 10 );
  else
    iOperation = 0;

  if (iOperation<=0)
    {
      iStatus = WriteSocketString
	( iSocket_a, "Nak|RKFF invalid operation", Command_c.Logging.lVerbose );
      return iStatus;
    }

  iStatus = DspResetKernelFifos( pDsp_a, iOperation );
  if (iStatus==SS_NORMAL)
    iStatus = WriteSocketString( iSocket_a, "Ack|", Command_c.Logging.lVerbose );
  else
    {
      char sReply[170], sMessage[160];
      sprintf( sReply, "Nak|%s", message_to_text_r( iStatus, 0, NULL, sMessage ) );
      iStatus = WriteSocketString( iSocket_a, sReply, Command_c.Logging.lVerbose );
    }

  return iStatus;
}

/* ======================================================================
 * Function to print signaled errors to the logging file or terminal.
 */
static MESSAGE SignalHandler
(MESSAGE iStatus_a,
 UINT4 iCount_a,
 const UINT4 iSigArgs_a[] )
{
  pid_t iPid=getpid();
  char sFullMessage[MESSAGE_SIZE+20], sShortMessage[MESSAGE_SIZE];

  message_to_text_r( iStatus_a, iCount_a, iSigArgs_a, sShortMessage );
  sprintf( sFullMessage, " %5d %s\n", iPid, sShortMessage );

  LoggingPrintf( &Command_c.Logging, sFullMessage );
  return(SS_NORMAL);
}

/* ===================================================================
 * Exit handler to close the shared memory.
 */
static void ExitHandler(void)
{
  MESSAGE iStatus;
  iStatus = sig_shmem_unmap( pState_c, iStateSize_c, iStateId_c );
  if (iStatus!=SS_NORMAL) sig_signal(iStatus);
}
