/***********************************************************************
 * 
 *  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 "internal.h"

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

#define INITIAL_RESPONSE_DATA_BUF_SIZE 1028
#define INCREMENT_RESPONSE_DATA_BUF_SIZE 256

#define MAX_RPC_RETRIES 3

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

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


//++=======================================================================
static
CasaStatus
CopyMultiToWideAlloc(
   IN    char *pMulti,
   IN    int multiSize,
   INOUT LPWSTR *ppWide,
   INOUT int *pWideSize)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   int      retStatus;
   int      size, i;


   DbgTrace(2, "-CopyMultiToWideAlloc- Start\n", 0);

   size = (multiSize + 1) * sizeof(WCHAR);

   if ((*ppWide = (PWCHAR) malloc(size)) != NULL)
   {
      for (i = 0; i < multiSize; i++)
      {
         *(*ppWide + i) = (unsigned char) *(pMulti + i);
      }

      *(*ppWide + i) = L'\0';

      if (pWideSize)
      {
         *pWideSize = size - sizeof(WCHAR);
      }

      retStatus = CASA_STATUS_SUCCESS;
   }
   else
   {
      retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                  CASA_FACILITY_AUTHTOKEN,
                                  CASA_STATUS_INSUFFICIENT_RESOURCES);
   }

   DbgTrace(2, "-CopyMultiToWideAlloc- End, retStatus = %08X\n", retStatus);

   return retStatus;
}


//++=======================================================================
RpcSession*
OpenRpcSession(
   IN    char *pHostName)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   RpcSession  *pSession;


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

   // Allocate space for the session
   pSession = (RpcSession*) malloc(sizeof(*pSession));
   if (pSession)
   {
      // Zero the session structure
      memset(pSession, 0, sizeof(*pSession));

      // Open a Winhttp session
      pSession->hSession = WinHttpOpen(L"CASA Client/1.0",
                                       WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
                                       WINHTTP_NO_PROXY_NAME,
                                       WINHTTP_NO_PROXY_BYPASS,
                                       0);
      if (pSession->hSession)
      {
         LPWSTR   pWideHostName;
         int      wideHostLen;

         // Session opened, now convert the host name to Unicode so that
         // we can open a connection.
         if (CopyMultiToWideAlloc(pHostName,
                                  (int) strlen(pHostName),
                                  &pWideHostName,
                                  &wideHostLen) == CASA_STATUS_SUCCESS)
         {
            // Now open connection
            pSession->hConnection = WinHttpConnect(pSession->hSession,
                                                   pWideHostName,
                                                   8080, /*INTERNET_DEFAULT_HTTP_PORT,*/
                                                   0);
            if (pSession->hConnection == NULL)
            {
               DbgTrace(0, "-OpenRpcSession- Failed to open connection, error = %d\n", GetLastError());

               // Free allocated resources
               WinHttpCloseHandle(pSession->hSession);
               free(pSession);
               pSession = NULL;
            }

            // Free the host name wide string buffer
            free(pWideHostName);
         }
         else
         {
            DbgTrace(0, "-OpenRpcSession- Error converting host name to wide string\n", 0);

            // Free allocated resources
            WinHttpCloseHandle(pSession->hSession);
            free(pSession);
            pSession = NULL;
         }
      }
      else
      {
         DbgTrace(0, "-OpenRpcSession- Failed to open session, error = %d\n", GetLastError());
      }
   }
   else
   {
      DbgTrace(0, "-OpenRpcSession- Failed to allocate buffer for rpc session\n", 0);
   }

   DbgTrace(2, "-OpenRpcSession- End, pSession = %08X\n", pSession);

   return pSession;
}


//++=======================================================================
void
CloseRpcSession(
   IN    RpcSession *pSession)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   DbgTrace(1, "-CloseRpcSession- Start\n", 0);

   // Close the connection handle
   WinHttpCloseHandle(pSession->hConnection);

   // Close the session handle
   WinHttpCloseHandle(pSession->hSession);

   // Free the space allocated for the session
   free(pSession);

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


//++=======================================================================
static
CasaStatus
InternalRpc(
   IN    RpcSession *pSession,
   IN    char *pMethod,
   IN    bool secure,
   IN    char *pRequestData,
   INOUT char **ppResponseData,
   INOUT int *pResponseDataLen)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   CasaStatus  retStatus = CASA_STATUS_SUCCESS;
   char        rpcTarget[256];
   LPWSTR      pWideRpcTarget;
   int         wideRpcTargetLen;
   WCHAR       sendHeaders[] = L"Content-Type: text/html";

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

   // Initialize output parameter
   *ppResponseData = NULL;

   // Create rpc target string and convert it to a wide string
   sprintf(rpcTarget, "CasaAuthTokenSvc/Rpc?method=%s", pMethod);
   retStatus = CopyMultiToWideAlloc(rpcTarget,
                                    (int) strlen(rpcTarget),
                                    &pWideRpcTarget,
                                    &wideRpcTargetLen);
   if (CASA_SUCCESS(retStatus))
   {
      HINTERNET   hRequest;

      // Open a request handle
      hRequest = WinHttpOpenRequest(pSession->hConnection,
                                    L"POST",
                                    pWideRpcTarget,
                                    NULL,
                                    WINHTTP_NO_REFERER,
                                    WINHTTP_DEFAULT_ACCEPT_TYPES,
                                    secure? WINHTTP_FLAG_REFRESH | WINHTTP_FLAG_SECURE : WINHTTP_FLAG_REFRESH);
      if (hRequest)
      {
         int   reqDataLen = (int) strlen(pRequestData);

         // Send the request
         if (WinHttpSendRequest(hRequest,
                                sendHeaders,
                                -1,
                                pRequestData,
                                reqDataLen,
                                reqDataLen,
                                0))
         {
            // Request sent, now await for the response.
            if (WinHttpReceiveResponse(hRequest, NULL))
            {
               WCHAR httpCompStatus[4] = {0};
               DWORD httpCompStatusLen = sizeof(httpCompStatus);

               // Response received, make sure that it completed successfully.
               if (WinHttpQueryHeaders(hRequest,
                                       WINHTTP_QUERY_STATUS_CODE,
                                       NULL,
                                       &httpCompStatus,
                                       &httpCompStatusLen,
                                       WINHTTP_NO_HEADER_INDEX))
               {
                  // Check that the request completed successfully
                  if (memcmp(httpCompStatus, L"200", sizeof(httpCompStatus)) == 0)
                  {
                     char  *pResponseData;
                     int   responseDataBufSize = INITIAL_RESPONSE_DATA_BUF_SIZE;      
                     int   responseDataRead = 0;

                     // Now read the response data, to do so we need to allocate a buffer.
                     pResponseData = (char*) malloc(INITIAL_RESPONSE_DATA_BUF_SIZE);
                     if (pResponseData)
                     {
                        char     *pCurrLocation = pResponseData;
                        DWORD    bytesRead;

                        do
                        {
                           bytesRead = 0;
                           if (WinHttpReadData(hRequest,
                                               (LPVOID) pCurrLocation,
                                               responseDataBufSize - responseDataRead,
                                               &bytesRead))
                           {
                              pCurrLocation += bytesRead;
                              responseDataRead += bytesRead;

                              // Check if we need to allocate a larger buffer
                              if (responseDataRead == responseDataBufSize)
                              {
                                 char  *pTmpBuf;

                                 // We need to upgrade the receive buffer
                                 pTmpBuf = (char*) malloc(responseDataBufSize + INCREMENT_RESPONSE_DATA_BUF_SIZE);
                                 if (pTmpBuf)
                                 {
                                    memcpy(pTmpBuf, pResponseData, responseDataBufSize);
                                    free(pResponseData);
                                    pResponseData = pTmpBuf;
                                    pCurrLocation = pResponseData + responseDataBufSize;
                                    responseDataBufSize += INCREMENT_RESPONSE_DATA_BUF_SIZE;
                                 }
                                 else
                                 {
                                    DbgTrace(0, "-InternalRpc- Buffer allocation failure\n", 0);
                                    retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                                                CASA_FACILITY_AUTHTOKEN,
                                                                CASA_STATUS_INSUFFICIENT_RESOURCES);
                                 }
                              }
                           }
                           else
                           {
                              DbgTrace(0, "-InternalRpc- Failed reading response data, error = %d\n", GetLastError());
                              retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                                          CASA_FACILITY_AUTHTOKEN,
                                                          CASA_STATUS_UNSUCCESSFUL);
                           }
                        } while (CASA_SUCCESS(retStatus)
                                 && bytesRead != 0);

                        // Check if the response data was successfully received
                        if (CASA_SUCCESS(retStatus))
                        {
                           // The response data was received, return it to the caller.
                           *ppResponseData = pResponseData;
                           *pResponseDataLen = responseDataRead; 
                        }
                        else
                        {
                           // Failed to receive the response data, free the allocated buffer.
                           free(pResponseData);
                        }
                     }
                     else
                     {
                        DbgTrace(0, "-InternalRpc- Buffer allocation failure\n", 0);
                        retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                                    CASA_FACILITY_AUTHTOKEN,
                                                    CASA_STATUS_INSUFFICIENT_RESOURCES);
                     }
                  }
                  else
                  {
                     DbgTrace(0, "-InternalRpc- HTTP request did not complete successfully, status = %S\n", httpCompStatus);
                     retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                                 CASA_FACILITY_AUTHTOKEN,
                                                 CASA_STATUS_UNSUCCESSFUL);
                  }
               }
               else
               {
                  DbgTrace(0, "-InternalRpc- Unable to obtain http request completion status, error = %d\n", GetLastError());
                  retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                              CASA_FACILITY_AUTHTOKEN,
                                              CASA_STATUS_UNSUCCESSFUL);
               }
            }
            else
            {
               DbgTrace(0, "-InternalRpc- Unable to receive response, error = %d\n", GetLastError());
               retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                           CASA_FACILITY_AUTHTOKEN,
                                           CASA_STATUS_UNSUCCESSFUL);
            }
         }
         else
         {
            int   error = GetLastError();

            DbgTrace(0, "-InternalRpc- Unsuccessful send http request, error = %d\n", error);
            if (error == ERROR_WINHTTP_CANNOT_CONNECT)
            {
               retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                           CASA_FACILITY_AUTHTOKEN,
                                           CASA_STATUS_AUTH_SERVER_UNAVAILABLE);
            }
            else
            {
               retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                           CASA_FACILITY_AUTHTOKEN,
                                           CASA_STATUS_UNSUCCESSFUL);
            }
         }

         // Close the request handle
         WinHttpCloseHandle(hRequest);
      }
      else
      {
         DbgTrace(0, "-InternalRpc- Unable to open http request, error = %d\n", GetLastError());
         retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                     CASA_FACILITY_AUTHTOKEN,
                                     CASA_STATUS_UNSUCCESSFUL);
      }

      // Free the rpc target wide string buffer
      free(pWideRpcTarget);
   }
   else
   {
      DbgTrace(0, "-InternalRpc- Error converting method name to wide string\n", 0);
   }

   DbgTrace(1, "-InternalRpc- End, retStatus = %d\n", retStatus);

   return retStatus;
}


//++=======================================================================
CasaStatus
Rpc(
   IN    RpcSession *pSession,
   IN    char *pMethod,
   IN    bool secure,
   IN    char *pRequestData,
   INOUT char **ppResponseData,
   INOUT int *pResponseDataLen)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   CasaStatus  retStatus;
   int         retries = 0;

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

   // Retry the RPC as needed
   do
   {
      // Issue the RPC
      retStatus = InternalRpc(pSession,
                              pMethod,
                              secure,
                              pRequestData,
                              ppResponseData,
                              pResponseDataLen);

      // Account for this try
      retries ++;

   } while (CasaStatusCode(retStatus) == CASA_STATUS_AUTH_SERVER_UNAVAILABLE
            && retries < MAX_RPC_RETRIES);

   DbgTrace(1, "-Rpc- End, retStatus = %d\n", retStatus);

   return retStatus;
}


//++=======================================================================
//++=======================================================================
//++=======================================================================