/* High-level client IPC API for TCP sockets and QNX message passing.
   Rick Smereka, Copyright (C) 1998-2002.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, get a copy via the Internet at
   http://gnu.org/copyleft/gpl.html or write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
   MA 02111-1307 USA

   You can contact the author via email at rsmereka@future-lab.com

   Original version for CodeWarrior V4 under Windows 32bit.
   Dec/98, Rick Smereka

   Ported to HP-UX under GNU C 2.8.1.
   Jan/99, Rick Smereka

   Ported to Red Hat Linux 5.2, Jul/99, Rick Smereka

   Changed 'clientSocket' to 'clientsock' to avoid global
   naming conflicts and removed 'WSA' Winsock functions.
   Dec/99, Rick Smereka
   
   Found major structural bug with socket 'connect' in the
   'ipc_init' function. Moved socket initialization to
   static function 'ipclient_connect'. Added 'ipclient_hostname',
   'ipclient_port' and 'ipclient_init' global data. Removed
   'ipc_init', 'ipc_send_data' and 'ipc_recv_data' functions.
   Added functions 'ipc_set_active, 'ipc_get_active' and 
   'ipc_send_receive'. May/2000, Rick Smereka

   Modified for use with TCP sockets and QNX message passing.
   Sep/2001, Rick Smereka

   Ported to Debian Linux. Nov/2002, Rick Smereka

   Changed socket version to use TCP 'connect' function only when 
   'ipc_set_active' is called. The socket now remains open and is
   only closed when 'ipc_close' is called (which is done automatically
   upon subsequent call to 'ipc_set_active'). This API will properly
   talk only to server's that are using the new 'ipcsrv' (GPL
   package version 1.33 or higher). May/2003, Rick Smereka */

#include "stdhead.h"
#include "flsocket.h"

#ifdef IPC_TCP
#include "ipcomm.h"
#endif

#include "ipclient.h"

#ifdef IPC_TCP

// global (to module) socket data

#ifdef OS_WIN32
SOCKET clientsock;               // client socket
SOCKADDR_IN sockClientAddr;      // client address structure
LPHOSTENT lpHostEnt;             // host info structure
#else
int clientsock;
struct sockaddr_in sockClientAddr;
struct hostent *lpHostEnt;
#endif

int ipclient_port;               // server port number
#else

// global QNX message passing data

pid_t ipclient_port = -1;        // QNX pid stored in port number
#endif

char ipclient_hostname[128];     // server host name
int ipclient_init = FALSE;       // client interface flag

/* private functions (sockets only) */

#ifdef IPC_TCP
static int ipclient_connect(void);
#endif

#ifdef IPC_TCP
int ipc_set_active(char *host_name, int port_number)
#else
int ipc_set_active(char *host_name)
#endif
{
   /* Set the active server host name and port number (sockets) or
      the QNX server name. In the case of sockets, the host and
      port data is loaded into the module globals, in the case of
      QNX message passing, the data is loaded and the 'nameloc'
      service name is verified. Function returns 'TRUE' upon 
      success, 'FALSE' otherwise. */
      
   if (host_name == (char *)NULL || !strlen(host_name))
      return(FALSE);

#ifdef IPC_TCP 
   if (port_number <= 0) 
      return(FALSE);
      
   /* if already connected to a server, close it */

   if (ipclient_init)
      (void)ipc_close();

   /* load global data */
   
   strcpy(ipclient_hostname, host_name);
   ipclient_port = port_number;

   /* attempt to connect to currently defined server (TCP socket only) */
   
   if (!ipclient_connect())
      return(FALSE);
#else
   /* QNX message passing code */

   if ((ipclient_port = qnx_name_locate(0, host_name, 0, NULL)) == -1)
      return(FALSE);

   strcpy(ipclient_hostname, host_name);
#endif

   ipclient_init = TRUE;
   return(TRUE);
}   

#ifdef IPC_TCP
int ipc_get_active(char *host_name, int *port_number)
#else
int ipc_get_active(char *host_name)
#endif
{
   /* Obtain the current server host name and port number.
      Function returns 'TRUE' upon success with the server
      host name loaded into 'host_name' which must be already
      allocated to sufficient size, the server port number will
      be loaded into 'port_number'. Function returns 'FALSE'
      upon failure. */
      
   if (host_name == (char *)NULL)
      return(FALSE);
      
#ifdef IPC_TCP
   if (port_number == (int *)NULL)
      return(FALSE);

   *port_number = 0;
#endif

   host_name[0] = EOS;
   
   if (!ipclient_init)
      return(FALSE);
            
   strcpy(host_name, ipclient_hostname);

#ifdef IPC_TCP
   *port_number = ipclient_port;
#endif

   return(TRUE);
}
   
#ifdef IPC_TCP
int ipc_send_receive(char *sbuf, char *rbuf)
#else
int ipc_send_receive(char *sbuf, char *rbuf, int size_rbuf)
#endif
{
   /* Send a command to the current server and
      wait for a reply. Function returns 'TRUE' upon
      success with the receive contents loaded into 
      'rbuf' which must be already allocated to 
      sufficient size by the caller. Function 
      returns 'FALSE' upon failure. */

#ifdef IPC_QNX
   int i, sent = FALSE;
#endif

   if (!ipclient_init)
      return(FALSE);
            
   if (sbuf == (char *)NULL || !strlen(sbuf))
      return(FALSE);
      
   if (rbuf == (char *)NULL)
      return(FALSE);
      
#ifdef IPC_QNX
   if (size_rbuf <= 0)
      return(FALSE);
#endif

   rbuf[0] = EOS;
   
#ifdef IPC_TCP
      
   /* we are connected, send the command string using
      the low level socket send function */
   
   if (ipc_send(clientsock, sbuf) == 0)
      {
      (void)ipc_close();
      return(FALSE);
      }
      
   /* receive reply */
   
   if (ipc_recv(clientsock, rbuf) == 0)
      {
      (void)ipc_close();
      return(FALSE);
      }
#else
   /* QNX message passing version uses a retry loop
      and the 'Send' function also receives the reply */

   for(i = 0; i < IPC_SEND_RETRY; i++)
      if (!Send(ipclient_port, sbuf, rbuf, strlen(sbuf) + 1, size_rbuf))
         {
         sent = TRUE;
         break;
         }

   if (!sent)
      return(FALSE);
#endif

   return(TRUE);
}
   
/* socket functions */

#ifdef IPC_TCP
int ipc_close(void)
{
   /* Close the socket between client and server.
      Function returns 'TRUE' upon success, 'FALSE'
      otherwise. */

#ifdef OS_WIN32
   if (closesocket(clientsock) == SOCKET_ERROR)
      return(FALSE);
#endif

#ifdef OS_UNIX
   close(clientsock);
#endif

   ipclient_init = FALSE;
   return(TRUE);
}

/* private functions */

static int ipclient_connect(void)
{
   /* Use the previously loaded host name and port number to
      connect to the server. Function returns 'TRUE' if the
      connection was successful, 'FALSE' otherwise (TCP only). */
      
   // resolve server host name

   lpHostEnt = gethostbyname(ipclient_hostname);

   if (!lpHostEnt)
      return(FALSE);

   // create the socket

#ifdef OS_WIN32
   clientsock = socket(PF_INET, SOCK_STREAM, DEFAULT_PROTOCOL);
#endif

#ifdef OS_UNIX
   clientsock = socket(AF_INET, SOCK_STREAM, DEFAULT_PROTOCOL);
#endif

   if (clientsock == INVALID_SOCKET)
      return(FALSE);

   // load client address data

   memset(&sockClientAddr, 0, sizeof(sockClientAddr));
   sockClientAddr.sin_family = AF_INET;
   sockClientAddr.sin_port = htons(ipclient_port);

#ifdef OS_WIN32
   sockClientAddr.sin_addr = *((LPIN_ADDR)*lpHostEnt->h_addr_list);
#endif

#ifdef OS_UNIX
   sockClientAddr.sin_addr.s_addr = ((struct in_addr *)(lpHostEnt->h_addr))->s_addr;
#endif

   // connect to server

#ifdef OS_WIN32
   if (connect(clientsock, (LPSOCKADDR)&sockClientAddr, sizeof(sockClientAddr)))
#endif

#ifdef OS_UNIX
   if (connect(clientsock, (SA *)&sockClientAddr, sizeof(sockClientAddr)) 
       == SOCKET_ERROR)
#endif
      return(FALSE);

   return(TRUE);
}
#endif
