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

#include "csocket.h"
#include "msocketconnectobserver.h"
#include "msocketconnectorstore.h"
#include "msocketcontrollerfactory.h"
#include "mcommsinfoprovider.h"
#include "csocketcontroller.h"
#include "httptransporthandlercommon.h"
#include "thttptrlayerpanic.h"

#if defined (_DEBUG) && defined (_LOGGING)
const TInt KHostNameSize	= 126;
#endif

CSocketConnector* CSocketConnector::NewL(
										MSocketConnectorStore&		aStore,
										MSocketControllerFactory&	aSocketControllerFactory,
										MCommsInfoProvider&			aCommsInfoProvider, TBool aPriority
										)
/**	
	The factory constructor.
	@param		aStore						The socket connector store.
	@param		aSocketControllerFactory	The socket controller factory.
	@param		aCommsInfoProvider			The comms info provider.
	@return		A pointer to a fully constructed object.
*/
	{
	return new (ELeave) CSocketConnector(aStore, aSocketControllerFactory, aCommsInfoProvider,aPriority);
	}

CSocketConnector::~CSocketConnector()
/**	
	Destructor.
*/
	{
	// Cancel any outstanding requests
	Cancel();

	// Cleanup...
	delete iHost;
	delete iConnectingSocket;
	iHostResolver.Close();

//	__FLOG_CLOSE;
	}

CSocketConnector::CSocketConnector(
								  MSocketConnectorStore&	aStore,
								  MSocketControllerFactory&	aSocketControllerFactory,
								  MCommsInfoProvider&		aCommsInfoProvider,
								  TBool aPriority
								  )
: CActive(aPriority ? CActive::EPriorityHigh : CActive::EPriorityStandard), iStore(aStore),
									   iSocketControllerFactory(aSocketControllerFactory),
									   iCommsInfoProvider(aCommsInfoProvider)
/**	
	Constructor.
	@param		aStore						The socket connector store.
	@param		aSocketControllerFactory	The socket controller factory.
	@param		aCommsInfoProvider			The comms info provider.
*/	{
	if(aPriority)
		{
		CActive::SetPriority(CActive::EPriorityHigh);	
		}
	
	CActiveScheduler::Add(this);

//	__FLOG_OPEN("http", "httptransporthandler.txt");
	}

void CSocketConnector::ConnectL(MSocketConnectObserver& aObserver, const TDesC8& aRemoteHost, TUint16 aRemotePort, TInetAddr* aRemoteAddress)
/**	
	Start connection to specified remote host. The socket connector starts 
	connecting to the specified remote host on the specified port.
	@param		aRemoteHost		The host name/IP address of the remote host
	@param		aRemotePort		The port number of the remote host
	@pre		The socket connector is in the Idle state.
	@post		The socket connector is not in the Idle state.
*/
	{
	__ASSERT_DEBUG( iState == EIdle, THttpTrLayerPanic::Panic(THttpTrLayerPanic::EBadSocketConnectorState) );

	// Set the observer
	iObserver = &aObserver;

	// Copy the remote host IP address and port
	iHost = HBufC::NewL(aRemoteHost.Length());
	iHost->Des().Copy(aRemoteHost);
	iPort = aRemotePort;

    // Move to the PendingDNSLookup state and self complete.
    if(aRemoteAddress == NULL)
        {
        // Address is unknown / DNS lookup is needed
        iState = EPendingDNSLookup;
        }
    else
        {
        // Address is know. No lookup is needed. Just go and connect.
        iHostDnsEntry().iAddr = *aRemoteAddress;
        iState = EConnecting;
        }
	CompleteSelf();
	}

void CSocketConnector::CompleteSelf()
/**	
	Requests that the socket connector complete itself. This will caused the 
	RunL() to be called by the scheduler at the next opportunity.
	@pre		The socket connector object is not active.
	@post		The socket connector object is active and the request has been
				completed.
*/
	{
	TRequestStatus* pStat = &iStatus;
	User::RequestComplete(pStat, KErrNone);
	SetActive();
	}

void CSocketConnector::Suicide()
/**	
	The socket connector is finished, it needs to remove itself from the store
	and self-destruct.
*/
	{
	// Inform store that connection is complete and then suicide.
	iStore.ConnectionCompleted(*this);
	delete this;
	}

/*
 *	Methods from MSocketConnector
 */

void CSocketConnector::StopConnect()
/**
	@see		MSocketConnector
*/
	{
	// Cancel any outstanding requests.
	Cancel();

#if defined (_DEBUG) && defined (_LOGGING)
	TBuf8<KHostNameSize> host;
	host.Copy((*iHost).Left(KHostNameSize)); //just get the KHostNameSize characters

	__FLOG_0(_T8("!! Stopping connection"));
	__FLOG_1(_T8("-> connect to host %S stopped"), &host);
#endif

	Suicide();
	}

void CSocketConnector::MSocketConnector_Reserved()
/**	
	@see		MSocketConnector
*/
	{
	User::Invariant();
	}

/*
 *	Methods from CActive
 */

void CSocketConnector::RunL()
/**	
	The request servicing function. Behaviour depends on the state of the socket
	connector.
*/
	{
	// Leave if there has been an error
	User::LeaveIfError(iStatus.Int());

	switch( iState )
		{
	case EPendingDNSLookup:
		{
#if defined (_DEBUG) && defined (_LOGGING)
		TBuf8<KHostNameSize> host;
		host.Copy((*iHost).Left(KHostNameSize)); //just get the KHostNameSize characters

		__FLOG_1(_T8("Doing DNS lookup -> searching for host %S"), &host);
#endif

		__OOM_LEAVE_TEST

		if ( iCommsInfoProvider.HasConnection() )
			{
			// Open the host resolver session with the preffered connection
			User::LeaveIfError(iHostResolver.Open(
												 iCommsInfoProvider.SocketServer(),
												 iCommsInfoProvider.ProtocolDescription().iAddrFamily, 
												 KProtocolInetUdp,
												 iCommsInfoProvider.Connection()
												 ));				
			}
		else
			{							
			// Open the host resolver session with no connection
			User::LeaveIfError(iHostResolver.Open(
												 iCommsInfoProvider.SocketServer(),
												 iCommsInfoProvider.ProtocolDescription().iAddrFamily, 
												 KProtocolInetUdp
												 ));				
			}

		// Start the DNS lookup for the remote host name.
		iHostResolver.GetByName(*iHost, iHostDnsEntry, iStatus);

		// Move to the Connecting state and go active
		iState = EConnecting;
		SetActive();
		} break;
	case EConnecting:
		{
		__OOM_LEAVE_TEST

		// DNS lookup successful - form the internet address object
		iAddress = TInetAddr(iHostDnsEntry().iAddr);
		iAddress.SetPort(iPort);

#if defined (_DEBUG) && defined (_LOGGING)
		TBuf8<KHostNameSize> host;
		host.Copy((*iHost).Left(KHostNameSize)); //just get the KHostNameSize characters

		TBuf<KIpv6MaxAddrSize> ip16bit;
		iAddress.Output(ip16bit);

		TBuf8<KIpv6MaxAddrSize> ip;
		ip.Copy(ip16bit);
		
		__FLOG_2(_T8("DNS lookup complete -> host %S has IP address %S"), &host, &ip);
#endif
		
		// Start a default RConnection, if one is not started and not local loopback address		
		if ( !iCommsInfoProvider.HasConnection() && !iAddress.IsLoopback() )
			{
			iCommsInfoProvider.StartDefaultCommsConnectionL ();				
			}

		// Create the connecting socket
		iConnectingSocket = CSocket::NewL(iCommsInfoProvider, CSocket::EProtocolSocket);

		// Start connecting to the remote client
		iConnectingSocket->Connect(iAddress, iStatus);

		__FLOG_2(_T8("Connecting -> to host %S on IP address %S"), &host, &ip);

		// Move to the Connected state and go active
		iState = EConnected;
		SetActive();
		} break;
	case EConnected:
		{
		__OOM_LEAVE_TEST

#if defined (_DEBUG) && defined (_LOGGING)
		TBuf8<KHostNameSize> host;
		host.Copy((*iHost).Left(KHostNameSize)); //just get the KHostNameSize characters

		TInetAddr addr;
		iConnectingSocket->RemoteName(addr);

		TBuf<KIpv6MaxAddrSize> ip16bit;
		addr.Output(ip16bit);

		TBuf8<KIpv6MaxAddrSize> ip;
		ip.Copy(ip16bit);

		TInetAddr local;
		iConnectingSocket->LocalName(local);

		__FLOG_1(_T8("!! Connection with host %S established"), &host);
		__FLOG_3(_T8("-> on local port %d with %S, remote port %d"), local.Port(), &ip, addr.Port());
#endif

		// A connection has been made with the remote client - lose ownership of
		// the connecting socket.
		CSocket* connectedSocket = iConnectingSocket;
		iConnectingSocket = NULL;

		// Create a socket controller object to own the connected socket.
		CSocketController* socketController = iSocketControllerFactory.CreateSocketControllerLC(connectedSocket, *iHost, iPort, iAddress);

		// Inform the socket connect observer that a TCP connection is established - 
		// pass back the input and output stream objects.
		iObserver->ConnectionMadeL(socketController->InputStream(), socketController->OutputStream());

		// Remove socket controller from cleanup stack - transferring ownership
		// to the store.
		CleanupStack::Pop(socketController);

		// Add the socket controller in the store - ownership is transferred to
		// the store.
		iSocketControllerFactory.AddToStoreL(socketController);

		// Socket connector is finished - suicide.
		Suicide();
		} break;
	case EIdle:
	default:
		THttpTrLayerPanic::Panic(THttpTrLayerPanic::EBadSocketConnectorState);
		break;
		}
	}

void CSocketConnector::DoCancel()
/**	
	The asynchronous request cancel.
*/
	{
	// Check state
	switch( iState )
		{
	case EConnecting:
		{
		// DNS lookup is pending - cancel
		iHostResolver.Cancel();
		} break;
	case EConnected:
		{
		if( iConnectingSocket )
			{
			// Connection is pending - cancel and delete the socket
			iConnectingSocket->CancelConnect();
			delete iConnectingSocket;
			iConnectingSocket = NULL;
			}
		} break;
	case EIdle:
	case EPendingDNSLookup:
	default:
		// Do nothing...
		break;
		}
	}

TInt CSocketConnector::RunError(TInt aError)
/**	
	The error handler for when RunL() leaves. This function does any necessary
	cleanup. The socket connector is then set to suicide.
	@param		aError	The leave code.
	@return		A value of KErrNone.if the error has been handled, any other 
				value if it has not been handled.
	@post		The socket connector is in the Suicide state.
*/
	{
#if defined (_DEBUG) && defined (_LOGGING)
	TBuf8<KHostNameSize> host;
	host.Copy((*iHost).Left(KHostNameSize)); //just get the KHostNameSize characters

	__FLOG_1(_T8("!! Error : %d"), aError);
	__FLOG_1(_T8("-> connect to host %S failed"), &host);
#endif

	TInt errorToPropagate = aError;
	TInt error = KErrNone;

	switch( iState )
		{
	case EPendingDNSLookup:
	case EConnecting:
		{
		// In EReadyForDNS or EReadyToConnect states, KErrNotReady may be 
		// returned by the comms call that require a connection. The RConnection
		// that is passed in MUST be started or this error will occur. If we own
		// the RConnection we should start the RConnection again, else if our
		// client own the RConnection, we just propagate the Comms error.
		if( iCommsInfoProvider.HasConnection() && iCommsInfoProvider.OwnsConnection() && aError == KErrNotReady )
			{
			__FLOG_0(_T8("-> re-starting comms interface..."));

			// We own the RConnection and the error is KErrNotReady
			errorToPropagate = iCommsInfoProvider.Connection().Start();

			if( errorToPropagate == KErrNone )
				{
				__FLOG_0(_T8("!! Re-started comms interface"));
				__FLOG_1(_T8("-> try connecting to host %S again"), &host);

				// RConnection started successfully, try to connect again
				CompleteSelf();
				return KErrNone;
				}
#if defined (_DEBUG) && defined (_LOGGING)
			else
				{
				__FLOG_1(_T8("!! Error : %d"), errorToPropagate);
				__FLOG_0(_T8("-> failed to re-start comms interface"));
				__FLOG_1(_T8("-> connect to host %S failed"), &host);
				}			
#endif
			}
		// Opening the connecting socket has failed or the DNS lookup completed
		// with an error code - suicide after notifying the observer - do nothing 
		// except drop through to the Connected state case...
		}
	case EConnected:
		{
		// Either the socket controller factory left in AddToStoreL() or in
		// CreateSocketControllerLC(), or the observer left in ConnectionMadeL()
		// or the connect request completed with an error code. Inform the 
		// observer of the error.
		error = iObserver->HandleConnectError(errorToPropagate);
		} break;
	case EIdle:
	default:
		THttpTrLayerPanic::Panic(THttpTrLayerPanic::EBadSocketConnectorState);
		break;
		}

	Suicide();

	return error;
	}
