// Copyright (c) 2003-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
//

#include "csocket.h"
#include "chttpsecuresocket.h"
#include "mcommsinfoprovider.h"
#include "thttptrlayerpanic.h"

#include  <http/rhttpconnectioninfo.h>
#include <httpsocketconstants.h>
#include <in6_opt.h> 

const TInt KDefaultFlags		= 0;
const TInt KTcpTriggeredKeepAlive	= 2;
const TInt KSocketRecvBufSize = 16 * 1024;
const TInt KSocketSendBufSize = 16 * 1024;
const TInt KSocketDefaultSendBufSize = 4 * 1024;
const TUint32 KSoTcpLingerinMicroSec = 0x101F55F6;//linger constant to send close connection fast

CSocket* CSocket::NewL(MCommsInfoProvider& aCommsInfoProvider, TSocketType aSocketType)
/**	
	The factory constructor.
	@param		aCommsInfoProvider	The comms info provider object.
	@param		aSocketType			The type of the socket.
	@return		A pointer to a fully constructed object.
*/
	{
	CSocket* self = new (ELeave) CSocket(aCommsInfoProvider);
	CleanupStack::PushL(self);
	self->ConstructL(aSocketType);
	CleanupStack::Pop(self);
	return self;
	}

CSocket* CSocket::New(MCommsInfoProvider& aCommsInfoProvider, TSocketType aSocketType)
    {
    CSocket* self = new CSocket(aCommsInfoProvider);
    if(self)
        {
        TInt error = self->Construct(aSocketType);
        if(error != KErrNone)
            {
            delete self;
            self = NULL;
            }
        }
    return self;
    }

CSocket::~CSocket()
/**
	Destructor.
*/
	{
	// Close socket and notify the socket status observer
	if( iSecureSocketController )
		delete iSecureSocketController;
	else
		iSocket.Close();
	}

CSocket::CSocket(MCommsInfoProvider& aCommsInfoProvider)
: CBase(), iCommsInfoProvider(aCommsInfoProvider)
/**	
	Constructor.
	@param		aCommsInfoProvider	The comms info provider object.
*/
	{
	}

void CSocket::ConstructL(TSocketType aSocketType)
/**	
	Second phase constructor.
	@param		aSocketType	The type of the socket.
*/
	{
    User::LeaveIfError(Construct(aSocketType));
	}

TInt CSocket::Construct(TSocketType aSocketType)
    {
    TInt error = KErrNone;
    switch( aSocketType )
        {
    case EProtocolSocket:
        {
        if ( iCommsInfoProvider.HasConnection() )
            {
            // Open a protocol socket with a RConnection
            error = iSocket.Open(iCommsInfoProvider.SocketServer(),
                                 iCommsInfoProvider.ProtocolDescription().iAddrFamily, 
                                 iCommsInfoProvider.ProtocolDescription().iSockType, 
                                 iCommsInfoProvider.ProtocolDescription().iProtocol,
                                 iCommsInfoProvider.Connection()
                                 );              
            }
        else
            {
            // Open a protocol socket without a RConnection, we don't need one ( Loopback address )
            error = iSocket.Open(iCommsInfoProvider.SocketServer(),
                                 iCommsInfoProvider.ProtocolDescription().iAddrFamily, 
                                 iCommsInfoProvider.ProtocolDescription().iSockType, 
                                 iCommsInfoProvider.ProtocolDescription().iProtocol
                                 );                                          
            }
        } break;
    case EBlankSocket:
        {
        // Open a blank socket
        error = iSocket.Open(iCommsInfoProvider.SocketServer());
        } break;
    default:
        User::Invariant();
        }
    if(error == KErrNone)
        {
        TInt id = iCommsInfoProvider.SessionId();
        if(id>=0) 
            {
            // set socket option
            iSocket.SetOpt(KSOHttpSessionId, KSOLHttpSessionInfo, id);
            iSocket.SetOpt(KSoTcpKeepAlive, KSolInetTcp, KTcpTriggeredKeepAlive);
            }
        if(aSocketType != EBlankSocket)
            {
            iSocket.SetOpt(KSoTcpNoDelay,KSolInetTcp,1);  // Disable the nagle.
            iSocket.SetOpt(KSORecvBuf, KSOLSocket, KSocketRecvBufSize); // Set the socket recv buf to be 16K
            }
        
        TInt lingerTimeout = iCommsInfoProvider.GetSocketImmediateCloseTimeout();
        if (lingerTimeout != KErrNotFound)
            {
            TPckgBuf<TSoTcpLingerOpt> linger;
            linger().iOnOff  = 1;
            linger().iLinger =  lingerTimeout;
            iSocket.SetOpt(KSoTcpLingerinMicroSec, KSolInetTcp, linger);
            }
        
        }
    return error;
    }

TInt CSocket::Listen(TUint aQSize, TUint16 aPort)
/**	
	Start the listen service. The socket is bound to the local port specified by
	aPort. The listen service is then started. The aQSize argument specifies the
	number of connections that can be received simultaneously, awaiting to be 
	accepted.
	@param		aQSize		The length of the listening queue.
	@param		aPort		The local port number on which to listen.
	@return		A value of KErrNone if the listen service was successfully 
				started.
*/
	{
	// Bind the socket to the port
	TSockAddr addr;
	addr.SetPort(aPort);

	TInt error = iSocket.Bind(addr);

	if( error == KErrNone )
		{
		// Start the listening service
		error = iSocket.Listen(aQSize);
		}
	return error;
	}

void CSocket::Accept(CSocket& aBlankSocket, TRequestStatus& aStatus)
/**	
	Start asynchronous accept service. The socket should have had the listening
	service started. When a connection has been received, the blank socket will
	be given the connection. The request status is completed either when a 
	connection is received or an error has occured - this is reflected in the 
	value of the request status.
	@param		aBlankSocket	The blank socket which will be given the 
								connection if one is received.
	@param		aStatus			The request status that is completed when the
								accept service completes.
*/
	{
	if( iSecureSocketController )
		{
		// Secure socket do not support secure listening
		aStatus=KRequestPending;
		TRequestStatus* pStat = &aStatus;
		User::RequestComplete(pStat, KErrNotSupported);
		}
	else
		iSocket.Accept(aBlankSocket.iSocket, aStatus);
	}

void CSocket::CancelAccept()
/**	
	Cancel the accept service.
*/
	{
	iSocket.CancelAccept();
	}

void CSocket::Connect(TInetAddr& aAddr, TRequestStatus& aStatus)
/**	
	Start asynchronous connect service. The address contains the IP address and
	port with which a tcp connection should be established with. The request 
	status is completed either when a connection has been established or an error
	has occurred - this is reflected in the value of the request status.
	@param		aAddr	The IP address and port of the remote host.
	@param		aStatus	The request status that is completed when the connect 
						service completes.
*/
	{
	iSocket.Connect(aAddr, aStatus);
	}

void CSocket::CancelConnect()
/**	
	Cancel the connect service.
*/
	{
	iSocket.CancelConnect();
	}

void CSocket::RecvOneOrMore(TDes8& aBuffer, TRequestStatus& aStatus)
/**	
	Receive data from socket asynchronously. Any data received by the socket is 
	placed in the buffer supplied by aBuffer. The request status is completed 
	either when data has been received or an error has occurred - this is 
	reflected in the value of the request status.
	@param		aBuffer	The buffer where any received data is placed.	
	@param		aStatus	The request status that is completed when the receive 
						service completes.
*/
	{
	if( iSecureSocketController )
		// aFlags not used for secure sockets
		iSecureSocketController->RecvOneOrMore(aBuffer, aStatus, iBytesReceived);
	else
		iSocket.RecvOneOrMore(aBuffer, KDefaultFlags, aStatus, iBytesReceived);
	}

void CSocket::CancelRecv()
/**	
	Cancel the receive service.
*/
	{
	if( iSecureSocketController )
		iSecureSocketController->CancelRecv();
	else
		iSocket.CancelRecv();
	}

void CSocket::Send(const TDesC8& aBuffer, TRequestStatus& aStatus)
/**	
	Send data to the socket asynchronously. The data in the supplied buffer is 
	sent to the socket. The request status is completed either when data has 
	been sent or an error has occurred - this is reflected in the value of the
	request status.
	@param		aBuffer	The buffer containing the data to be sent.
	@param		aStatus	The request status that is completed when the send 
						service completes.
*/
	{
	if( iSecureSocketController )
		iSecureSocketController->Send(aBuffer, aStatus);
	else
		iSocket.Write(aBuffer, aStatus);
	}

void CSocket::CancelSend()
/**	
	Cancel the send service.
*/
	{
	if( iSecureSocketController )
		iSecureSocketController->CancelSend();
	else
		iSocket.CancelWrite();
	}

void CSocket::Shutdown(TRequestStatus& aStatus)
/**	
	Shutdown the connection asynchronously. Any pending receive or send requests
	are allowed to complete before the connection is shutdown. The request 
	status is completed when either the connection has shutdown or an error has
	occurred - this is reflected in the value of the request status. This request
	cannot be cancelled. This method does not apply to secure connections. The
	request will complete with KErrNotSupported if used with a secure connection.
	@param		aStatus	The request status that is completed when the shutdown 
						service completes.
*/
	{
	if( iSecureSocketController )
		{
		// Not supported with secure connections
		aStatus=KRequestPending;
		TRequestStatus* pStat = &aStatus;
		User::RequestComplete(pStat, KErrNotSupported);
		}
	else
		iSocket.Shutdown(RSocket::ENormal, aStatus);
	}
	
void CSocket::ShutdownImmediate()
/**	
	Shutdown the connection synchronously. This method will shutdown the socket
	connection immediately using the abortive closing functionality. This method
	has no effect with secure connections.
*/
	{
	if( iSecureSocketController==NULL )
		{
		TRequestStatus shutdownStatus = KRequestPending;
		iSocket.Shutdown(RSocket::EImmediate, shutdownStatus);
		User::WaitForRequest(shutdownStatus);
		}
	}

void CSocket::RemoteName(TInetAddr& aAddr)
/**	
	Get the remote host name. The IP address and port of the remote host is set
	in the output argument.
	@param		aAddr	The output argument where the IP address and port of the
						remote host is placed.
*/
	{
	iSocket.RemoteName(aAddr);
	}

void CSocket::LocalName(TInetAddr& aAddr)
/**	
	Get the local socket name. The IP address and port of the local socket is 
	set in the output argument.
	@param		aAddr	The output argument where the IP address and port of the
						local socket is placed.
*/
	{
	iSocket.LocalName(aAddr);
	}

const CX509Certificate* CSocket::ServerCert()
 /**
 	Get the Server Certificate for this socket session.
	@return		A Certificate with the certificate information. This may be 
				NULL if the information is not available.
 */
 	{
 	if( iSecureSocketController )
		{
		return iSecureSocketController->ServerCert();
		}
	return NULL;
 	}
 	
	
TInt CSocket::CipherSuite(TDes8& aCipherSuite)
/**
	Get the current Cipher Suite for this socket session.
	@param		aCiptherSuite	A descriptor which will be filled with the cipher suite. 
								This is a 2 digit code as defined by RFC 2246. 
	@return		An error code. KErrNone on sucess. KErrNotSupported if this socket session is not secure.
*/
	{
	if (iSecureSocketController)
		return iSecureSocketController->CipherSuite(aCipherSuite);
	//else
	return KErrNotSupported;
	}

void CSocket::UpgradeToSecureL(TRequestStatus& aStatus, const TDesC8& aHostName,const TDesC& aProtocol)
/**
	Asynchronous request to upgrade the socket to a secure socket.
	@param	aStatus The request status, this will complete with KErrNone on the successful
					upgrade to a secure connection.
	@param	aHostName The host name of the server used for domain name checking
*/
	{
	// Check that the socket is already a secure one.
	if( iSecureSocketController )
		{
		// The socket connection is already secure, simply complete the request
		aStatus=KRequestPending;
		TRequestStatus* pStat = &aStatus;
		User::RequestComplete(pStat, KErrNone);
		}
	else
		{
		// Create the secure socket controller and start the secure handshake
		TRAPD(error, iSecureSocketController = CHttpSecureSocket::NewL(iSocket, iCommsInfoProvider));
		if (error == KErrNotFound || error == KEComErrNoInterfaceIdentified)
			User::Leave(KErrNotSupported);
		else if (error != KErrNone)
			User::Leave(error);
		iSecureSocketController->StartSecureHandshakeL(aStatus, aHostName,aProtocol);
		}
	}

void CSocket::CancelUpgradeToSecure()
/**
	Cancel the upgrade to a secure connection.
*/
	{
	if( iSecureSocketController )
		iSecureSocketController->CancelHandshake();
	}

TInt CSocket::PendingBytesToRead ()
	{
    if ( iSecureSocketController )
		    return iSecureSocketController->PendingBytesToRead ();
	TInt bytesToRead;
	TInt err = iSocket.GetOpt ( KSOReadBytesPending, KSOLSocket, bytesToRead );
	if ( err == KErrNone )
		return bytesToRead;
	return err;
	}
	
void CSocket::SetTCPCorking(TBool aValue)
    {
    if(aValue)
    	{
    	iSocket.SetOpt(KSoTcpNoDelay,KSolInetTcp,0);  // Disable the nagle.
      iSocket.SetOpt(KSOSendBuf, KSOLSocket, KSocketSendBufSize);
      }
    else
    	{
    	iSocket.SetOpt(KSoTcpNoDelay,KSolInetTcp,1);  // Disable the nagle.
      iSocket.SetOpt(KSOSendBuf, KSOLSocket, KSocketDefaultSendBufSize);        
      }
    
    iSocket.SetOpt(KSoTcpCork, KSolInetTcp, aValue);       
    
    }
