/***********************************************************************
 * 
 *  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"
#include "remoteendpoint.h"


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

extern
unsigned long numCChannelObjects;

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

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

#define DEFAULT_MAX_RPC_RETRIES  3

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

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

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

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

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

// Application threaded information
bool  appMultithreaded;

// Client mutex
pthread_mutex_t   clientMutex;

// Mutex for interlocked operations
pthread_mutex_t   interlockedMutex;

// Indicators
bool  svcInitialized = false;

// Map of open remote endpoints.
//
// This map contains all of the open remote
// endpoint objects. The key used to obtain
// RemoteEndPoint objects from the map is an
// object handle.
//
typedef map<uint32_t, SmartPtr<RemoteEndPoint>*> REPMap;
typedef REPMap::iterator REPMapIter;
typedef pair<REPMapIter, bool> REPIterBoolPair;
REPMap   repMap;

// RemoteEndPoint handle allocator
uint32_t remoteEndPointHandleAllocator = 1;


//++=======================================================================
extern "C"
int
IpcClientOpenUnixRemoteEndPoint(
   IN char *pSocketFileName,
   IN int maxRpcRetries,
   INOUT uint32_t *pEndPointHandle)
//
// Arguments In:  port - Server's listening port number.
// 
//                address - The server's IP Address. Use
//                          0x7F000001 if the server is local.
// 
//                maxRpcRetries - Maximum number of Rpc retries that
//                                should be utilized when submitting
//                                a request to the endpoint. A value
//                                of zero requests that the default
//                                setting be utilized.
//
// Arguments Out: pEndPointHandle - Pointer to variable that will receive
//                                  the endpoint handle.
//
// Returns:       0 == Success
//                -1 == Failure
//
// Abstract:      Method to open a UNIX (PF_UNIX) remote endpoint.
// 
// Note:          The service should have been initialized before calling
//                this procedure.
//
// L2
//=======================================================================--
{
   int   retStatus = -1;

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

   // Verify the input parameters
   if (pSocketFileName == NULL
       || pEndPointHandle == NULL)
   {
      DbgTrace(0, "IpcClientOpenUnixRemoteEndPoint- Invalid parameter\n", 0);
      goto exit;
   }

   // Verify that we have been initialized
   if (svcInitialized)
   {
      // Set the default max rpc retry value if necessary
      if (maxRpcRetries == 0)
         maxRpcRetries = DEFAULT_MAX_RPC_RETRIES;

      // Acquire our mutex
      pthread_mutex_lock(&clientMutex);

      try {
         // Instantiate a RemoteEndPoint object and keep track of it
         // with a smart pointer.
         SmartRemoteEndPoint *pSmartRemoteEndPoint = new SmartRemoteEndPoint(new RemoteEndPoint(appMultithreaded,
                                                                                                maxRpcRetries,
                                                                                                pSocketFileName));

         // Allocate a handle for the endpoint 
         uint32_t handle = remoteEndPointHandleAllocator ++;

         // Insert the new RemoteEndPoint into the REP map
         REPIterBoolPair insertResult;
         insertResult = repMap.insert(make_pair(handle, pSmartRemoteEndPoint));
         if (!insertResult.second)
         {
            // Insertion failed
            DbgTrace(0, "IpcClientOpenUnixRemoteEndPoint- Unable to insert RemoteEndPoint into REP\n", 0);
            delete pSmartRemoteEndPoint;
         }
         else
         {
            // RemoteEndPoint inserted in the REP map, success.
            *pEndPointHandle = handle;
            retStatus = 0;
         }
      } catch (...) {
         DbgTrace(0, "IpcClientOpenUnixRemoteEndPoint- Exception caught\n", 0);
      }

      pthread_mutex_unlock(&clientMutex);
   }
   else
   {
      DbgTrace(0, "IpcClientOpenUnixRemoteEndPoint- Not initialized\n", 0);
   }

exit:

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

   return retStatus;
}


//++=======================================================================
extern "C"
int
IpcClientOpenInetRemoteEndPoint(
   IN unsigned short int port,
   IN uint32_t address,
   IN int maxRpcRetries,
   INOUT uint32_t *pEndPointHandle)
//
// Arguments In:  port - Server's listening port number.
// 
//                address - The server's IP Address. Use
//                          0x7F000001 if the server is local.
// 
//                maxRpcRetries - Maximum number of Rpc retries that
//                                should be utilized when submitting
//                                a request to the endpoint. A value
//                                of zero requests that the default
//                                setting be utilized.
//
// Arguments Out: pEndPointHandle - Pointer to variable that will receive
//                                  the endpoint handle.
//
// Returns:       0 == Success
//                -1 == Failure
//
// Abstract:      Method to open a TCP (AF_INET) remote endpoint.
// 
// Note:          The service should have been initialized before calling
//                this procedure.
//
// L2
//=======================================================================--
{
   int   retStatus = -1;

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

   // Verify the input parameters
   if (pEndPointHandle == NULL)
   {
      DbgTrace(0, "IpcClientOpenInetRemoteEndPoint- Invalid parameter\n", 0);
      goto exit;
   }

   // Verify that we have been initialized
   if (svcInitialized)
   {
      // Set the default max rpc retry value if necessary
      if (maxRpcRetries == 0)
         maxRpcRetries = DEFAULT_MAX_RPC_RETRIES;

      // Acquire our mutex
      pthread_mutex_lock(&clientMutex);

      try {
         // tbd - add code to allow us to share endpoints to the same destination that are already opened
         // 
         // Instantiate a RemoteEndPoint object and keep track of it
         // with a smart pointer.
         SmartRemoteEndPoint *pSmartRemoteEndPoint = new SmartRemoteEndPoint(new RemoteEndPoint(appMultithreaded,
                                                                                                maxRpcRetries,
                                                                                                port,
                                                                                                address));

         // Allocate a handle for the endpoint 
         uint32_t handle = remoteEndPointHandleAllocator ++;

         // Insert the new RemoteEndPoint into the REP map
         REPIterBoolPair insertResult;
         insertResult = repMap.insert(make_pair(handle, pSmartRemoteEndPoint));
         if (!insertResult.second)
         {
            // Insertion failed
            DbgTrace(0, "IpcClientOpenInetRemoteEndPoint- Unable to insert RemoteEndPoint into REP\n", 0);
            delete pSmartRemoteEndPoint;
         }
         else
         {
            // RemoteEndPoint inserted in the REP map, success.
            *pEndPointHandle = handle;
            retStatus = 0;
         }
      } catch (...) {
         DbgTrace(0, "IpcClientOpenInetRemoteEndPoint- Exception caught\n", 0);
      }

      pthread_mutex_unlock(&clientMutex);
   }
   else
   {
      DbgTrace(0, "IpcClientOpenInetRemoteEndPoint- Not initialized\n", 0);
   }

exit:

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

   return retStatus;
}


//++=======================================================================
extern "C"
int
IpcClientCloseRemoteEndPoint(
   IN uint32_t endPointHandle)
//
// Arguments In:  endpointHandle - Handle of the endpoint being closed.
// 
//
// Arguments Out: None.
//                                  the endpoint handle.
//
// Returns:       0 == Success
//                -1 == Failure
//
// Abstract:      Method to close a remote endpoint.
// 
// Note:          The service should have been initialized before calling
//                this procedure.
//
// L2
//=======================================================================--
{
   int   retStatus = -1;

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

   // Verify that we have been initialized
   if (svcInitialized)
   {
      // Acquire our mutex
      pthread_mutex_lock(&clientMutex);

      // Find the appropriate RemoteEndPoint object in the REP Map using
      // the handle provided by the caller.
      REPMapIter iter = repMap.find(endPointHandle);
      if (iter != repMap.end())
      {
         // Object was found in the map, remove it.
         SmartRemoteEndPoint *pSmartRemoteEndPoint = iter->second;
         repMap.erase(iter);

         // Release our mutex before deleting the endpoint
         pthread_mutex_unlock(&clientMutex);

         // Close the endpoint
         delete pSmartRemoteEndPoint;

         // Success
         retStatus = 0;
      }
      else
      {
         DbgTrace(0, "IpcClientCloseRemoteEndPoint- Invalid handle\n", 0);

         // Release our mutex
         pthread_mutex_unlock(&clientMutex);
      }
   }
   else
   {
      DbgTrace(0, "IpcClientCloseRemoteEndPoint- Not initialized\n", 0);
   }

exit:

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

   return retStatus;
}


//++=======================================================================
extern "C"
int
IpcClientSubmitReq(
   IN uint32_t endPointHandle,
   IN char *pClientData,
   IN int clientDataLen,
   INOUT char **ppServerData,
   INOUT int *pServerDataLen)
//
// Arguments In:  endPointHandle - Handle of the remote endpoint that will
//                                 be the target of the request.
// 
//                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.
//
//                               The returned buffer always contains a NULL after the
//                               data indicated. You may be able to leverage this to
//                               treat the data as a NULL terminated string in cases
//                               where the request consists of ASCII characters.
// 
//                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.
//
// Notes:         The routine blocks until the request completes.
// 
//                The buffer returned with the server data must be released
//                by the calling application by calling free().
//
// L2
//=======================================================================--
{
   int            retStatus = -1;

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

   // Verify that we have been initialized
   if (svcInitialized)
   {
      // Acquire our mutex
      pthread_mutex_lock(&clientMutex);

      // Find the appropriate RemoteEndPoint object in the REP Map using
      // the handle provided by the caller.
      REPMapIter iter = repMap.find(endPointHandle);
      if (iter != repMap.end())
      {
         // Object was found in the map, use it to submit
         // the request.
         SmartRemoteEndPoint *pSmartRemoteEndPoint = new SmartRemoteEndPoint(*(iter->second));

         // Release our mutex before deleting the endpoint
         pthread_mutex_unlock(&clientMutex);

         // Submit the request
         retStatus = (*pSmartRemoteEndPoint)->submitReq(pClientData,
                                                        clientDataLen,
                                                        ppServerData,
                                                        pServerDataLen);

         // Get rid of the reference to the remote endpoint
         delete pSmartRemoteEndPoint;
      }
      else
      {
         DbgTrace(0, "IpcClientSubmitReq- Invalid handle\n", 0);

         // Release our mutex
         pthread_mutex_unlock(&clientMutex);
      }
   }
   else
   {
      DbgTrace(0, "IpcClientSubmitReq- Not initialized\n", 0);
   }

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

   return retStatus;
}


//++=======================================================================
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.
//
// L2
//=======================================================================--
{
   int   retStatus = -1;

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

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

   // Verify that we have not been initialized already
   if (!svcInitialized)
   {
      // 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 app multithreaded information
      appMultithreaded = multithreaded;

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

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

      // Success
      svcInitialized = true;
      retStatus = 0;
   }
   else
   {
      DbgTrace(0, "IpcClientInit- Initialized already\n", 0);
   }

exit:

   DbgTrace(1, "IpcClientInit- 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);

   // Verify that we have been initialized
   if (svcInitialized)
   {
      // Forget about having been initialized
      svcInitialized = false;

      // Clean up the REP map
      pthread_mutex_lock(&clientMutex);
      while (!repMap.empty())
      {
         REPMapIter iter = repMap.begin();
         SmartRemoteEndPoint *pSmartRemoteEndPoint = iter->second;
         repMap.erase(iter);
         pthread_mutex_unlock(&clientMutex);

         delete pSmartRemoteEndPoint;
         pthread_mutex_lock(&clientMutex);
      }
      pthread_mutex_unlock(&clientMutex);

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

      // Wait until all of the channels are gone
      while (numCChannelObjects)
         sleep(0);   // Only suffer a time-slice
      sleep(0);
   }
   else
   {
      DbgTrace(0, "IpcClientShutdown- Not initialized\n", 0);
   }

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


//++=======================================================================
static void __attribute__((destructor))
so_fini()
//
// Arguments In:  None.
//
// Arguments Out: None.
//
// Returns:       Nothing.
//
// Abstract:      Library un-initialization routine.
//
// L2
//=======================================================================--
{
   DbgTrace(0, "so_fini- Start\n", 0);
   IpcClientShutdown();
}


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