
/*
 * RPCdg.c - Remote Procedure Call datagram message library routines.
 *
 * These routines are used to provide datagram-based 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 datagram messages between processes on different
 * host systems.
 *
 * Related components: common.h/common.c, RPCst.c.
 *
 */

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

/* global data definitions */

struct sockaddr_in dest_addr;		/* destination IP address */
int sock;				/* socket descriptor */
struct RPCmessage RPCbuf;		/* RPC send & receive buffer */

/*
 * 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.
 *
 * 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 sockaddr_in local_addr;	/* local IP address */
   struct hostent *hp;			/* result of host name lookup */

   /* set RPC validation ID in RPC message buffer */

   strcpy(RPCbuf.header.ID, RPC_ID);

   /* create Internet datagram socket */

   bzero(&local_addr, sizeof(local_addr));	/* clear all bytes */
   local_addr.sin_family = AF_INET;	/* protocol family */
   sock = socket(PF_INET, SOCK_DGRAM, 0);
   if (sock < 0) {
      print("      RPC_client_init: Error creating the datagram socket.\n");
      return -1;
   }

   /* bind socket to an arbitrary port */

   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) { print("      RPC_client_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_client_init: getsockname() error.\n"); 
      return -1; 
   }
   printn("      RPC_client_init: Socket created and connected to local "
      "port #%d.\n", ntohs(local_addr.sin_port));

   /* 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.
 *
 */

void RPC_client_term()
{
   close(sock);				/* close socket */
   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)
{
   /* copy message to RPC buffer and send the datagram to the server */

   bcopy(message, RPCbuf.data, RPCDATALEN - 1);
   RPCbuf.data[RPCDATALEN - 1] = '\0';	/* guarantee zero terminator */

   st = sendto(sock, &RPCbuf, RPCMSGLEN, 0, (struct sockaddr *) (&dest_addr), 
      sizeof(dest_addr)); 
   if (st < 0) error("      RPC_send: Error sending datagram to server.");
   print("      RPC_send: Message sent; waiting for response.\n");

   /* receive a message from the server and return it to caller */

   i = sizeof(dest_addr);
   st = recvfrom(sock, &RPCbuf, RPCMSGLEN, 0, (struct sockaddr *) (&dest_addr), 
      &i);
   if (st < 0) error("      RPC_send: Error receiving data from server.");

   /* validate RPC header ID field */

   if (strncmp(RPCbuf.header.ID, RPC_ID, sizeof(RPC_ID)) != 0)
      error("      RPC_send: Received invalid RPC header ID.");

   return RPCbuf.data;
}

/*
 * 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 datagram 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 TRUE
 * 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))
{
   int terminate_server;		/* TRUE if server shutdown requested */
   struct sockaddr_in local_addr;	/* local IP address */
   char *response;			/* server processor response string */

   /* set RPC validation ID in RPC message buffer */

   strcpy(RPCbuf.header.ID, RPC_ID);

   /* create Internet socket for datagram communication 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_DGRAM, 0);
   if (sock < 0) {
      print("      RPC_server_init: An error occurred while creating the "
         "datagram 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("      RPC_server_init: Socket created and connected to local "
      "port #%d.\n", ntohs(local_addr.sin_port));

   /* loop forever waiting for client requests */

   for (terminate_server = FALSE; !terminate_server;) {
      print("      RPC_server_init: Waiting for datagram...\n");
      i = sizeof(dest_addr);
      st = recvfrom(sock, &RPCbuf, RPCMSGLEN, 0, 
         (struct sockaddr *) (&dest_addr), &i);
      if (st < 0) {
         print("      RPC_server_init: Error receiving data from client.\n");
         return -1;
      }

      /* validate RPC header ID field */

      if (strncmp(RPCbuf.header.ID, RPC_ID, sizeof(RPC_ID)) != 0)
         error("      RPC_server_init: Received invalid RPC header ID.");

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

      printsn("      RPC_server_init: Request received from %s, port #%d.\n", 
         inet_ntoa(dest_addr.sin_addr), ntohs(dest_addr.sin_port));

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

      terminate_server = (*funcp)(RPCbuf.data, &response);

      /* copy response message to RPC buffer and send the datagram back
         to the client; terminate if requested */

      bcopy(response, RPCbuf.data, RPCDATALEN - 1);
      RPCbuf.data[RPCDATALEN - 1] = '\0';	/* guarantee zero terminator */

      st = sendto(sock, &RPCbuf, RPCMSGLEN, 0, (struct sockaddr *) (&dest_addr),
         sizeof(dest_addr)); 
      if (st < 0) {
         print("      RPC_server_init: Error sending response datagram "
            "to client.\n");
         return -1;
      }
   }

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

   return 0;
}

