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

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

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

extern int
ServiceRequest(
   ServerReq *pServerReq);

extern void
RemoveFromSChannelList(
   SChannel *pSChannel);


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

//
// Socket Mapping definitions
//
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#define LINGER struct linger
#define SOCKADDR_IN struct sockaddr_in
#define closesocket close

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

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

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

//
// Object Counters
//
unsigned long numSChannelObjects = 0;


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

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

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


//++=======================================================================
SChannel::SChannel(
   int connSocket) :

   m_state (State_Connected),
   m_socket (connSocket)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   DbgTrace(1, "SChannel::SChannel- Start, Obj = %08X\n", this);

   // Initialize the mutex
   if (pthread_mutex_init(&m_mutex, NULL) != 0)
   {
      DbgTrace(0, "SChannel::SChannel- Mutex initialization failed\n", 0);

      // Throw exception
      throw bad_alloc();
   }

   // Increment the object count
   InterlockedIncrement(&numSChannelObjects);

   DbgTrace(1, "SChannel::SChannel- End\n", 0);

}  /*-- SChannel::SChannel() --*/


//++=======================================================================
SChannel::~SChannel(void)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   DbgTrace(1, "SChannel::~SChannel- Start, Obj = %08X\n", this);

   // Cleanup resources allocated for the object
   pthread_mutex_destroy(&m_mutex);

   // Free connection socket if necessary
   if (m_socket != INVALID_SOCKET)
   {
      shutdown(m_socket, SHUT_RDWR);
      struct linger linger_opt = {1, 15};
      setsockopt(m_socket, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(linger_opt));
      closesocket(m_socket);
   }

   // Decrement the object count
   InterlockedDecrement(&numSChannelObjects);

   DbgTrace(1, "SChannel::~SChannel- End\n", 0);

}  /*-- SChannel::~SChannel() --*/


//++=======================================================================
int
SChannel::init(void)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   int            retStatus = -1;
   SmartSChannel  *pSmartSChannel = NULL;

   DbgTrace(1, "SChannel::init- Start, Obj = %08X\n", this);

   // Verify the state of the object
   if (m_state == State_Connected)
   {
      // Launch a thread to service the channel connection
      try {
         // Create a SmartSChannel object to make sure that the object
         // does not get deleted prematurely.
         pSmartSChannel = new SmartSChannel(this);

         // Create the channel connection thread
         pthread_t thread;
         int threadCreateStatus = pthread_create(&thread,
                                                 NULL,
                                                 (void*(*)(void*))SChannel::connectionThread,
                                                 pSmartSChannel);
         if (threadCreateStatus == 0)
         {
            // We succeeded
            retStatus = 0;
         }
         else
         {
            DbgTrace(0, "SChannel::init- Unable to create channel connection thread, error = %08X\n", threadCreateStatus);
         }
      }
      catch (...) {
         DbgTrace(0, "SChannel::init- Exception caught creating smart pointer\n", 0);
      }
   }
   else
   {
      DbgTrace(0, "SChannel::init- invalid state, state = %d\n", m_state);
   }

   // Deal with initialization failures
   if (retStatus)
   {
      // Adjust the object state
      m_state = State_FailedInitialization;

      // Free SmartSChannel just in case
      delete pSmartSChannel;
   }

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

   return retStatus;

}  /*-- SChannel::init() --*/


//++=======================================================================
void*
SChannel::connectionThread(
   SmartPtr<SChannel> *pSmartSChannel)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   SChannel       *pSChannel = *pSmartSChannel;
   bool           doneReceivingData = false;
   unsigned long  bytesReceived;
   unsigned long  bytesSent;
   uint32_t       reqId;
   int            payloadLength;
   unsigned long  totalPayloadBytesReceived = 0;
   char           reqDataPktHdr[ReqDataPktHdrTemplate.length()];
   char           reqErrorPktHdr[ReqErrorPktHdrTemplate.length()];
   char           *pRecvBuff;
   ServerReq      *pServerReq;

   DbgTrace(1, "SChannel::connectionThread- Start, Obj = %08X\n", pSChannel);

   // Set the thread in the detached state so that it is cleaned up when it exits
	pthread_detach(pthread_self());

   // Check that we are still connected
   if (pSChannel->m_state == State_Connected)
   {
      // Receive and process channel data
      while (!doneReceivingData)
      {
         DbgTrace(2, "SChannel::connectionThread- Receive Loop, Obj = %08X\n", pSChannel);

         // Receive the ReqDataPktHdr. Note, if we add other packet types and if the
         // packet types have different header lengths, then we will need to modify
         // this code to first receive the packet type and then receive the rest
         // of the header based on type.
         while (1)
         {
            bytesReceived = recv(pSChannel->m_socket,
                                 reqDataPktHdr,
                                 ReqDataPktHdrTemplate.length(),
                                 MSG_WAITALL);
            if (bytesReceived != SOCKET_ERROR
                || errno != EINTR)
            {
               break;
            }
         }
         if (bytesReceived != SOCKET_ERROR)
         {
            // Check if the connection was terminated
            if (bytesReceived == ReqDataPktHdrTemplate.length())
            {
               // Get the reqId and payload length
               if (ChannelProto::getReqIdAndPayloadLength(reqDataPktHdr,
                                                          sizeof(reqDataPktHdr),
                                                          &reqId,
                                                          &payloadLength))
               {
                  // Procced based on the packet type
                  switch (ChannelProto::getPktType(*reqDataPktHdr))
                  {
                     case ChannelProto::ReqDataCarrierPacketType:

                        DbgTrace(2, "SChannel::connectionThread- Processing Request Data Packet, Obj = %08X\n", pSChannel);

                        // Allocate a buffer big enough to receive the payload. Allow space to NULL terminate.
                        pRecvBuff = new char[payloadLength + 1];
                        if (pRecvBuff != NULL)
                        {
                           pRecvBuff[payloadLength] = '\0';

                           // Buffer allocated, receive the Req payload.
                           while (1)
                           {
                              bytesReceived = recv(pSChannel->m_socket,
                                                   pRecvBuff,
                                                   payloadLength,
                                                   MSG_WAITALL);
                              if (bytesReceived != SOCKET_ERROR
                                  || errno != EINTR)
                              {
                                 break;
                              }
                           }
                           if (bytesReceived != SOCKET_ERROR)
                           {
                              // Verify that we received all of the payload
                              if (bytesReceived == payloadLength)
                              {
                                 // Received all of the payload data
                                 totalPayloadBytesReceived += bytesReceived;

                                 // Instantiate ServerReq object
                                 bool reqProcessingStartedSuccessfully = false;

                                 try {
                                    pServerReq = new ServerReq(pSChannel,
                                                               reqId,
                                                               pRecvBuff,
                                                               bytesReceived);
                                 }
                                 catch (...) {
                                    DbgTrace(0, "SChannel::connectionThread- Exception caught creating ServerReq obj\n", 0);
                                 }

                                 // Acquire exclusive access to the SChannel object
                                 pthread_mutex_lock(&pSChannel->m_mutex);

                                 if (pServerReq)
                                 {
                                    // Forget about the receive buffer
                                    pRecvBuff = NULL;

                                    // Start processing the Request
                                    if (ServiceRequest(pServerReq) != 0)
                                    {
                                       // Failed to start processing of the Request, delete the ServerReq object.
                                       DbgTrace(0, "SChannel::connectionThread- StartRequest failed, Obj = %08X\n", pSChannel);
                                       delete pServerReq;
                                    }
                                    else
                                    {
                                       reqProcessingStartedSuccessfully = true;
                                    }
                                 }
                                 else
                                 {
                                    //DbgTrace(1, "SChannel::connectionThread- Failed to obtain idle ServerReq, Obj = %08X\n", pSChannel);
                                    DbgTrace(0, "SChannel::connectionThread- Failed to obtain idle ServerReq, Obj = %08X\n", pSChannel);
                                 }

                                 // Check if we must send an Request Error packet back to the client
                                 if (reqProcessingStartedSuccessfully == false)
                                 {
                                    // Build ReqErrorHeader
                                    if (ChannelProto::buildReqErrorPktHdr(reqId,
                                                                          0,
                                                                          reqErrorPktHdr) == 0)
                                    {
                                       // Packet header was built, now sent it to the client.
                                       bytesSent = send(pSChannel->m_socket,
                                                        reqErrorPktHdr,
                                                        sizeof(reqErrorPktHdr),
                                                        MSG_NOSIGNAL);
                                       if (bytesSent != sizeof(reqErrorPktHdr))
                                       {
                                          DbgTrace(1, "SChannel::connectionThread- Connection aborted prematurely, Obj = %08X\n", pSChannel);
                                          //printf("SChannel::connectionThread- 1Connection aborted prematurely, Obj = %08X\n", pSChannel);
                                          doneReceivingData = true;
                                       }
                                    }
                                    else
                                    {
                                       DbgTrace(0, "SChannel::connectionThread- Error building Req End Pkt Header, Obj = %08X\n", pSChannel);
                                    }
                                 }

                                 // Release exclusive access to the SChannel object
                                 pthread_mutex_unlock(&pSChannel->m_mutex);
                              }
                              else
                              {
                                 DbgTrace(1, "SChannel::connectionThread- Connection aborted prematurely, Obj = %08X\n", pSChannel);
                                 //printf("bytesReceived = %d, payloadLength = %d\n", bytesReceived, payloadLength); 
                                 //printf("SChannel::connectionThread- 2Connection aborted prematurely, Obj = %08X\n", pSChannel);
                                 doneReceivingData = true;
                              }
                           }
                           else
                           {
                              DbgTrace(1, "SChannel::connectionThread- Connection aborted prematurely, Obj = %08X\n", pSChannel);
                              //printf("Socket error = %d\n", errno);
                              //printf("SChannel::connectionThread- 3Connection aborted prematurely, Obj = %08X\n", pSChannel);
                              doneReceivingData = true;
                           }

                           // Free receive buffer if necessary
                           if (pRecvBuff)
                              delete[] pRecvBuff;
                        }
                        else
                        {
                           DbgTrace(0, "SChannel::connectionThread- Unable to allocate receive buffer, Obj = %08X\n", pSChannel);
                           doneReceivingData = true;
                        }
                        break;

                     default:

                        DbgTrace(0, "SChannel::connectionThread- Unknown Packet Type, Obj = %08X\n", pSChannel);
                        doneReceivingData = true;
                        break;
                  }
               }
               else
               {
                  DbgTrace(1, "SChannel::connectionThread- Unable to obtain payload length, Obj = %08X\n", pSChannel);
                  doneReceivingData = true;
               }
            }
            else
            {
               DbgTrace(1, "SChannel::connectionThread- The channel connection was terminated, Obj = %08X\n", pSChannel);
               //printf("bytesReceived = %d, expected = %d\n", bytesReceived, ReqDataPktHdrTemplate.length()); 
               //printf("SChannel::connectionThread- 4The channel connection was terminated, Obj = %08X\n", pSChannel);
               doneReceivingData = true;
            }
         }
         else
         {
            DbgTrace(1, "SChannel::connectionThread- The channel connection was aborted, Obj = %08X\n", pSChannel);
            //printf("Socket error = %d\n", errno);
            //printf("SChannel::connectionThread- 5The channel connection was aborted, Obj = %08X\n", pSChannel);
            doneReceivingData = true;
         }
      }
   }

   // Acquire exclusive access to the SChannel object
   pthread_mutex_lock(&pSChannel->m_mutex);

   // Try to change the SChannel state to disconnected
   if (pSChannel->m_state == State_Connected)
      pSChannel->m_state = State_Disconnected;

   // Release exclusive access to the SChannel object
   pthread_mutex_unlock(&pSChannel->m_mutex);

   // Remove ourselves from the SChannel list
   RemoveFromSChannelList(pSChannel);

   // Free SmartSChannel
   delete pSmartSChannel;

   DbgTrace(1, "SChannel::connectionThread- End\n", 0);

   // Exit
	pthread_exit(NULL);

	return 0;	// never-reached!

}  /*-- SChannel::connectionThread() --*/


//++=======================================================================
void
SChannel::closeChannel(void)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{

   DbgTrace(1, "SChannel::closeChannel- Start, Obj = %08X\n", this);

   // Acquire SChannel mutex
   pthread_mutex_lock(&m_mutex);

   // Switch the socket state to closed
   m_state = State_Closed;

   // Check if we must close the socket
   if (m_socket != INVALID_SOCKET)
   {
      // Socket needs to be closed, this will
      // release the channel connection thread
      // if it is active.
      shutdown(m_socket, SHUT_RDWR);
      struct linger linger_opt = {1, 15};
      setsockopt(m_socket, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(linger_opt));
      closesocket(m_socket);
      m_socket = INVALID_SOCKET;
   }

   // Release SChannel mutex
   pthread_mutex_unlock(&m_mutex);

   DbgTrace(1, "SChannel::closeChannel- End\n", 0);

}  /*-- SChannel::closeChannel() --*/


//++=======================================================================
int
SChannel::sendReplyData(
   uint32_t reqId,
   char *pServerData,
   int32_t serverDataLen)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
//  Environment:
//
// L2
//=======================================================================--
{
   int            retStatus = -1;
   char           reqDataPktHdr[ReqDataPktHdrTemplate.length()];
   struct msghdr  sendmsgHdr = {0};
   struct iovec   ioVectors[2];
   unsigned long  bytesSent;
   unsigned long  totalBytesSent = 0;
   unsigned long  bytesToSend = sizeof(reqDataPktHdr) + serverDataLen;

   DbgTrace(1, "SChannel::sendReplyData- Start, Obj = %08X\n", this);

   // Acquire exclusive access to the channel object
   pthread_mutex_lock(&m_mutex);

   // Verify that the channel is connected
   if (m_state == State_Connected)
   {
      // Build ReqDataHeader
      if (ChannelProto::buildReqDataPktHdr(reqId,
                                           serverDataLen,
                                           reqDataPktHdr) == 0)
      {
         // Packet header was built, now sent it along with the client data to
         // the server.
         ioVectors[0].iov_base = reqDataPktHdr;
         ioVectors[0].iov_len = sizeof(reqDataPktHdr);
         ioVectors[1].iov_base = (char*) pServerData;
         ioVectors[1].iov_len = serverDataLen;
         sendmsgHdr.msg_iov = ioVectors;
         sendmsgHdr.msg_iovlen = 2;
         while (1)
         {
            bytesSent = sendmsg(m_socket,
                                &sendmsgHdr,
                                MSG_NOSIGNAL);
            if (bytesSent == SOCKET_ERROR)
            {
               // Check if we were interrupted during the transfer
               if (errno == EINTR)
               {
                  // Just try again
                  continue;
               }

               // An unrecoverable error was encountered during the send operation,
               // assume there was a communication failure. Close the socket to make
               // sure that the connectionThread cleans up.
               //printf("SChannel::sendReplyData- sendmsgn error, totalBytesSent = %d, bytesToSend = %d, errno = %d\n", totalBytesSent, bytesToSend, errno);
               DbgTrace(0, "SChannel::sendReplyData- sendmsgn error, errno = %d\n", errno);
               m_state = State_Disconnected;
               shutdown(m_socket, SHUT_RDWR);
               struct linger linger_opt = {1, 15};
               setsockopt(m_socket, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(linger_opt));
               closesocket(m_socket);
               m_socket = INVALID_SOCKET;
               break;
            }
            else
            {
               // Account for the bytes sent
               totalBytesSent += bytesSent;

               // Check if we are done sending all of the data
               if (totalBytesSent >= bytesToSend)
               {
                  // We are done
                  break;
               }
               else
               {
                  // Adjust the ioVector structure to send data not yet sent
                  if (totalBytesSent >= sizeof(reqDataPktHdr))
                  {
                     // The packet header was sent, use only one ioVector.
                     int   serverDataAlreadySent = totalBytesSent - sizeof(reqDataPktHdr);
                     ioVectors[0].iov_base = (char*) pServerData + serverDataAlreadySent;
                     ioVectors[0].iov_len = serverDataLen - serverDataAlreadySent;
                     sendmsgHdr.msg_iov = ioVectors;
                     sendmsgHdr.msg_iovlen = 1;
                  }
                  else
                  {
                     // Not all of the packet header was sent, use two ioVectors.
                     ioVectors[0].iov_base = (char*) reqDataPktHdr + totalBytesSent;
                     ioVectors[0].iov_len = sizeof(reqDataPktHdr) - totalBytesSent;
                     ioVectors[1].iov_base = (char*) pServerData;
                     ioVectors[1].iov_len = serverDataLen;
                     sendmsgHdr.msg_iov = ioVectors;
                     sendmsgHdr.msg_iovlen = 2;
                  }
               }
            }
         }

         // Return success even if the send failed to allow things to be cleaned up
         // by the connectionThread routine.
         retStatus = 0;
      }
      else
      {
         DbgTrace(0, "SChannel::sendReplyData- Error building Req Data Pkt Header, Obj = %08X\n", this);
      }
   }
   else
   {
      DbgTrace(1, "SChannel::sendReplyData- Channel not connected, state = %08X\n", m_state);
   }

   // Release exclusive access to the channel object
   pthread_mutex_unlock(&m_mutex);

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

   return retStatus;

}  /*-- SChannel::sendReplyData() --*/


//++=======================================================================
int
SChannel::sendReplyError(
   uint32_t reqId)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
//  Environment:
//
// L2
//=======================================================================--
{
   int            retStatus = -1;
   char           reqErrorPktHdr[ReqErrorPktHdrTemplate.length()];
   struct msghdr  sendmsgHdr = {0};
   struct iovec   ioVectors[1];
   unsigned long  bytesSent;
   unsigned long  totalBytesSent = 0;
   unsigned long  bytesToSend = sizeof(reqErrorPktHdr);

   DbgTrace(1, "SChannel::sendReplyError- Start, Obj = %08X\n", this);

   // Acquire exclusive access to the channel object
   pthread_mutex_lock(&m_mutex);

   // Verify that the channel is connected
   if (m_state == State_Connected)
   {
      // Build ReqErrorHeader
      if (ChannelProto::buildReqErrorPktHdr(reqId,
                                            0,
                                            reqErrorPktHdr) == 0)
      {
         // Packet header was built, now sent it along with the client data to
         // the server.
         ioVectors[0].iov_base = reqErrorPktHdr;
         ioVectors[0].iov_len = sizeof(reqErrorPktHdr);
         sendmsgHdr.msg_iov = ioVectors;
         sendmsgHdr.msg_iovlen = 1;
         while (1)
         {
            bytesSent = sendmsg(m_socket,
                                &sendmsgHdr,
                                MSG_NOSIGNAL);
            if (bytesSent == SOCKET_ERROR)
            {
               // Check if we were interrupted during the transfer
               if (errno == EINTR)
               {
                  // Just try again
                  continue;
               }

               // An unrecoverable error was encountered during the send operation,
               // assume there was a communication failure. Close the socket to make
               // sure that the connectionThread cleans up.
               //printf("SChannel::sendReplyError- sendmsgn error, totalBytesSent = %d, bytesToSend = %d, errno = %d\n", totalBytesSent, bytesToSend, errno);
               DbgTrace(0, "SChannel::sendReplyError- sendmsgn error, errno = %d\n", errno);
               m_state = State_Disconnected;
               shutdown(m_socket, SHUT_RDWR);
               struct linger linger_opt = {1, 15};
               setsockopt(m_socket, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(linger_opt));
               closesocket(m_socket);
               m_socket = INVALID_SOCKET;
               break;
            }
            else
            {
               // Account for the bytes sent
               totalBytesSent += bytesSent;

               // Check if we are done sending all of the data
               if (totalBytesSent >= bytesToSend)
               {
                  // We are done
                  break;
               }
               else
               {
                  // Adjust the ioVector structure to send data not yet sent
                  ioVectors[0].iov_base = (char*) reqErrorPktHdr + totalBytesSent;
                  ioVectors[0].iov_len = sizeof(reqErrorPktHdr) - totalBytesSent;
                  sendmsgHdr.msg_iov = ioVectors;
                  sendmsgHdr.msg_iovlen = 1;
               }
            }
         }

         // Return success even if the send failed to allow things to be cleaned up
         // by the connectionThread routine.
         retStatus = 0;
      }
      else
      {
         DbgTrace(0, "SChannel::sendReplyError- Error building Req Data Pkt Header, Obj = %08X\n", this);
      }
   }
   else
   {
      DbgTrace(1, "SChannel::sendReplyError- Channel not connected, state = %08X\n", m_state);
   }

   // Release exclusive access to the channel object
   pthread_mutex_unlock(&m_mutex);

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

   return retStatus;

}  /*-- SChannel::sendReplyError() --*/


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