// 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 <e32hal.h>	// UserHal::MachineInfo()
#include <comms-infras/nifif.h>
#include <nifvar.h>
#include <nifutl.h>
#include <es_mbuf.h>
#include <in_iface.h>
#include <comms-infras/commsdebugutility.h>
#include <connectprog.h>

#include <in_chk.h>
#include <in_sock.h>
#include <in6_if.h>	// KSoIface*, KIf*, TSoInet6IfConfig
#include <cdbcols.h>
#include <cdblen.h>
#include "dummynifvar.h"
#include "dummynif.h"


/*
 * The IPv6 interface binder class
 */

CDummyIf6::CDummyIf6(CDummyIfLink& aLink)
	: CNifIfBase(aLink)
	{
	CDummyIfLog::Printf(_L("CDummyIf6::CDummyIf6()"));

	iLink = &aLink;

	//
	// Use the 64 bit id of MARM machines as our interface id
	//
	TMachineInfoV1Buf machineInfo;

	UserHal::MachineInfo(machineInfo);
	iLocalIfId.SetAddr(machineInfo().iMachineUniqueId);
	iLocalIfId.SetUniversalBit(0);

	//
	// In WINS environment the id is zero which is no-no
	//
	if (iLocalIfId.IsZero())
		iLocalIfId.SetAddrRandomNZ();

	iIfName.Format(_L("dummynif6[0x%08x]"), this);
	}

void CDummyIf6::BindL(TAny *aId)
	{
	CDummyIfLog::Printf(_L("CDummyIf6::BindL(aId %x)"), aId);
	if(iProtocol)
		User::Leave(KErrAlreadyExists);
	iProtocol = (CProtocolBase*)aId;	
	}

TInt CDummyIf6::Send(RMBufChain& aPdu, TAny*)
	{
	Recv(aPdu);
	return 1;
	}

TInt CDummyIf6::State()
    {
    return EIfUp;
    }

void CDummyIf6::UpdateHeaders(TInet6HeaderIP* aIp6, TInet6HeaderUDP* /*aUdp*/)
{
	// swap over the destination and source addresses
	TIp6Addr temp;
	temp = aIp6->SrcAddr();
	aIp6->SetSrcAddr(aIp6->DstAddr());
	aIp6->SetDstAddr(temp);
}

void CDummyIf6::Recv(RMBufChain& aPdu)
	{

	TInt res;

	// this received data has already been looped back...
	// get the ip header from the RMBufChain
	TInet6HeaderIP* ip6 = (TInet6HeaderIP*) aPdu.First()->Next()->Ptr();
	TInet6HeaderUDP* udp = NULL;

	if ((TUint)ip6->NextHeader() == KProtocolInetUdp)
		{
		// get the udp header as well - assume only udp traffic here
		udp = (TInet6HeaderUDP*) ip6->EndPtr();

		CDummyIfLog::Printf(_L("CDummyIf6::Recv(...): UDP length %d, src port %d, dst port %d"),
			udp->Length(), udp->SrcPort(), udp->DstPort());

		// depending on the contents, pass it on up thru the stack 
		// or maybe do something else

		// use the destination port number to decide whether or not the payload is a command
		TUint dstPort = udp->DstPort();
		if (KDummyNifCmdPort == dstPort)
			{	
			// let's use the first payload byte as the command byte
			switch (*(udp->EndPtr()))
				{
			case KForceDisconnect:
				CDummyIfLog::Printf(_L("KForceDisconnect command"));
				// do some action
				iNotify->IfProgress(KLinkLayerClosed, KErrCommsLineFail);
				iNotify->LinkLayerDown(KErrCommsLineFail, MNifIfNotify::EDisconnect);
				// no return code so all we can do is respond with what we got
				UpdateHeaders(ip6, udp);
				iProtocol->Process(aPdu, (CProtocolBase*)this);
				break;

			case KForceReconnect:
				CDummyIfLog::Printf(_L("KForceReconnect command"));
				// do some action
				iNotify->IfProgress(KLinkLayerClosed, KErrCommsLineFail);
				iNotify->LinkLayerDown(KErrCommsLineFail, MNifIfNotify::EReconnect);
				// no return code so all we can do is respond with what we got
				UpdateHeaders(ip6, udp);
				iProtocol->Process(aPdu, (CProtocolBase*)this);
				break;

			case KSendNotification:
				CDummyIfLog::Printf(_L("KSendNotification command"));
				res = iNotify->Notification(ENifToAgentEventTypeQueryIsDialIn);
				//let's write the result in the next byte of the reply
				if (res == KErrNotSupported)
					udp->EndPtr()[1] = (unsigned char) KErrNone;
				else
					udp->EndPtr()[1] = (unsigned char) KErrGeneral; // this will lose it's sign :-(
				
				UpdateHeaders(ip6, udp);
				iProtocol->Process(aPdu, (CProtocolBase*)this);
				break;

			default:
				CDummyIfLog::Printf(_L("Unknown command - ignoring it"));
				break;
				// unknown command, just ignore this packet???
				}
			return;
			}

		}
	else
		{
		CDummyIfLog::Printf(_L("CDummyIf6::Recv(...): IPv6 length %d, next header %d"),
			ip6->PayloadLength(), ip6->NextHeader());
		}

	// just echo the packet back to the original sender

	// update the headers (addresses, checksums etc).  If "udp" is non-NULL, then
	// the UDP ports will be updated as well.
	UpdateHeaders(ip6, udp);
	// now process it (pass up the stack)
	iProtocol->Process(aPdu, (CProtocolBase*)this);		
}

void CDummyIf6::Info(TNifIfInfo& aInfo) const
	{
	aInfo.iVersion = TVersion(1,1,1);
	aInfo.iFlags = KNifIfIsBase | KNifIfUsesNotify | KNifIfCreatedByLink;
	aInfo.iName.Copy(iIfName);
	aInfo.iProtocolSupported = 0;
	}

TInt CDummyIf6::Control(TUint aLevel, TUint aName, TDes8& aOption, TAny* /* aSource */)
	{

	if (aLevel==KSOLInterface)
		{
		switch (aName)
			{
		case KSoIfInfo6:
			{
			CDummyIfLog::Printf(_L("CDummyIf6::Control(KSOLInterface, KSoIfInfo6, ...)"));
			__ASSERT_DEBUG((TUint)aOption.MaxLength() >= sizeof (TSoIfInfo6), User::Panic(_L("Dummy6"), 0));

			if ((TUint)aOption.MaxLength() < sizeof (TSoIfInfo6))
				return KErrArgument;

			TSoIfInfo6& opt = *(TSoIfInfo6*)aOption.Ptr();
			opt.iFeatures = KIfCanBroadcast | KIfCanMulticast;
			opt.iName.Copy(iIfName);
			opt.iMtu = 1500;
			opt.iRMtu = opt.iMtu;
			opt.iSpeedMetric = 0;

			return KErrNone;
			}

		case KSoIfHardwareAddr:
			return KErrNotSupported;

		case KSoIfConfig:
			{
			CDummyIfLog::Printf(_L("CDummyIf6::Control(KSOLInterface, KSoIfConfig, ...)"));
			__ASSERT_DEBUG((TUint)aOption.MaxLength() >= sizeof (TSoInet6IfConfig), User::Panic(_L("Dummy6"), 0));

			if ((TUint)aOption.MaxLength() < sizeof (TSoInet6IfConfig))
				return KErrArgument;

			TSoInet6IfConfig& opt = *(TSoInet6IfConfig*)aOption.Ptr();
			if (opt.iFamily != KAfInet6)
				return KErrNotSupported;

			TEui64Addr* ifId = (TEui64Addr*)&opt.iLocalId;

			ifId->Init();
			ifId->SetAddress(iLocalIfId);

			ifId = (TEui64Addr*)&opt.iRemoteId;
			ifId->Init();
			ifId->SetAddress(iRemoteIfId);

			// Setup static DNS address if required

			if (!iPrimaryDns.IsUnspecified())
				{
				opt.iNameSer1.SetAddress(iPrimaryDns);
				if (!iSecondaryDns.IsUnspecified())
					opt.iNameSer2.SetAddress(iSecondaryDns);
				}

			opt.idPaddingBits = 0;
			return KErrNone;
			}

		case KSoIfGetConnectionInfo:
			{
			CDummyIfLog::Printf(_L("CDummyIf6::Control(KSOLInterface, KSoIfGetConnectionInfo, ...)"));
			TSoIfConnectionInfo& opt = *(TSoIfConnectionInfo*)aOption.Ptr();
			TInt err = KErrNone;
			TBuf<2*KCommsDbSvrMaxColumnNameLength+1> fieldName;
			_LIT(KSlashChar, "\\");

			fieldName.Copy(TPtrC(IAP));
			fieldName.Append(KSlashChar);
			fieldName.Append(TPtrC(COMMDB_ID));
			if ((err = iNotify->ReadInt(fieldName, opt.iIAPId)) != KErrNone)
				return err;

			fieldName.Copy(TPtrC(IAP));
			fieldName.Append(KSlashChar);
			fieldName.Append(TPtrC(IAP_NETWORK));
			if ((err = iNotify->ReadInt(fieldName, opt.iNetworkId)) != KErrNone)
				return err;

			return KErrNone;
			}
		default:
			CDummyIfLog::Printf(_L("CDummyIf6::Control(KSOLInterface, aName %x, ...)"), aName);
			break;
			}
		}
	else
		{
		CDummyIfLog::Printf(_L("CDummyIf6::Control(aLevel %x, aName %x, ...)"), aLevel, aName);
		}
	return KErrNotSupported;
	}

TInt CDummyIf6::Notification(TAgentToNifEventType /*aEvent*/, void * /*aInfo*/)
	{
	return KErrNone;
	}

void CDummyIf6::StaticDnsConfiguration()
	{
	TBool requestDynamicDNSAddress;
	// See whether we are configured to request a dynamic DNS address.
	if (iNotify->ReadBool(TPtrC(SERVICE_IP6_DNS_ADDR_FROM_SERVER), requestDynamicDNSAddress) != KErrNone)
		{
		// Default behaviour
		requestDynamicDNSAddress = ETrue;
		}

	if(!requestDynamicDNSAddress)
		{
		// Setup static DNS addresses from CommDb
		CDummyIfLog::Printf(_L("CDummyIf6: Configuring static DNS addresses"));
		PresetAddr(iPrimaryDns, TPtrC(SERVICE_IP6_NAME_SERVER1));
		PresetAddr(iSecondaryDns, TPtrC(SERVICE_IP6_NAME_SERVER2));
		}
	else
		{
		// Ensure that static DNS addresses are set as unspecified,
		// so they are not used in Control(KSoIfConfig).
		iPrimaryDns = KInet6AddrNone;
		iSecondaryDns = KInet6AddrNone;
		}
	}

TInt CDummyIf6::PresetAddr(TIp6Addr& aAddr, const TDesC& aVarName)
/**
 * Preset IP adress in aAddr with value from CommDB. The field name
 * is given in aVarName. Examples are "IpAddr" and "IpNameServer1"
 *
 * @param aAddr    IP address to be set
 * @param aVarName name of CommDB field
 * @return KErrNone if success, global error code otherwise
 */
	{
	if (!aAddr.IsUnspecified())
		return KErrNone;
	
	TBuf<KCommsDbSvrMaxFieldLength> name;

	(void)iNotify->ReadDes(aVarName, name); // ignore return value
	TInetAddr ip6Addr;

	TInt ret = ip6Addr.Input(name);
	if (ret == KErrNone)
		{
		aAddr = ip6Addr.Ip6Address();
		}
	return ret;
	}
