/*
 * SMlib.c - Shared memory library routines.
 *
 * These routines are used to provide a shared memory server and client 
 * function laid on top of the RPCdg/RPCst remote procedure call library
 * mechanism.
 *
 * Functions used by the server and client processes are commingled herein;
 * each function is documented by EXECUTES IN SERVER or EXECUTES IN CLIENT.
 * Some data definitions are also identified by USED IN SERVER/USED IN CLIENT.
 *
 * See associated documentation for further information.
 *
 */

#include "common.h"

/* shared memory library compile-time constants */

#define INACTIVE	0		/* entry in clientIDs is not in use */
#define ACTIVE_NO_DATA	1		/* entry in clientIDs is in use; no
					   data has been written yet */
#define ACTIVE_W_DATA	2		/* entry in clientIDs is in use; data
					   has been written (via Twrite) */

/* shared memory library function prototypes */

int issue_SM_server_request(int SM_function);
int process_SM_request(char *clientmsg, char **response);

/* shared memory library data definitions */

char *RPC_send_response;		/* response from RPC_send */
struct SMmessage SMmsg;			/* message to/from client or server */
int clientIDs[MAXCLIENTS + 1];		/* table of active client IDs; IDs
					   range from 1..MAXCLIENTS;
					   USED IN SERVER */
int ID_of_this_client;			/* this client's ID; USED IN CLIENT */
int currentID;				/* client du jour; USED IN SERVER */
int op;					/* operation code; USED IN SERVER */
int opID;				/* operation ID; USED IN SERVER */

/* define pointers to server Tread and Twrite functions */

void (*server_Tread)(int sourceID, int *value1, int *value2); /* IN SERVER */
void (*server_Twrite)(int sourceID, int value1, int value2);  /* IN SERVER */

/*
 * int issue_SM_server_request(int SM_function);  EXECUTES IN CLIENT 
 *
 * issue_SM_server_request forwards a client process request to the shared
 * memory server. SM_function is the desired shared memory function to be
 * performed. The originating_clientID field is set in the SMmsg header by
 * issue_SM_server_request; any other function parameters (e.g., sourceID,
 * value1, etc.) must be set by the caller before executing this function.
 *
 * If no error has occurred, issue_SM_server_request returns a value of
 * SM_request_ok; if a user error occurs (return code above SM_TR_codes),
 * issue_SM_server_request returns the server status code. Other errors
 * (outside the control of the user, such as shared memory protocol errors)
 * result in an error message being printed and the client process aborted.
 * Note that the return status need only be checked for SM server requests
 * which return statuses above SM_TR_codes (i.e., Tread & Twrite).
 *
 */

int issue_SM_server_request(int SM_function)
{
   /* set client ID in message header if not requesting new client ID */

   if (SM_function != SM_assign_ID)
      SMmsg.hdr.originating_clientID = ID_of_this_client;

   /* issue shared memory request to server and put response in SMmsg */

   SMmsg.hdr.operation = SM_function;		/* set shared memory function */
   RPC_send_response = RPC_send((char *) &SMmsg); /* issue request to server */
   bcopy(RPC_send_response, &SMmsg, sizeof(SMmsg)); /* move response to SMmsg */

   /* check response from server to make sure it sent it to the correct 
      client ID (except if requesting server assign a new ID) */

   if (SM_function != SM_assign_ID && 
      SMmsg.hdr.originating_clientID != ID_of_this_client) {
      printn("    issue_SM_server_request: Incorrect client ID (#%d) in "
         "header returned from\n", SMmsg.hdr.originating_clientID);
      errorn("                             SM_unassign_ID request; client "
         "ID #%d expected.\n", ID_of_this_client);
   }

   /* check server return status code - fail if non-user error */

   st = SMmsg.hdr.status_from_server;
   if (st != SM_request_ok && st < SM_TR_codes)
      errorn("    issue_SM_server_request: Unexpected return status (%d) "
         "from\n                             SM server request.", 
         SMmsg.hdr.status_from_server);

   return st;				/* return ok or user error status */
}

/*
 * int process_SM_request(char *clientmsg, char **response); EXECUTES IN SERVER
 *
 * process_SM_request performs client service processing upon receipt of
 * a request from a client. SM_server() calls process_SM_request with the 
 * first parameter containing a pointer to the character string received 
 * from the client process. process_SM_request returns a response string 
 * based upon the client message.
 *
 * The clientmsg and response parameters are the standard ones used in
 * the RPC protocol. The clientmsg buffer is copied to the SMmsg structure,
 * from which the various shared memory library parameters are obtained.
 * Similarly the reply SMmsg structure is returned to the calling RPC routine.
 *
 * process_SM_request recognizes the following SM operations: SM_assign_ID
 * (assign new client ID to a client process), SM_Tread (read shared memory
 * storage for a specified client ID), SM_Twrite (write to shared memory 
 * storage for the current client ID), and SM_unassign_ID (releases the
 * current client ID for future use).
 *
 * process_SM_request always returns a value of FALSE, which is the termination
 * status expected by the caller (RPC_server_init). The shared memory server
 * mechanism currently has no provision for server termination.
 *
 */

int process_SM_request(char *clientmsg, char **response)
{
   bcopy(clientmsg, &SMmsg, sizeof(SMmsg));	/* move to SMmsg structure */

   /* note current client ID, validate it, and note operation ID */

   op = SMmsg.hdr.operation;
   if (op != SM_assign_ID) {
      currentID = SMmsg.hdr.originating_clientID;
      if (currentID < 1 || currentID > MAXCLIENTS)
         errorn("    process_SM_request: message received with invalid "
            "client ID (#%d).", currentID);
      else if (clientIDs[currentID] == INACTIVE)
         errorn("    process_SM_request: message received from inactive "
            "client ID (#%d).", currentID);
      opID = SMmsg.hdr.operationID;
   }

   /* echo request from client and process it */

   switch (op) {

      case SM_assign_ID:
           print("    process_SM_request: SM request SM_assign_ID().\n");

	   /* find next available client ID slot */

           for (i = 1; i <= MAXCLIENTS && clientIDs[i] != INACTIVE; i++);
           if (i <= MAXCLIENTS) {
              printn("    process_SM_request: Assigning new client ID #%d.\n", 
                 i);
	      clientIDs[i] = ACTIVE_NO_DATA;
              SMmsg.hdr.originating_clientID = i;
              SMmsg.hdr.status_from_server = SM_request_ok;
           }
           else {
              print("    process_SM_request: ID table full; unable to "
		 "assign client ID.\n");
              SMmsg.hdr.originating_clientID = 0;
              SMmsg.hdr.status_from_server = SM_no_ID_avail;
	   }
	   break;

      case SM_Tread:
	   printnn("    process_SM_request: SM request Tread(client #%d) "
              "from client #%d.\n", opID, currentID);

           /* make sure client for which data is requested is valid 
	      and has already written data */

           if (opID < 1 || opID > MAXCLIENTS) {
              printn("    process_SM_request: client #%d is out of range.\n",
                 opID);
              SMmsg.hdr.status_from_server = SM_bad_ID; 
           }
           else if (clientIDs[opID] == INACTIVE) {
              printn("    process_SM_request: client #%d is not active.\n",
                 opID);
              SMmsg.hdr.status_from_server = SM_inactive_ID; 
           }
           else if (clientIDs[opID] == ACTIVE_NO_DATA) {
              printn("    process_SM_request: client #%d has not written "
                 "any data yet.\n", opID);
              SMmsg.hdr.status_from_server = SM_no_data; 
           }
           else {

              /* execute server Tread function and return status code */

              (*server_Tread)(opID, &SMmsg.data.value1, &SMmsg.data.value2);
              SMmsg.hdr.status_from_server = SM_request_ok;
           }
	   break;

      case SM_Twrite:
	   printnn("    process_SM_request: SM request Twrite(client #%d) "
              "from client #%d.\n", opID, currentID);
           if (opID != currentID) {
              print("    process_SM_request: Cannot write to another "
                 "client's shared memory.\n");
              SMmsg.hdr.status_from_server = SM_bad_ID;
           }
           else {

              /* execute server Twrite function, set data-written flag,
                 and return status code */

              (*server_Twrite)(currentID, SMmsg.data.value1, SMmsg.data.value2);
              clientIDs[currentID] = ACTIVE_W_DATA;
              SMmsg.hdr.status_from_server = SM_request_ok;
           }
	   break;

      case SM_unassign_ID:
	   printn("    process_SM_request: SM request SM_unassign_ID() "
              "from client #%d.\n", currentID);
           clientIDs[currentID] = INACTIVE;
           SMmsg.hdr.status_from_server = SM_request_ok;
	   break;

      default:
	   printnn("    process_SM_request: INVALID SM REQUEST (%d) "
              "from client #%d.\n", op, currentID);
           SMmsg.hdr.status_from_server = SM_bad_request;
	   break;
   }

   /* return response to client */

   bcopy(&SMmsg, clientmsg, sizeof(SMmsg)); /* move back to original buffer */
   *response = clientmsg;

   return FALSE;			/* return; don't terminate server */
}

/* 
 * int SM_init(char *hostname, int portnum);  EXECUTES IN CLIENT
 *
 * SM_init initializes the shared memory library routines for use by client
 * processes. It must be called before Tread() or Twrite() can be used. When
 * shared memory client processing is finished, SM_term() must be called.
 *
 * hostname specifies the name of the host on which the shared memory server 
 * is executing; portnum is the port number on that host for the server.
 *
 * SM_init sends a SM_assign_ID request to the shared memory server to 
 * obtain a new client ID to be used to identify all messages from the client
 * to the shared memory server. The client ID is released for reassignment
 * when SM_term() is called.
 *
 * SM_init returns an integer in the range [1..MAXCLIENTS] indicating the
 * client number assigned to this client by the server.
 *
 */

int SM_init(char *hostname, int portnum)
{
   print("    SM_init: Calling RPC_client_init() to set up communication.\n");
   st = RPC_client_init(hostname, portnum);	/* init. client communication */
   if (st < 0)
      error("    SM_init: RPC error initiating client-server communication.");

   /* request a new client ID from the shared memory server */

   print("    SM_init: requesting new clientID from server.\n");
   SMmsg.hdr.operation = SM_assign_ID; /* request new client ID */
   RPC_send_response = RPC_send((char *) &SMmsg);
   bcopy(RPC_send_response, &SMmsg, sizeof(SMmsg)); /* move to SMmsg */
   if (SMmsg.hdr.status_from_server != SM_request_ok)
      errorn("    SM_init: Unexpected return status (%d) from request "
         "for client ID.", SMmsg.hdr.status_from_server);

   ID_of_this_client = SMmsg.hdr.originating_clientID;
   printn("    SM_init: This client has been assigned client ID #%d.\n",
      ID_of_this_client);

   return ID_of_this_client;		/* return client ID */
}

/*
 * void SM_server(void (*TRfuncp)(int sourceID, int *value1, int *value2), 
 *                void (*TWfuncp)(int sourceID, int value1, int value2));
 *						 EXECUTES IN SERVER
 *
 * SM_server initializes and executes the shared memory library server.
 * Control does NOT return from this routine, as the server loops forever.
 * No provision has been made to terminate the server! When a message 
 * (that is, an SM request) is received by SM_server, the routine 
 * process_SM_request() is executed to process it and return a response.
 *
 * The first parameter in the SM_server call is the name of the server-level
 * function which is to be called in the event of a Tread server request.
 * The second parameter specifies the server-level function to perform a
 * Twrite server request.
 *
 */

void SM_server(void (*TRfuncp)(int sourceID, int *value1, int *value2), 
               void (*TWfuncp)(int sourceID, int value1, int value2))
{
   print("    SM_server: Initiating shared memory server.\n");

   /* ensure SMmessage fits within maximum RPC data area */

   if (sizeof(struct SMmessage) > RPCDATALEN)
      error("    SM_server: SMmessage size exceeds size of RPC data area.");

   /* initialize available client ID table */

   for (i = 1; i <= MAXCLIENTS; i++) clientIDs[i] = INACTIVE;

   /* save pointers to Tread and Twrite server functions */

   server_Tread = TRfuncp;
   server_Twrite = TWfuncp;

   /* initialize shared memory server */

   st = RPC_server_init(process_SM_request);
   if (st != 0) 
      error("    SM_server: An error occurred in RPC server processing.");
   error("    SM_server: Unexpected return from RPC_server_init.");
}

/* void SM_term();  EXECUTES IN CLIENT
 *
 * SM_term terminates shared memory client processing.
 *
 */

void SM_term()
{
   printn("    SM_term: Terminating shared memory processing for client #%d.\n",
      ID_of_this_client);

   /* request that server release client ID so it can be reassigned later */

   printn("    SM_term: Unassigning client ID #%d.\n", ID_of_this_client);
   st = issue_SM_server_request(SM_unassign_ID); /* release ID for reuse */

   /* terminate RPC communications */

   RPC_client_term();

   return;
}

/* int Tread(int sourceID, int *value1, int *value2);  EXECUTES IN CLIENT
 *
 * Tread sends a shared memory table read request to the shared memory 
 * server, using issue_SM_server_request(), which calls the RPC_send() 
 * library function. sourceID specifies the client ID to be read; value1
 * and value2 contain addresses into which the shared memory table values
 * are placed.
 *
 * Tread returns a value of SM_request_ok if no error was encountered during
 * processing; a non-zero value contains the error code for the error.
 *
 */

int Tread(int sourceID, int *value1, int *value2)
{
   printnn("    Tread: Client ID #%d requests Tread(client #%d) from server.\n",
      ID_of_this_client, sourceID);

   SMmsg.hdr.operationID = sourceID;	/* client ID to be read */
   st = issue_SM_server_request(SM_Tread); /* request Tread operation */

   /* return value1/value2 from server reply message if no error */

   *value1 = (st == SM_request_ok ? SMmsg.data.value1 : 0);
   *value2 = (st == SM_request_ok ? SMmsg.data.value2 : 0);

   return st;				/* return status */
}

/* void Twrite(int value1, int value2);  EXECUTES IN CLIENT
 *
 * Twrite sends a shared memory table write request to the shared memory 
 * server, using issue_SM_server_request(), like Tread(). value1 and
 * value2 contain the values to be written to the shared memory for the
 * current client.
 *
 * Twrite returns no return status, as no errors which can be created by
 * or remedied by the user level code can occur.
 *
 */

void Twrite(int value1, int value2)
{
   printnnn("    Twrite: Requesting server write (%d,%d) for client ID "
      "#%d.\n", value1, value2, ID_of_this_client);

   SMmsg.hdr.operationID = ID_of_this_client;	/* client ID to write to */
   SMmsg.data.value1 = value1;
   SMmsg.data.value2 = value2;
   st = issue_SM_server_request(SM_Twrite); /* request Tread operation */
   if (st != SM_request_ok) 
      errorn("    Twrite: Unexpected return status (%d) from "
      "issue_SM_server_request.\n", st);
   return;
}
