// Copyright (c) 2004-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:
// Implements the DHCP statemachine helper functions
// 
//

/**
 @file DHCPStateMachine.cpp
 @internalTechnology
*/

#include "DHCPStates.h"
#include "DHCPStatesDebug.h"
#include "DHCPControl.h"
#include "ExpireTimer.h"
#include "DHCPMsg.h"
#include <e32math.h>
#include "DHCPServer.h"
#include "in6_opt.h"
#include "es_sock.h"

CDHCPStateMachine::~CDHCPStateMachine()
/**
  * Destructor of the If base class
  *
  * @internalTechnology
  *
  */
	{
	__CFLOG_VAR((KLogSubSysDHCP, KLogCode, _L8("CDHCPStateMachine::~CDHCPStateMachine")));
	Cancel();
	delete iDhcpMessage;
	delete iMessageSender;
	delete iTimer;
	delete iHostName;
	delete iDomainName;
#ifdef SYMBIAN_NETWORKING_DHCP_MSG_HEADERS
	iSavedExtraParameters.Close();
#endif //SYMBIAN_NETWORKING_DHCP_MSG_HEADERS	
	iClientId.Close();
	iSocket.Close();
#ifdef SYMBIAN_NETWORKING_DHCPSERVER	
	iSvrSocket.Close();
	delete iDNSInformation;
#endif // SYMBIAN_NETWORKING_DHCPSERVER	
	}

void CDHCPStateMachine::ConstructL()
/**
  * Creates socket and connections for the object
  *
  *
  *	@internalTechnology
  *
  */
	{
	__CFLOG_VAR((KLogSubSysDHCP, KLogCode, _L8("CDHCPStateMachine::ConstructL")));
	
#ifdef _DEBUG
	// let's set debug properties to something
	//  so they can be read immediately..
	CDHCPStateMachine* const & iStateMachine = this;
	CDHCPStateMachine* const & iDhcpStateMachine = iStateMachine;
	DHCP_DEBUG_PUBLISH_READY(DHCPDebug::ENotReady);
	DHCP_DEBUG_PUBLISH_STATE(DHCPDebug::EStateUnknown);
#endif

	iTimer = CExpireTimer::NewL();
#ifdef SYMBIAN_NETWORKING_DHCPSERVER	
	if(!iServerImpl) // Assemble client Ids only for DHCP client implementation
		{
#endif // SYMBIAN_NETWORKING_DHCPSERVER	
	InitialiseSocketL();
	AssembleClientIDsL();
#ifdef SYMBIAN_NETWORKING_DHCPSERVER	
		}
	else
		InitialiseServerSocketL();
#endif // SYMBIAN_NETWORKING_DHCPSERVER			
	}

void CDHCPStateMachine::Start(MStateMachineNotify* aStateMachineNotify)
/**
  * The Start function
  *
  * Starts the statemachine task
  *
  * @internalTechnology
  */
{
   SetLastError( KErrNone );
   iReceiving = EFalse;
   iHistory = 0;
	SetActiveEvent(iFirstState);
	if ( !aStateMachineNotify )
		{//no notifier specified => leave the current one unchanged
		aStateMachineNotify = iStateMachineNotify;
		}
	CStateMachine::Start(NULL, NULL, aStateMachineNotify);
}

void CDHCPStateMachine::CloseNSendMsgL(TRequestStatus& aStatus, CDHCPStateMachine::EAddressType aEAddressType)
/**
  * Handles sending of packets for this object
  *
  * @internalTechnology
  *
  */
	{
	__CFLOG_VAR((KLogSubSysDHCP, KLogCode, _L8("CDHCPStateMachine::CloseNSendMsgL")));
   	PrepareToSendL(aEAddressType);
  	iMessageSender->Cancel();
	
	GetServerAddress(iSocketAddr);
	AddScopeToAddrL(iSocketAddr);

#ifdef _DEBUG
	THostName addrDes;
	iSocketAddr.Output(addrDes);	
	__CFLOG_VAR((KLogSubSysDHCP, KLogCode, 
		_L("CDHCPStateMachine::CloseNSendMsgL - Sending message to %S%%%d"), &addrDes, iSocketAddr.Scope()));
#endif

	iSocket.SendTo(iDhcpMessage->Message(), iSocketAddr, 0, aStatus);
	}

#ifdef SYMBIAN_NETWORKING_DHCPSERVER
void CDHCPStateMachine::CloseNSendServerMsgL(TRequestStatus& aStatus, CDHCPStateMachine::EAddressType aEAddressType)
/**
  * Handles sending of packets for this object
  *
  * @internalTechnology
  *
  */
	{
	__CFLOG_VAR((KLogSubSysDHCP, KLogCode, _L8("CDHCPStateMachine::CloseNSendMsgL")));


   	PrepareToSendServerMsgL(aEAddressType);
  	iMessageSender->Cancel();

	GetClientAddress(iSrvSocketAddr);
	AddScopeToClientAddrL(iSrvSocketAddr);

#ifdef _DEBUG
	THostName addrDes;
	iSrvSocketAddr.Output(addrDes);	
	__CFLOG_VAR((KLogSubSysDHCP, KLogCode, 
		_L("CDHCPStateMachine::CloseNSendServerMsgL - Sending message to %S%%%d"), &addrDes, iSrvSocketAddr.Scope()));
#endif

	iSvrSocket.SendTo(iDhcpMessage->Message(), iSrvSocketAddr, 0, aStatus);
	}
	
void CDHCPStateMachine::GetClientAddress( TInetAddr& /*aAddress*/ )
/**
  * GetClientAddress
  *
  * Null implementation
  *
  * @internalTechnology	
*/
	{
	}	
	
#ifdef SYMBIAN_DNS_PROXY	
TInetAddr CDHCPStateMachine::GetListenerAddress()
	{
	return iCurrentAddress;
	}
#endif
		
void CDHCPStateMachine::InitServerStateMachineL(MStateMachineNotify* /*aStateMachineNotify*/)
/**
  * InitServerStateMachineL
  *
  * Null implementation
  *
  * @internalTechnology
  */
	{
	}	
	
void CDHCPStateMachine::InitServerBinding(MStateMachineNotify* /*aStateMachineNotify*/)		
	{
	
	}
	
#endif // SYMBIAN_NETWORKING_DHCPSERVER

void CDHCPStateMachine::CloseNSendMsgL(TTimeIntervalSeconds aSecs, TInt aMaxRetryCount, CDHCPStateMachine::EAddressType aEAddressType)
/**
  * Handles sending of packets for this object
  *
  * @internalTechnology
  *
  */
	{
	__CFLOG_VAR((KLogSubSysDHCP, KLogCode, _L8("CDHCPStateMachine::CloseNSendMsgL")));
   	PrepareToSendL(aEAddressType);
	iMessageSender->Cancel();
	
	TInetAddr addr;		
	GetServerAddress( addr );
	AddScopeToAddrL(addr);
	
#ifdef _DEBUG
	THostName addrDes;
	addr.Output(addrDes);	
	__CFLOG_VAR((KLogSubSysDHCP, KLogCode, 
		_L("CDHCPStateMachine::CloseNSendMsgL - Sending message to %S%%%d"), &addrDes, addr.Scope()));
#endif

	if (FastTimeoutDuringInform())	// true only when InformNegotiationIsRequiredForConnectionStartCompletion is ETrue
		{
		iMessageSender->SendL(addr, iDhcpMessage->Message(), (aSecs.Int() * KMicrosecondsInSecs) / 14, aMaxRetryCount);
		}
	else
		{
		iMessageSender->SendL(addr, iDhcpMessage->Message(), aSecs.Int() * KMicrosecondsInSecs, aMaxRetryCount);	
		}
	}

TInt CDHCPStateMachine::MSReportError(TInt aError)
/**
  * Report an error properly
  *
  * @internalTechnology
  */
	{
	Cancel();

	SetLastError(aError);
	OnCompletion();

	return aError;
	}

void CDHCPStateMachine::Cancel()
/**
  * Cancel the state machine's activities
  *
  * @internalTechnology
  */
	{
	__CFLOG_VAR((KLogSubSysDHCP, KLogCode, _L8("CDHCPStateMachine::Cancel")));
	CancelMessageSender();
	iSocket.Close();
#ifdef SYMBIAN_NETWORKING_DHCPSERVER	
	iSvrSocket.Close();
#endif // SYMBIAN_NETWORKING_DHCPSERVER	
	CancelTimer();
	CStateMachine::Cancel(KErrNone);
	delete iFirstState;
	iFirstState = NULL;
	iFastTimeout = EFalse;	// reset iFastTimeout
	iMaxRetryCount = KInfinity;//reset
	}

void CDHCPStateMachine::DoCancel()
/**
  * Implements a default docancel for the connection object
  *
  * @internalTechnology
  */
	{
	// we have to cancel send and recv independently as cancelAll()
	// doesn't satisfy us, only supporting read, write, ioctl, connect,
	// accept and shutdown...:-(
	__CFLOG_VAR((KLogSubSysDHCP, KLogCode, _L8("CDHCPStateMachine::DoCancel")));
	if (iSocket.SubSessionHandle())
		{
		// check that the socket is open
		// and if it is then we need to cancel things on it
		iSocket.CancelRecv();
		}
#ifdef SYMBIAN_NETWORKING_DHCPSERVER		
	if(iSvrSocket.SubSessionHandle())
		{
		iSvrSocket.CancelRecv();
		}
#endif // SYMBIAN_NETWORKING_DHCPSERVER		
	/*	iAsyncCancelHandler is set by the DHCP State that assumes ownership of the 
		RequestStatus object of CDHCPStateMachine 				 
	*/
	 if(iAsyncCancelHandler)
		{
		iAsyncCancelHandler->Cancel();
		}
	}
#ifdef SYMBIAN_NETWORKING_DHCPSERVER
void CDHCPStateMachine::FetchServerAddressL()
/**
  * This function is used to get the server's address (interface address)
  *
  */
	{
	if(!iSvrSocket.SubSessionHandle())
		InitialiseServerSocketL();
	
	TPckgBuf<TSoInetInterfaceInfo> opt;
	while (iSvrSocket.GetOpt(KSoInetNextInterface, KSolInetIfCtrl, opt) == KErrNone)
		{
		if (opt().iName == iInterfaceName)
			{
			iCurrentAddress = opt().iAddress;
			break;
			}
		}
   }
   
void CDHCPStateMachine::SetDNSInformation(TDes8* aDNSInfo)	
	{
	delete iDNSInformation;
	iDNSInformation = NULL;
	
	iDNSInformation = HBufC8::NewL(aDNSInfo->Length());
	iDNSInformation->Des() = *aDNSInfo;
	}   
	
TBool CDHCPStateMachine::CheckNetworkId()
/**
  * This function compares the NetworkIds of the client and server
  *
  */	
	{
	iInformClientAddr.SetV4MappedAddress(iCiaddr);

	if ((iInformClientAddr.Address() & KInetAddrNetMaskC) == 
			(iCurrentAddress.Address() & KInetAddrNetMaskC))		
		{
		return ETrue;		
		}
	else
		{
		return EFalse;
		}	
	}   
#endif // SYMBIAN_NETWORKING_DHCPSERVER

void CDHCPStateMachine::FetchHWAddress()
/**
  * Fetches hardware address from the interface
  *
  */
	{
	TPckgBuf<TSoInetInterfaceInfo> opt;
	while (iSocket.GetOpt(KSoInetNextInterface, KSolInetIfCtrl, opt) == KErrNone)
		{
		if (opt().iName == iInterfaceName)
			{
			iHardwareAddr = opt().iHwAddr;
			if(iHardwareAddr.Length() <= KHwAddrOffset)
				{
				// the hardware address came back too short
   		 		//  to be a valid TSockAddr.. so we'll treat it as an empty value
				iHardwareAddr.SetFamily(KAFUnspec);
				iHardwareAddr.SetLength(KHwAddrOffset);
				}
			break;
			}
		}
   }


void CDHCPStateMachine::StartTimer(TTimeIntervalSeconds aSeconds, MExpireTimer& aExpireTimer)
/**
  * Give the tcp/ip6 stack time to perform
  * its gratuitous ARP...
  *
  * @internalTechnology
  */
	{
	CancelTimer();
	__CFLOG_VAR((KLogSubSysDHCP, KLogCode, _L8("CDHCPStateMachine::StartTimerL")));

	iTimer->After(aSeconds, aExpireTimer);
	}

void CDHCPStateMachine::StartTimer( TTimeIntervalMicroSeconds32 aMicroSeconds, MExpireTimer& aExpireTimer)
/**
  * Give the tcp/ip6 stack time to perform
  * its gratuitous ARP...
  *
  * @internalTechnology
  */
	{
	CancelTimer();
	__CFLOG_VAR((KLogSubSysDHCP, KLogCode, _L8("CDHCPStateMachine::StartTimerL")));

	iTimer->After(aMicroSeconds, aExpireTimer);
	}

void CDHCPStateMachine::RemoveConfiguredAddress( const TSoInet6InterfaceInfo& aSoInet6InterfaceInfo )
/**
  * This function can be called as a result of DAD failing
  * or the lease expiring! It removes the address from the interface
  * inside the TCP/IP6 stack as the address cannot continue to be used.
  *
  * @see "Implementation of IPv4/IPv6 Basic Socket API for Symbian OS"
  * document for explanation of TSoInet6InterfaceInfo and its use
  * @internalTechnology
  */
	{
	__CFLOG_VAR((KLogSubSysDHCP, KLogCode, _L8("CDHCPStateMachine::RemoveConfiguredAddress")));
	
	TPckgBuf<TSoInet6InterfaceInfo> configInfo(aSoInet6InterfaceInfo);
	// not interested in error
	// how could we attempt to handle it anyway?...keep trying??? i think not...
	// ensure that we have a socket to write down	
	iSocket.Close();	
	(void)iSocket.Open(iEsock, KAfInet, KSockDatagram, KProtocolInetUdp, iConnection);
	(void)iSocket.SetOpt(KSoInetConfigInterface, KSolInetIfCtrl, configInfo);	
	// make socket invisible for interface counting
	(void)iSocket.SetOpt(KSoKeepInterfaceUp, KSolInetIp, 0);
	}

void CDHCPStateMachine::ConfigureInterfaceL( const TSoInet6InterfaceInfo& aInterfaceInfo )
/**
  * Set the interface IP address and other params
  * into the TCP/IP6 stack.
  *
  * What we set depends on the setup
  * in commDB for the service.  If ipAddressFromServer
  * is true then we set the ip address that has
  * been assigned by DHCP, along with the netmask and gateway.
  * If ipAddressFromServer is false, then we set the static ip
  * address as long as it has been okayed by the DHCP server after
  * we have sent an inform.  We will then set the netmask and gateway
  * choosing from those in commDB if they have been given values, or 
  * those returned in the DHCP Server ACK if a zero address is in commDB
  * The same principle applies to DNS Server addresses.
  *
  * @internalTechnology
  */
	{
	
	TPckgBuf<TSoInet6InterfaceInfo> configInfo(aInterfaceInfo);
	

	__CFLOG_VAR((KLogSubSysDHCP, KLogCode, _L8("CDHCPStateMachine::ConfigureInterfaceL - KSoInetConfigInterface")));
	
	User::LeaveIfError(iSocket.SetOpt(KSoInetConfigInterface, KSolInetIfCtrl, configInfo));
	}

TInt CDHCPStateMachine::BindToSource()
/**
  * Binds socket to newly assigned address for the interface
  *
  * @internalTechnology
  */
	{
	__CFLOG_VAR((KLogSubSysDHCP, KLogCode, _L8("CDHCPStateMachine::BindToSourceL")));
	iSocket.Close();	// destroy the old socket
	
	UpdateHistory(CDHCPState::EBindToSource);
	// now start a new one.
	// might be nice if we left here if the socket open fails...but nobody's perfect...
   // PS: cannot leave here the failure doesn't mean exception here see the usage....
	TInt err = iSocket.Open(iEsock, KAfInet, KSockDatagram, KProtocolInetUdp, iConnection);
	// make socket invisable for interface counting
	if (err == KErrNone)
		{
		err = iSocket.SetOpt(KSoKeepInterfaceUp, KSolInetIp, 0);
		}
		
    if (err == KErrNone)       //PDEF122482: Enabling the ReUseAddr option.
	    {
	    err = iSocket.SetOpt(KSoReuseAddr, KSolInetIp, 1);
	    }			
	
	if (err == KErrNone)
		{
        TInetAddr bindTo;        
        AssignAddresses( bindTo, iCurrentAddress ); 

        err = iSocket.Bind(bindTo);
        if (err==KErrNone)
	        {
	        // we are finished with this socket, 
	        // release it so as not to hold esock open
	        iSocket.Close();
	        }
		}
	return err;
	}


TUint32 CDHCPStateMachine::GetNetworkIdL() const
	{	
	TUint32 networkId;
	_LIT(KIapNetwork, "IAP\\IAPNetwork");
	User::LeaveIfError(iConnection.GetIntSetting(KIapNetwork, networkId));
	
	return networkId;
	}

void CDHCPStateMachine::AddScopeToAddrL(TInetAddr& addr)
	{
	TPckgBuf<TSoInetIfQuery> queryBuf;
	queryBuf().iName = iInterfaceName;		
	User::LeaveIfError(iSocket.GetOpt(KSoInetIfQueryByName, KSolInetIfQuery, queryBuf));
	
	const TUint s = addr.Ip6Address().Scope() - 1;

	if (s < 16)
		{
		addr.SetScope(queryBuf().iZone[s]);
		}
	}

#ifdef SYMBIAN_NETWORKING_DHCPSERVER
void CDHCPStateMachine::AddScopeToClientAddrL(TInetAddr& addr)
	{
	TPckgBuf<TSoInetIfQuery> queryBuf;
	queryBuf().iName = iInterfaceName;		
	User::LeaveIfError(iSvrSocket.GetOpt(KSoInetIfQueryByName, KSolInetIfQuery, queryBuf));
	
	const TUint s = addr.Ip6Address().Scope() - 1;

	if (s < 16)
		{
		addr.SetScope(queryBuf().iZone[s]);
		}
	}
// Set when the DHCP server implementation is being used	
void CDHCPStateMachine::SetServerState(TBool aServerImpl)	
	{
	iServerImpl = aServerImpl;
	}
	
TInt CDHCPStateMachine::BindServerInterface()
/**
  * Binds socket to newly assigned address for the interface
  *
  * @internalTechnology
  */
	{
	__CFLOG_VAR((KLogSubSysDHCP, KLogCode, _L8("CDHCPStateMachine::BindServerInterface")));
	iSvrSocket.Close();	// destroy the old socket
	
	// now start a new one.
	TInt err = iSvrSocket.Open(iEsock, KAfInet, KSockDatagram, KProtocolInetUdp, iConnection);
	// make socket invisable for interface counting
	if (err == KErrNone)
		{
		TInt err = iSvrSocket.SetOpt(KSoKeepInterfaceUp, KSolInetIp, 0);
		}

	if (err == KErrNone)
		{
        TInetAddr bindTo;        
        AssignAddresses( bindTo, iCurrentAddress );
        err = iSvrSocket.Bind(bindTo);
		}
	return err;
	}

	
TInetAddr CDHCPStateMachine::GetInterfaceServerGlobalAddress()
/**
  * Are any of the addresses on the interface global addresses?
  *  If so, DHCP might decide not to attempt to discover an address.
  *
  * Returns unspecified address if no global address present.
  */
	{
	TPckgBuf<TSoInetInterfaceInfo> opt;
	if (iSvrSocket.SubSessionHandle() == 0)
		{
		InitialiseServerSocketL();
		}
	iSvrSocket.SetOpt(KSoInetEnumInterfaces, KSolInetIfCtrl);
	__CFLOG_STMT(TBuf<512> addrStr;);
	while (iSvrSocket.GetOpt(KSoInetNextInterface, KSolInetIfCtrl, opt) == KErrNone)
		{
		if (opt().iName == iInterfaceName)
			{
			TInetAddr& addr = opt().iAddress;
			__CFLOG_STMT(addr.Output(addrStr););
			if ( ! addr.IsLinkLocal())
				{
				__CFLOG_VAR((KLogSubSysDHCP, KLogCode, _L("Global address %S found on interface %S"),&addrStr,&iInterfaceName));
				return addr;
				}
			else
				{
				__CFLOG_VAR((KLogSubSysDHCP, KLogCode, _L("Linklocal address %S found on interface %S"),&addrStr,&iInterfaceName));
				}
			break;
			}
		}
	return TInetAddr(); // unspecified
	}



		
#endif // SYMBIAN_NETWORKING_DHCPSERVER
	
TInetAddr CDHCPStateMachine::GetInterfaceGlobalAddress()
/**
  * Are any of the addresses on the interface global addresses?
  *  If so, DHCP might decide not to attempt to discover an address.
  *
  * Returns unspecified address if no global address present.
  */
	{
#ifdef SYMBIAN_NETWORKING_DHCPSERVER	
	if(iServerImpl)
		{
		return GetInterfaceServerGlobalAddress();
		}
	else
		{
#endif // SYMBIAN_NETWORKING_DHCPSERVER	
	
	TPckgBuf<TSoInetInterfaceInfo> opt;
	if (iSocket.SubSessionHandle() == 0)
		{
		InitialiseSocketL();
		}
	iSocket.SetOpt(KSoInetEnumInterfaces, KSolInetIfCtrl);
	__CFLOG_STMT(TBuf<512> addrStr;);
	while (iSocket.GetOpt(KSoInetNextInterface, KSolInetIfCtrl, opt) == KErrNone)
		{
		if (opt().iName == iInterfaceName)
			{
			TInetAddr& addr = opt().iAddress;
			__CFLOG_STMT(addr.Output(addrStr););
			if ( ! addr.IsLinkLocal())
				{
				__CFLOG_VAR((KLogSubSysDHCP, KLogCode, _L("Global address %S found on interface %S"),&addrStr,&iInterfaceName));
				return addr;
				}
			else
				{
				__CFLOG_VAR((KLogSubSysDHCP, KLogCode, _L("Linklocal address %S found on interface %S"),&addrStr,&iInterfaceName));
				}
			break;
			}
		}
	return TInetAddr(); // unspecified
	
#ifdef SYMBIAN_NETWORKING_DHCPSERVER
		}
#endif // SYMBIAN_NETWORKING_DHCPSERVER				
	}


TBool CDHCPStateMachine::DoesInterfaceKnowAnyDNSServers()
/**
  * Does the interface know of any DNS servers?
  */
	{
	TPckgBuf<TSoInetInterfaceInfo> opt;
	if (iSocket.SubSessionHandle() == 0)
		{
		InitialiseSocketL();
		}
	iSocket.SetOpt(KSoInetEnumInterfaces, KSolInetIfCtrl);
	__CFLOG_STMT(TBuf<512> addrStr;);
	while (iSocket.GetOpt(KSoInetNextInterface, KSolInetIfCtrl, opt) == KErrNone)
		{
		if (opt().iName == iInterfaceName)
			{
			TInetAddr& addr = opt().iNameSer1;
			__CFLOG_STMT(addr.Output(addrStr););
			if ( ! addr.IsUnspecified() )
				{
				__CFLOG_VAR((KLogSubSysDHCP, KLogCode, _L("DNS server %S found on interface %S"),&addrStr,&iInterfaceName));
				return ETrue;
				}
			else
				{
				__CFLOG_VAR((KLogSubSysDHCP, KLogCode, _L("No DNS servers already set on interface %S (prior to DHCP)"),&iInterfaceName));
				}
			break;
			}
		}
	return EFalse;
	}




TDhcpRnd::TDhcpRnd():iXid(0)
/**
  * Constructor for this little random number 
  * class that creates us a random transaction id
  *
  * @internalTechnology
  */
	{
	TTime now;
	now.HomeTime();
	iSeed = now.Int64();
	}

TInt TDhcpRnd::Rnd(TInt aMin, TInt aMax)
/**
  * Utility class function to generated a real
  * random number
  *
  * @internalTechnology
  */
	{
	return Math::Rand(iSeed)%(aMax-aMin+1)+aMin;
	}
