/*
 * RPCst.c - Remote Procedure Call stream message library routines.
 *
 * These routines are used to provide connection-oriented client/server data
 * communication. They are used by the client and server processes to
 * insulate them from the actual low-level socket and Internet system
 * calls needed to pass stream messages between processes on different
 * host systems.
 *
 * The server implemented by RPC_server_init is an iterative (as compared
 * with a concurrent) server.
 *
 * Related components: server.c, client.c, common.h, common.c, RPCdg.c.
 *
 */

#include <arpa/inet.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 
#include <strings.h>
#include <stdio.h>
#include "common.h"

/* function prototypes */

void RPC_send_connect();
int RPC_server_process_client(int clientsock, 
   int (*funcp)(char *clientmsg, char **response)); /* process client msgs */

/* global data definitions */

struct sockaddr_in dest_addr;		/* destination IP address */
int sock;				/* socket descriptor */
char sendbuf[RCVLEN];			/* buffer to send message */

/*
 * int RPC_client_init(char *machine, int port);
 *
 * RPC_client_init initializes remote procedure call processing for the
 * client process. machine points to a character string containing the 
 * name of the host on which the server is executing; port is the remote
 * port number of the server.
 *
 * Unlike the datagram version of RPC_client_init, no socket() or bind()
 * system calls are done here; these are done instead each time RPC_send()
 * is called, as this is the only way an iterative server can be implemented
 * owing to the limitations of the NeXT operating system.
 *
 * If an error occurs, RPC_client_init will print an explanatory message
 * and return a non-zero value; otherwise RPC_client_init returns 0.
 *
 */

int RPC_client_init(char *machine, int port)

{

   struct hostent *hp;			/* result of host name lookup */

   /* do not need to do socket() or bind(); this is done in RPC_send() */

   /* set up destination address for server */

   hp = gethostbyname(machine);
   if (hp == NULL) {
      prints("RPC_client_init: An error occurred when calling "
         "gethostbyname(%s).\n", machine);
      return -1;
   }
   bzero(&dest_addr, sizeof(dest_addr));	/* clear all bytes */
   dest_addr.sin_family = AF_INET;	/* protocol family */
   bcopy(hp->h_addr_list[0], &dest_addr.sin_addr, hp->h_length);
   dest_addr.sin_port = htons(port);	/* local port # of server */

   /* return successful status */

   return 0;

}

/*
 * void RPC_client_term();
 *
 * RPC_client_term is called by the client process to terminate communications
 * with the server process.
 *
 * Unlike the datagram RPC_client_term(), the socket is not closed here as
 * it is closed by RPC_send() after each message is sent.
 *
 */

void RPC_client_term()
{
   return;
}

/*
 * char *RPC_send(char *message);
 *
 * RPC_send is used to send a message to the server process after routine
 * RPC_client_init has been called to establish the communications link.
 * message points to the outgoing character buffer; RPC_send returns the
 * character string reply message from the server. 
 *
 * If errors occur, RPC_send prints a message and aborts execution.
 *
 */

char *RPC_send(char *message)
{

   char rcvbuf[RCVLEN];		/* buffer to receive message from server */

   /* (re)establish stream connection with server */

   RPC_send_connect();

   /* send a stream message to the server */

   strncpy(sendbuf, message, RCVLEN);	/* copy to correct size buffer */
   st = write(sock, sendbuf, RCVLEN);
   if (st < 0) error("RPC_send: Error sending stream message to server.");
   print("RPC_send: Message sent; waiting for response.\n");

   /* receive a stream message from the server */

   for (i = 0; i < RCVLEN; i+= st) {
      st = read(sock, &rcvbuf[i], RCVLEN - i);
      if (st < 0) error("RPC_send: Error receiving stream data from server.");
      if (st == 0) error("RPC_send: Stream data read failed.");
   }

   /* close socket for stream connection and return incoming message */

   print("RPC_send: Closing socket to stream server.\n");
   close(sock);
   return rcvbuf;

}

/*
 * void RPC_send_connect();
 *
 * RPC_send_connect (re)connects to the RPC iterative stream server. On a 
 * reasonable system this would entail performing an initial socket() & 
 * bind(), followed by a connect() & write()/read() (and perhaps some way
 * to disconnect?) for each message sent. Because the NeXT does not allow 
 * multiple connect() calls for a stream socket, the client must perform
 * socket(), bind() & connect() system calls for each message sent.
 *
 */

void RPC_send_connect()
{

   struct sockaddr_in local_addr;	/* local IP address */

   /* create Internet stream socket */

   sock = socket(PF_INET, SOCK_STREAM, 0);
   if (sock < 0)
      error("RPC_send_connect: An error occurred while creating the "
         "stream socket.");

   /* bind socket to an arbitrary port */

   bzero(&local_addr, sizeof(local_addr));	/* clear all bytes */
   local_addr.sin_family = AF_INET;	/* protocol family */
   local_addr.sin_addr.s_addr = htonl(INADDR_ANY); /* asg. any local address */
   local_addr.sin_port = 0;		/* assign any local port */
   st = bind(sock, (struct sockaddr *) (&local_addr), sizeof(local_addr));
   if (st < 0) error("RPC_send_connect: bind() returned error status.");

   /* print out port number for local address */

   i = sizeof(local_addr);
   st = getsockname(sock, (struct sockaddr *) (&local_addr), &i);
   if (st < 0) error("RPC_send_connect: getsockname() error.");
   printn("RPC_send_connect: Socket created & connected to local port #%d.\n", 
      ntohs(local_addr.sin_port));

   /* create a connection from the socket to the server */

   st = connect(sock, (struct sockaddr *) (&dest_addr), sizeof(dest_addr));
   if (st < 0) error("RPC_send_connect: Unable to connect to server.\n");

   return;

}

/*
 * int RPC_server_init(int (*funcp)(char *clientmsg, char **response));
 *
 * RPC_server_init is called by the server process to initiate server
 * execution. The server itself is insulated from the actual low-level
 * system calls used to provide connection-oriented stream message 
 * communications with the client processes.
 *
 * funcp is the name of a server function which is invoked upon receipt
 * of a message from a client. funcp is expected to process the message,
 * which is passed as the first (*funcp) parameter clientmsg, and return
 * a response message by altering the specified response pointer. The
 * server function also is expected to return a value of TRUE or FALSE
 * indicating whether or not termination of the server is desired.
 *
 * RPC_server_init loops until the user server function returns a 
 * termination status, at which point it returns with a value of 0.
 * If an error is detected at any point, a message is printed and a
 * non-zero value is returned by RPC_server_init.
 *
 */

int RPC_server_init(int (*funcp)(char *clientmsg, char **response))
{
   struct sockaddr_in local_addr;	/* local IP address */
   int terminate_server;		/* TRUE if server shutdown requested */
   int clientsock;			/* descriptor for new connection */

   /* create Internet socket for stream communications with client processes */

   bzero(&local_addr, sizeof(local_addr));	/* clear all bytes */
   local_addr.sin_family = AF_INET;	/* protocol family */
   sock = socket(PF_INET, SOCK_STREAM, 0);
   if (sock < 0) { 
      print("RPC_server_init: An error occurred while creating the "
         "stream socket.\n");
      return -1;
   }

   /* bind socket to an arbitrary port */

   local_addr.sin_addr.s_addr = htonl(INADDR_ANY); /* assign any local addr. */
   local_addr.sin_port = 0;		/* assign any local port */
   st = bind(sock, (struct sockaddr *) (&local_addr), sizeof(local_addr));
   if (st < 0) { print("RPC_server_init: bind() error.\n"); return -1; }

   /* print out port number for local address */

   i = sizeof(local_addr);
   st = getsockname(sock, (struct sockaddr *) (&local_addr), &i);
   if (st < 0) { print("RPC_server_init: getsockname() error.\n"); return -1; }
   printn("\nRPC_server_init: Socket created and connected to local "
      "port #%d.\n", ntohs(local_addr.sin_port));

   /* set maximum number of queued connection requests */

   st = listen(sock, 5);
   if (st < 0) { print("RPC_server_init: listen() failed.\n"); return -1; }

   /* loop forever waiting for client requests */

   for (terminate_server = FALSE; !terminate_server;) {
      print("RPC_server_init: Waiting for connection...\n");

      /* wait for connection from a client */

      i = sizeof(dest_addr);
      clientsock = accept(sock, (struct sockaddr *) (&dest_addr), &i);
      if (clientsock < 0) {
         print("RPC_server_init: accept() failed.\n"); 
         return -1;
      }

      /* print out IP address and port number of client */

      prints("RPC_server_init: Connection request from %s, ", 
         inet_ntoa(dest_addr.sin_addr));
      printn("port #%d.\n", ntohs(dest_addr.sin_port));

      /* process incoming message from this client and send reply back */

      terminate_server = RPC_server_process_client(clientsock, funcp);
      close(clientsock);		/* terminate connection to client */

   }

   /* server termination has been requested; return to caller */

   return 0;				/* successful return */

}

int RPC_server_process_client(int clientsock, 
   int (*funcp)(char *clientmsg, char **response))
{

   char clientbuf[RCVLEN];		/* buffer for client request */
   char *response;			/* server processor response string */
   int terminate_server;		/* TRUE if done processing client */

   /* receive a stream message from a client */

   for (i = 0; i < RCVLEN; i+= st) {
      st = read(clientsock, &clientbuf[i], RCVLEN - i);
      if (st < 0)
         error("RPC_server_process_client: Error receiving client data.");
      if (st == EOF)
         error("RPC_server_process_client: Unexpected EOF from client.");
   }

   /* process server function for client message, returning 
      server termination status and response string */

   terminate_server = (*funcp)(clientbuf, &response); /* do server function */

   /* send a stream message response to the client, close client socket 
      and return termination status */

   strncpy(sendbuf, response, RCVLEN);	/* copy msg into correct size buffer */
   st = write(clientsock, sendbuf, RCVLEN);
   if (st < 0)
      error("RPC_server_process_client: Error sending client response.");

   return terminate_server;

}

