/***********************************************************************
 * 
 *  Copyright (C) 2006 Novell, Inc. All Rights Reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; version 2.1
 *  of the License.
 *
 *  This library 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
 *  Library Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, Novell, Inc.
 * 
 *  To contact Novell about this file by physical or electronic mail, 
 *  you may find current contact information at www.novell.com.
 * 
 *  Author: Juan Carlos Luciani <jluciani@novell.com>
 *
 ***********************************************************************/


//===[ Include files ]=====================================================

#include "ipcint.h"

extern "C" {
#include "casa_c_ipc.h"
}

#include "cchannel.h"
#include "clientreq.h"


//===[ External data ]=====================================================

//===[ External prototypes ]===============================================

//===[ Manifest constants ]================================================

#define  MAX_RPC_RETRIES         3

#define  MAX_CHANNELS            3

//===[ Type definitions ]==================================================

//
// Class for maintaining SmartCChannel pointers within the daemonVector.
//
class SmartCChannelPointer
{
private:
      SmartCChannel *m_pSmartCChannel;
public:

   SmartCChannelPointer() : m_pSmartCChannel(NULL) {}
   ~SmartCChannelPointer() { if (m_pSmartCChannel != NULL) delete m_pSmartCChannel; }
   SmartCChannel* getPointer() { return m_pSmartCChannel; }
   void setPointer(SmartCChannel *pSmartCChannel) { m_pSmartCChannel = pSmartCChannel; }
};

//===[ Function prototypes ]===============================================

//===[ Global variables ]==================================================

// Debug Level
int   DebugLevel = 0;
bool  UseSyslog = false;

// Application Name for logging purposes
char  unInitialized[] = "Uninitialized";
char  *pAppName = unInitialized;

vector<SmartCChannelPointer> cchannelVector;
int   numCChannels;
int   numChannelSubmits = 0;

// Client mutex
pthread_mutex_t   clientMutex;

// Mutex for interlocked operations
pthread_mutex_t   interlockedMutex;

// Indications
bool  svcInitialized = false;
bool  serverAddressSet = false;

// Server address variables
bool                 use_AF_INET;
bool                 use_PF_UNIX;
struct sockaddr_in   serverInAddr = {0};
struct sockaddr_un   serverUnAddr = {0};


//++=======================================================================
void
ReInitializeIpc(void)
//
// Arguments In:  None.
//
// Arguments Out: None.
//
// Returns:       Nothing.
//
// Abstract:      Method to re-initialize the IPC infrastructure for process.
//
// L2
//=======================================================================--
{
   CChannel   *pCChannel;

   DbgTrace(1, "ReInitializeIpc- Start\n", 0);

   // Clean up all allocated SmartCChannel objects
   for (int i = 0; i < cchannelVector.size(); i++)
   {
      // Close the channel if present
      if (cchannelVector[i].getPointer() != NULL)
      {
         pCChannel = *(cchannelVector[i].getPointer());
         pCChannel->closeChannel();

         // Free the SmartCChannel
         delete cchannelVector[i].getPointer();
         cchannelVector[i].setPointer(NULL);
      }
   }

   DbgTrace(1, "ReInitializeIpc- End\n", 0);
}


//++=======================================================================
extern "C"
int
IpcClientInit(
   IN char *pName,
   IN bool multithreaded,
   IN int debugLevel,
   IN bool useSyslog)
//
// Arguments In:  pName - Pointer to string containing the name that the
//                        calling application wants associated with the
//                        debug logs emitted by the library.
// 
//                multithreaded - Set to TRUE if the process is
//                                multithreaded.
// 
//                debugLevel - The level that the library should use for
//                             determining what information should be logged
//                             for debugging purposes. 0 being the lowest
//                             level.
// 
//                useSyslog - Set to TRUE to log debug statements using Syslog,
//                            else debugs are log to stderr.
//
// Arguments Out: None.
//
// Returns:       0 == Success
//                -1 == Failure
//
// Abstract:      Method to initialize the IPC infrastructure for process.
// 
// Note:          It is necessary to call the appropriate function to
//                set the server address before a request can be submitted.
//
// L1
//=======================================================================--
{
   int   retStatus = -1;

   DbgTrace(1, "IpcClientInit- Start\n", 0);

   // Check input parameters
   if (pAppName == NULL)
   {
      DbgTrace(0, "IpcClientInit- Invalid parameter\n", 0);
      goto exit;
   }

   // Save a copy of the application name
   pAppName = new char[strlen(pName) + 1];
   if (pAppName == NULL)
   {
      DbgTrace(0, "IpcClientInit- Memory allocation failure\n", 0);
      goto exit;
   }
   strcpy(pAppName, pName);

   // Save the rest of the debug settings
   DebugLevel = debugLevel;
   UseSyslog = useSyslog;

   // Initialize our mutexes
   pthread_mutex_init(&clientMutex, NULL);
   pthread_mutex_init(&interlockedMutex, NULL);

   // Proceed based on whether or not we have already instantiated
   // SmartCChannel vectors.
   if (cchannelVector.size() == 0)
   {
      // SmartCChannel entries have not been instantiated
      //
      // Setup the number of channels that we may have based on
      // whether the application is multi-threaded or not.
      if (multithreaded)
         numCChannels = MAX_CHANNELS;
      else
         numCChannels = 1;

      // Instantiate entries in SmartCChannel vector
      try {
         for (int i = 0; i < numCChannels; i++)
            cchannelVector.push_back(SmartCChannelPointer());

         // Done initializing
         svcInitialized = true;
         retStatus = 0;

      } catch (...) {
         DbgTrace(0, "IpcClientInit- Exception caught while initializing the cchannelVector\n", 0);
      }
   }
   else
   {
      // SmartCChannel vector has already been instantiated
      ReInitializeIpc();
      retStatus = 0;
   }

exit:

   DbgTrace(1, "IpcClientInit- End, status = %08X\n", retStatus);

   return retStatus;
}


//++=======================================================================
extern "C"
int
IpcClientSetUnAddress(
   IN char *pSocketFileName)
//
// Arguments In:  pSocketFileName - Pointer to string containing the name
//                                  of the socket file.
//
// Arguments Out: None.
//
// Returns:       0 == Success
//                -1 == Failure
//
// Abstract:      Method to set the socket file name to utilize for
//                communicating with the server via DOMAIN sockets.
//
// Note:          The service should have been initialized before calling
//                this procedure.
//
// L1
//=======================================================================--
{
   int   retStatus = -1;

   DbgTrace(1, "IpcClientSetUnAddress- Start\n", 0);

   // Verify that we have been initialized
   if (svcInitialized)
   {
      // Verify that the address has not already been set
      if (serverAddressSet == false)
      {
         // Set the necessary information in the serverUnAddr variable
         serverUnAddr.sun_family = AF_UNIX;
         strcpy(serverUnAddr.sun_path, pSocketFileName);

         // Set the necessary flags to indicate that DOMAIN sockets
         // should be used for communications.
         use_PF_UNIX = true;
         use_AF_INET = false;

         // Success
         serverAddressSet = true;
         retStatus = 0;
      }
      else
      {
         DbgTrace(0, "IpcClientSetUnAddress- Already set\n", 0);
      }
   }
   else
   {
      DbgTrace(0, "IpcClientSetUnAddress- Not initialized\n", 0);
   }

   DbgTrace(1, "IpcClientSetUnAddress- End, status = %08X\n", retStatus);

   return retStatus;
}


//++=======================================================================
extern "C"
int
IpcClientSetInAddress(
   IN unsigned short int serverPort,
   IN uint32_t serverAddress)
//
// Arguments In:  serverPort - Server's listening port number.
// 
//                serverAddress - The server's IP Address. Use
//                                0x7F000001 if the server is local.
//
// Arguments Out: None.
//
// Returns:       0 == Success
//                -1 == Failure
//
// Abstract:      Method to set the address to utilize for communicating
//                with the server via TCP sockets.
// 
// Note:          The service should have been initialized before calling
//                this procedure.
//
// L1
//=======================================================================--
{
   int   retStatus = -1;

   DbgTrace(1, "IpcClientSetInAddress- Start\n", 0);

   // Verify that we have been initialized
   if (svcInitialized)
   {
      // Verify that the address has not already been set
      if (serverAddressSet == false)
      {
         // Set the necessary information in the serverInAddr variable
         serverInAddr.sin_family = AF_INET;
         serverInAddr.sin_port = htons(serverPort);
         serverInAddr.sin_addr.s_addr = htonl(serverAddress);

         // Set the necessary flags to indicate that TCP sockets
         // should be used for communications.
         use_AF_INET = true;
         use_PF_UNIX = false;

         // Success
         serverAddressSet = true;
         retStatus = 0;
      }
      else
      {
         DbgTrace(0, "IpcClientSetInAddress- Already set\n", 0);
      }
   }
   else
   {
      DbgTrace(0, "IpcClientSetInAddress- Not initialized\n", 0);
   }

   DbgTrace(1, "IpcClientSetInAddress- End, status = %08X\n", retStatus);

   return retStatus;
}


//++=======================================================================
extern "C"
void
IpcClientShutdown(void)
//
// Arguments In:  None.
//
// Arguments Out: None.
//
// Returns:       Nothing.
//
// Abstract:      Method to shutdown the IPC infrastructure for process.
//
// L2
//=======================================================================--
{
   DbgTrace(1, "IpcClientShutdown- Start\n", 0);

   ReInitializeIpc();

   // Free the AppName string if necessary
   if (pAppName != unInitialized)
   {
      delete[] pAppName;
      pAppName = unInitialized;
   }

   DbgTrace(1, "IpcClientShutdown- End\n", 0);
}


//++=======================================================================
SmartCChannel *
getCChannel(void)
//
// Arguments In:  Nothing.
//
// Arguments Out: Nothing.
//
// Returns:       Pointer to SmartCChannel object if successful, otherwise
//                NULL.
//
// Abstract:      Method to get a SmartCChannel for submitting a request.
//
// L2
//=======================================================================--
{
   SmartCChannel  *pSmartCChannel = NULL;
   int            channelSelector = (numChannelSubmits++) % numCChannels;

   DbgTrace(1, "IPCCLNT -getCChannel- Start\n", 0);

   // Just exit if the server address has not been set
   if (!serverAddressSet)
   {
      DbgTrace(0, "IPCCLNT -getCChannel- Server address not set\n", 0);
      goto exit;
   }

   // Obtain client mutex
   pthread_mutex_lock(&clientMutex);

   // Check if there is an available and usable channel for the client
   if (cchannelVector[channelSelector].getPointer() != NULL
       && (*cchannelVector[channelSelector].getPointer())->ok())
   {
      // Use the available channel
      pSmartCChannel = new SmartCChannel(*cchannelVector[channelSelector].getPointer());
   }
   else
   {
      // The channel is either unavailable or unusable, clean up
      // the channel if it is indeed unusable.
      if (cchannelVector[channelSelector].getPointer() != NULL)
      {
         // Clean up the channel
         CChannel *pCChannel = *cchannelVector[channelSelector].getPointer();
         pCChannel->closeChannel();
         delete cchannelVector[channelSelector].getPointer();
         cchannelVector[channelSelector].setPointer(NULL);
      }

      CChannel *pCChannel;
      try {

         // Use the appropriate server address when instantiating
         // the CChannel object.
         if (use_PF_UNIX)
         {
            // PF_UNIX
            pCChannel = new CChannel(&serverUnAddr);
         }
         else
         {
            // Assume AF_INET
            pCChannel = new CChannel(&serverInAddr);
         }
   
         // CChannel object created, now associate a SmartCChannel
         // object with it. It is important to do this to keep
         // the object from being deleted as we initialize it.
         cchannelVector[channelSelector].setPointer(new SmartCChannel(pCChannel));
   
         // Initialize the CChannel
         if (pCChannel->init() == 0)
         {
            // CChannel initialization succeeded, use it to
            // satisfy the caller.
            pSmartCChannel = new SmartCChannel(*cchannelVector[channelSelector].getPointer());
         }
         else
         {
            // CChannel initialization failed
            delete cchannelVector[channelSelector].getPointer();
            cchannelVector[channelSelector].setPointer(NULL);
         }
      }
      catch (...) {
         DbgTrace(0, "getCChannel- Exception caught\n", 0);

         // Try to clean things up just in case
         if (cchannelVector[channelSelector].getPointer())
         {
            delete cchannelVector[channelSelector].getPointer();
            cchannelVector[channelSelector].setPointer(NULL);
         }
         else
         {
            if (pCChannel != NULL)
               delete pCChannel;
         }
      }
   }

   // Release client mutex
   pthread_mutex_unlock(&clientMutex);

exit:

   DbgTrace(1, "getCChannel- End, Obj = %08X\n", pSmartCChannel);

   return pSmartCChannel;
}


//++=======================================================================
extern "C"
int
IpcClientSubmitReq(
   IN char *pClientData,
   IN int clientDataLen,
   INOUT char **ppServerData,
   INOUT int *pServerDataLen)
//
// Arguments In:  pClientData - Pointer to client data that must be sent to
//                              the server. Buffer is NEVER released by the
//                              procedure.
//
//                clientDataLen - Length of the client data.
//
// Arguments Out: ppServerData - Pointer to variable that will receive a
//                               pointer to the buffer containing the data
//                               received from the server.
//
//                pServerDataLen - Pointer to variable that will receive the
//                                 length of the data received from the server.
//
// Returns:       0 == Request completed gracefully
//                -1 == Request did not complete gracefully
//
// Abstract:      Method to submit a request.
//
// Note: The routine blocks until the request completes.
//
// L2
//=======================================================================--
{
   int            retStatus = -1;

   DbgTrace(1, "IpcClientSubmitReq- Start\n", 0);

   try {
      SmartCChannel *pSmartCChannel;

      // Perform the following in a loop to deal with abnormal connection terminations
      unsigned long  rpcRetryCount = 0;
      while (rpcRetryCount < MAX_RPC_RETRIES)
      {
         // Get SmartCChannel
         pSmartCChannel = getCChannel();
         if (pSmartCChannel != NULL)
         {
            // Get pointer to channel object
            CChannel *pCChannel = *pSmartCChannel;

            // Allocate a requestId
            uint32_t reqId = pCChannel->allocReqId();

            // Allocate client request object.
            ClientReq clientReq(reqId);

            // Submit the request via the channel
            if (pCChannel->submitReq(reqId,
                                     clientReq,
                                     pClientData,
                                     clientDataLen) == 0)
            {
               // Request submission over the channel succeeded, now
               // wait for the completion of the request.
               clientReq.waitForCompletion(ppServerData,
                                           pServerDataLen);

               // Remove the request from the channel
               pCChannel->removeReq(reqId);

               // Now proceed based on the completion status
               ClientReq::CompletionStatus compStatus = clientReq.completionStatus();
               if (compStatus == ClientReq::SuccessCompletionStatus)
               {
                  // Success
                  retStatus = 0;
               }
            }
            else
            {
               DbgTrace(0, "IpcClientSubmitReq- Request submittion over the channel failed\n", 0);

               // Remove the request from the channel
               pCChannel->removeReq(reqId);
            }

            // Delete the SmartCChannel
            delete pSmartCChannel;
         }
         else
         {
            DbgTrace(0, "IpcClientSubmitReq- Channel unavailable\n", 0);
         }

         // Stop trying if the RPC succeeded
         if (retStatus == 0)
            break;

         // Account for this RPC try
         rpcRetryCount ++;
         DbgTrace(0, "IpcClientSubmitReq- Will attempt to retry RPC, count = %d\n", rpcRetryCount);
      }
   }
   catch(...) {

      DbgTrace(0, "IpcClientSubmitReq-- Exception caught\n", 0);
   }

   DbgTrace(1, "IpcClientSubmitReq- End, retStatus = %08X\n", retStatus);

   return retStatus;
}

//=========================================================================
//=========================================================================