// Copyright (c) 1998-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 <treng.h>
#include <e32hal.h>
#include <icmp6_hdr.h>
#include <in_chk.h>
#include <commdbconnpref.h>

const TUint KDefaultMaxTtl = 30;
const TInt KDefaultWait = 5000000;
const TInt KDefaultNrProbes = 3;

const TInt KMinIpHeaderSize = 20;
const TInt KMaxSendTime=60000000;
const TInt KRecvDataSize=512;
const TInt KResolvTime=10000000;

const TInt KIcmpHeaderSize = 8;

class CTraceRtTimer : public CTimer
	{
friend class CTraceRtEng;
protected:
	CTraceRtTimer(CTraceRtEng& aParent);
	void RunL();
private:
	CTraceRtEng* iParent;
	};

class CTraceRtSender : public CActive
	{
friend class CTraceRtEng;
protected:
	CTraceRtSender(CTraceRtEng& aParent);
	~CTraceRtSender();
	void RunL();
	void DoCancel();
private:
	CTraceRtEng* iParent;
	};

class CTraceRtReceiver : public CActive
	{
friend class CTraceRtEng;
protected:
	CTraceRtReceiver(CTraceRtEng& aParent);
	~CTraceRtReceiver();
	void RunL();
	void DoCancel();
private:
	CTraceRtEng* iParent;
	};


// Class to manage ICMP headers information
class HTraceRtHeader : public TInet6HeaderICMP_Echo
	{
public:
	static HTraceRtHeader* NewL(TInt aSize = KIcmpHeaderSize);
	~HTraceRtHeader();

	TBool VerifyRecv(TInt aIdent, TInt aSeq);
	void FormatSend(TUint aId, TUint aSeq);
	TInt MaxLength();
	TInt DataLength();
	TPtr8* Grab();

private:
	void ConstructL(TInt aSize);
	TBool SetHeader(TUint aOffset = 0);
	
	HBufC8* iData;
	TPtr8* iDataPtr;	// Packet data
	};

enum TTraceRtEngPanic
	{

    ETimerPriorityGreaterThanSender,	// 0
	ESenderPrirityGreaterThanReceiver	// 1
	};

LOCAL_C void Panic(TTraceRtEngPanic aPanic)
//
// Panic the user
//
	{

	User::Panic(_L("TraceRtEng"), aPanic);
	}

EXPORT_C TTraceRtOptions::TTraceRtOptions()
//
// Default TraceRt options
//
	{

	iMaxTtl=KDefaultMaxTtl;
	iResolveAddress=ETrue;
	iWait=KDefaultWait;
	iTos=0;
	iDestname.SetLength(0);
	iNrProbes=KDefaultNrProbes;
	iPrompt = EFalse;
	}

EXPORT_C CTraceRtEng* CTraceRtEng::NewL(MTraceRtNotificationHandler& aUi)
//
// Create a new TraceRt engine
//
	{

	CTraceRtEng* p= new(ELeave) CTraceRtEng;
	CleanupStack::PushL(p);
	p->ConstructL(aUi);
	CleanupStack::Pop();
	return p;
	}

EXPORT_C CTraceRtEng::CTraceRtEng()
//
// Declare a name
//
	{	
	
	__DECLARE_NAME(_S("CTraceRtEng"));
	}

EXPORT_C CTraceRtEng::~CTraceRtEng()
//
// Destroy wot TraceRt created
	{

	iIcmpSocket.Close();
	iResolver.Close();
	iConnect.Close();
	iSocketServ.Close();
	delete iReceiver;
	delete iSender;
	delete iTimer;
	delete iRecvData;
	delete iSendData;
	}

EXPORT_C void CTraceRtEng::ConstructL(MTraceRtNotificationHandler& aUi)
//
// Construct and heap objects
//
	{

	iUi = &aUi;
	iTimer = new (ELeave) CTraceRtTimer(*this);
	iTimer->ConstructL();
	iSender = new (ELeave) CTraceRtSender(*this);
	iReceiver = new (ELeave) CTraceRtReceiver(*this);
	User::LeaveIfError(iSocketServ.Connect());
	iSendData = NULL;
	iRecvData = NULL;
	iResolv = EFalse;
	}

EXPORT_C void CTraceRtEng::SetPriorities(TInt aTimerPriority, TInt aSenderPriority, TInt aReceiverPriority)
//
//	Set various active object priorities
//
	{	

	__ASSERT_ALWAYS(aTimerPriority < aSenderPriority, Panic(ETimerPriorityGreaterThanSender));
	__ASSERT_ALWAYS(aSenderPriority < aReceiverPriority, Panic(ESenderPrirityGreaterThanReceiver));

	iTimer->SetPriority(aTimerPriority);
	iSender->SetPriority(aSenderPriority);
	iReceiver->SetPriority(aReceiverPriority);
	}

EXPORT_C void CTraceRtEng::Start(const TTraceRtOptions& aOptions)
//
// Start a TraceRt
//
	{

	// Reset All Variables
	iProbeNr=0;
	iTtl=1;
	iSeq=0;
	iNameEntry().iName.SetLength(0);
	iNameEntry().iFlags=0;
	TInetAddr::Cast(iNameEntry().iAddr).SetAddress(0);
	iGotThere=EFalse;
	iUnreachCount=0;
	iIdent=User::TickCount()&KMaxTUint16;
	iLastCode=KTraceRtCodeTimeout;

	iOptions=aOptions;


    if(iOptions.iMaxTtl>255 || iOptions.iTos>255
		|| iOptions.iNrProbes>100 || iOptions.iNrProbes<1)
		{
		DoError(KErrNotSupported);
		return;
		}

	if(iSendData)
		{
		delete iSendData;
		}
	iSendData = HTraceRtHeader::NewL(KIcmpHeaderSize);

	if(iRecvData)
		{
		delete iRecvData;
		}
	iRecvData = HTraceRtHeader::NewL(KRecvDataSize);

	if(!iRecvData || !iSendData)
		{
		DoError(KErrNoMemory);
		}
	else if(iOptions.iDestname.Length()>0)
		{	
		iConnect.Close();
		User::LeaveIfError(iConnect.Open(iSocketServ, KConnectionTypeDefault));
		
		TCommDbConnPref commDbPref;
		if(!iOptions.iPrompt)
			{
			commDbPref.SetDialogPreference(ECommDbDialogPrefDoNotPrompt);
			}
		else
			{
			commDbPref.SetDialogPreference(ECommDbDialogPrefPrompt);
			}
		User::LeaveIfError(iConnect.Start(commDbPref));
		
		TInt err=iResolver.Open(iSocketServ, KAfInet, KProtocolInetUdp, iConnect);
		if(err==KErrNone)
			{

			TInetAddr& addr = (TInetAddr&)iNameEntry().iAddr;
			if ((err=addr.Input(iOptions.iDestname))==KErrNone)
				{

				if(iOptions.iResolveAddress)
					{
					iState=ELookingUpHost;
					iResolver.GetByAddress(addr, iNameEntry, iSender->iStatus);
					iSender->SetActive();
					iTimer->After(KResolvTime);
					}
				else
					{
					iSender->iStatus=KErrNone;
					iState=ELookingUpHost;
					SendComplete();
					}
				}
			else
				{
				iState=ELookingUpHost;
				iResolver.GetByName(iOptions.iDestname, iNameEntry, iSender->iStatus);

				iSender->SetActive();
				iTimer->After(KResolvTime);
				}
			}
		else
			{
			DoError(err);
			}
		}
	else
		{
		DoError(KErrBadName);
		}
	}

EXPORT_C void CTraceRtEng::CancelAndFinished()
//
// Cancel from the UI
//
	{
	
	if(iState!=EStopped || iTimer->IsActive()) 
		{
		Cancel();
		iUi->Finished(KErrCancel);
		}
	}

EXPORT_C void CTraceRtEng::Cancel()
//
// Cancel a TraceRt in progress
//
	{

	iSender->Cancel();
	iReceiver->Cancel();	
	iTimer->Cancel();
	
	iIcmpSocket.Close();
	iResolver.Close();

	iState=EStopped;
	}

void CTraceRtEng::DoError(TInt aError)
//
// Generate an error from somewhere
//
	{
	
	Cancel();
	iTimer->SetActive();
	TRequestStatus* p = &iTimer->iStatus;
	User::RequestComplete(p, aError);
	}

void CTraceRtEng::TimerComplete()
//
// Timer event completed
//
	{

	if(iTimer->iStatus==KErrNone)
		{

		if(iSender->IsActive())
			{
			if(iResolv)
				{
				// We didn't manage to get the name
				// from the address, so we pass on
				iResolv = EFalse;
				// Reset sender
				iSender->Cancel();
				// Clear previous name
				iNameEntry().iName.SetLength(0);
				// Continue
				NextSend();
				}
			else
				{
				Cancel();
				iUi->Finished(KErrTimedOut);
				}
			}
		else
			{
			iUi->Reply(iProbeNr, 0, KTraceRtCodeTimeout);
			switch(iLastCode)
				{
			case KTraceRtCodeUnreachNet:
			case KTraceRtCodeUnreachHost:
				++iUnreachCount;
				break;
			default:
				break;
				}
			NextSend();
			}
		}
	else
		{
		Cancel();
		iUi->Finished(iTimer->iStatus.Int());
		}
	}

void CTraceRtEng::SendComplete()
//
// A send operation has completed
//
	{

	TInt err=iSender->iStatus.Int();
	iTimer->Cancel();
	iResolv = EFalse;

	switch(iState)
		{

	case ELookingUpHost:
		iDstAddr = iNameEntry().iAddr;
		if(iDstAddr.Address()!=0)
			{		
			TInt res=iIcmpSocket.Open(iSocketServ, KAfInet, KSockDatagram, KProtocolInetIcmp, iConnect);
			
			if(res==KErrNone)
				{
				res=iIcmpSocket.SetOpt(KSORecvBuf, KSOLSocket, iRecvData->MaxLength());
				}
			if(res==KErrNone)
				{
				res=iIcmpSocket.SetOpt(KSOSendBuf, KSOLSocket, iSendData->MaxLength());
				}
			if(res==KErrNone)
				{
				res=iIcmpSocket.SetOpt(KSoIpTOS, KSolInetIp, (TInt)iOptions.iTos);
				}

			if(res!=KErrNone)
				{
				DoError(res);
				return;
				}
			
			// Tell the UI
			iUi->Starting(iNameEntry(), iOptions.iMaxTtl, iSendData->MaxLength());
			iState=ESending;
			NextSend();
			}
		else
			{
			DoError(err!=KErrNone ? err : KErrNotFound);
			}
		break;

	case ELookingUpReply:
		if(err!=KErrNone)
			{
			iNameEntry().iName.SetLength(0);
			}
		NextSend();
		break;

	default:
		if(err==KErrNone)
			{
			iTimer->After(iOptions.iWait);
			iIcmpSocket.RecvFrom(*(iRecvData->Grab()), iSrcAddr, 0, iReceiver->iStatus);
			iReceiver->SetActive();
			}
		else
			{
			DoError(err);
			}
		}
	}

void CTraceRtEng::NextSend()
//
// Initiate the next send
//
	{

	iReceiver->Cancel();

	if(iProbeNr>=iOptions.iNrProbes)
		{

		if(iState==ELookingUpReply || !iOptions.iResolveAddress || TInetAddr::Cast(iNameEntry().iAddr).Address()==0)
			{

			if(!iOptions.iResolveAddress)
				{
				iNameEntry().iName.SetLength(0);
				}

			iUi->FromHost(iNameEntry());
			if(iGotThere || iUnreachCount>=iOptions.iNrProbes )
				{
				Cancel();
				iUi->Finished(KErrNone);
				return;
				}

			TInetAddr::Cast(iNameEntry().iAddr).SetAddress(0);
			++iTtl;
			iProbeNr=0;
			iLastCode=KTraceRtCodeTimeout;
			iUnreachCount=0;
			iState=ESending;
			}
		else
			{
			iState=ELookingUpReply;

			// Work around for bug in pre-61 versions of TCPIP
			TProtocolDesc info;
			if(iIcmpSocket.Info(info)==KErrNone)
				{
				TVersion &v=info.iVersion;
				if(v.iMajor==1 && v.iMinor==0 && v.iBuild<61)
					{
					iResolver.Close();
					TInt res=iResolver.Open(iSocketServ, KAfInet, KProtocolInetUdp, iConnect);
					if(res!=KErrNone)
						{
						DoError(res);
						return;
						}
					}
				}
			//  Work around ends

			iResolver.GetByAddress(iNameEntry().iAddr, iNameEntry, iSender->iStatus);
			iSender->SetActive();
			iResolv = ETrue;
			iTimer->After(KResolvTime);
			return;
			}
		}

	if(iTtl>iOptions.iMaxTtl)
		{
		Cancel();
		iUi->Finished(KErrNone);
		return;
		}

	if(iProbeNr==0)
		{
		iUi->Probe(iTtl);
		}
	
	iSendData->FormatSend(++iSeq, iIdent);
	TInt res=iIcmpSocket.SetOpt(KSoIpTTL, KSolInetIp, (TInt)iTtl);
	if(res!=KErrNone)
	{
		DoError(res);
		return;
	}
	iIcmpSocket.SendTo(*(iSendData->Grab()), iDstAddr, 0, iSender->iStatus);
	iSendTime.UniversalTime();
	iSender->SetActive();
	iTimer->After(KMaxSendTime);
//	pg 13/03/2000 - Base removing this api
//	UserHal::ResetAutoSwitchOffTimer();

	++iProbeNr;
	return;
	}

void CTraceRtEng::SendDoCancel()
//
// A send operation requires cancelling
//
	{

	if(iState==ELookingUpHost || iState==ELookingUpReply)
		{
		iResolver.Cancel();
		}
	else if(iState==ESending)
		{
		iIcmpSocket.CancelSend();
		}
	}

void CTraceRtEng::RecvComplete()
//
// A recv operation has completed
//
	{

	if(iReceiver->iStatus==KErrNone)
		{
		
		if(iState!=ESending)
			{
			return;
			}

		if(iRecvData->VerifyRecv(iSeq, iIdent))
			{
			
			iNameEntry().iAddr = iSrcAddr;
			TTime now;
			now.UniversalTime();
			TTimeIntervalMicroSeconds delta = now.MicroSecondsFrom(iSendTime);
			TUint code;

			switch(iRecvData->Type())
				{
			case KTraceRtTypeTimeExceeded:
				code = KTraceRtCodeTimedOutInTransit;
				break;
			case KTraceRtTypeEchoReply:
				code = KTraceRtCodeEchoReply;
				iGotThere=ETrue;
				break;
			default:
				code=iRecvData->Code();
				++iUnreachCount;
				}

			iLastCode=code;

			delta = delta.Int64()/TInt64(1000);
			iUi->Reply(iProbeNr, I64LOW(delta.Int64()), code);

			iTimer->Cancel();	
			NextSend();
			return;
			}
		if(!iSender->IsActive())
			{
			iLastCode = iRecvData->Code();
			iIcmpSocket.RecvFrom(*(iRecvData->Grab()), iSrcAddr, 0, iReceiver->iStatus);
			iReceiver->SetActive();
			}
		}
	else
		{
		DoError(iReceiver->iStatus.Int());
		}
	}

void CTraceRtEng::RecvDoCancel()
//
// A send operation requires cancelling
//
	{

	iIcmpSocket.CancelRecv();
	}

CTraceRtTimer::CTraceRtTimer(CTraceRtEng& aParent)
//
// To time events
//
	: CTimer(ETraceRtTimerPriority)
	{
	
	iParent = &aParent;
	CActiveScheduler::Add(this);
	__DECLARE_NAME(_S("CTraceRtTimer"));
	}

void CTraceRtTimer::RunL()
//
//	Timer is complete
//
	{

	iParent->TimerComplete();
	}

CTraceRtSender::CTraceRtSender(CTraceRtEng& aParent)
//
// C'tor
//
	: CActive(ETraceRtSenderPriority)
	{

	iParent = &aParent;
	CActiveScheduler::Add(this);
	__DECLARE_NAME(_S("CTraceRtSender"));
	}

CTraceRtSender::~CTraceRtSender()
//
// D'tor cancels
//
	{

	Cancel();
	}

void CTraceRtSender::RunL()
//
// Upcall to parent
//
	{

	iParent->SendComplete();
	}

void CTraceRtSender::DoCancel()
//
// Get parent to cancel send
//
	{
	
	iParent->SendDoCancel();
	}

CTraceRtReceiver::CTraceRtReceiver(CTraceRtEng& aParent)
//
// C'tor
//
	: CActive(ETraceRtReceiverPriority)
	{

	iParent = &aParent;
	CActiveScheduler::Add(this);
	__DECLARE_NAME(_S("CTraceRtReceiver"));
	}

CTraceRtReceiver::~CTraceRtReceiver()
//
// D'tor cancels
//
	{

	Cancel();
	}

void CTraceRtReceiver::RunL()
//
// Upcall to parent
//
	{

	iParent->RecvComplete();
	}

void CTraceRtReceiver::DoCancel()
//
// Get parent to cancel send
//
	{
	
	iParent->RecvDoCancel();
	}


HTraceRtHeader::~HTraceRtHeader()
//
// D'tor deletes 
//
	{
	
	delete iData;
	delete iDataPtr;
	}

HTraceRtHeader* HTraceRtHeader::NewL(TInt aSize)
//
// Create a new trace route header
//
	{	
	
	HTraceRtHeader* h = new(ELeave) HTraceRtHeader();

	CleanupStack::PushL(h);
	h->ConstructL(aSize);
	CleanupStack::Pop(h);	

	return h;
	}

void HTraceRtHeader::ConstructL(TInt aSize)
	{
	
	iData = HBufC8::NewL(aSize);
	iDataPtr = new(ELeave) TPtr8(iData->Des());
	
	iData->Des().FillZ();
	}

TInt HTraceRtHeader::MaxLength()
	{
	
	return iData->Des().MaxLength(); 
	}

TInt HTraceRtHeader::DataLength()
	{
	
	return iData->Des().Length(); 
	}

TPtr8* HTraceRtHeader::Grab()
	{
	
	iDataPtr->Copy(iData->Des());
	return iDataPtr;
	}

TBool HTraceRtHeader::SetHeader(TUint aOffset)
//
// Set the header from an Icmp reply
// 
	{
	
	const TUint8* buffData;
	
	// Check size
	if(DataLength() < KIcmpHeaderSize)
		{
		return EFalse;
		}
	
	buffData = iData->Des().Ptr();

	if(!buffData)
		{
		return EFalse;
		}
	
	// Fill TInet6HeaderICMP_Echo from the buffer
	for(int k=0;k<KIcmpHeaderSize;k++)
		{
		i[k] = *(buffData + k + aOffset);
		}

	return ETrue;
	}

TBool HTraceRtHeader::VerifyRecv(TInt aSeq, TInt aIdent)
//
// Verify header is valid
//
	{
	
	TBool ret = SetHeader();

	if(ret)
		{
		ret = EFalse;
		
		if(Type() == KTraceRtTypeEchoReply && Identifier() == aIdent && Sequence() == aSeq)
			{
			ret = ETrue;
			}

		if(!ret && ((Type() == KTraceRtTypeTimeExceeded && Code() == KTraceRtCodeExceedInTransit) || Type() == KTraceRtTypeUnreachable))
			{
			TUint code = Code();
			TUint type = Type();
			ret = SetHeader(KMinIpHeaderSize + KIcmpHeaderSize);

			if(ret)
				{
				ret = EFalse;
			
				if(Type() != KTraceRtTypeEchoRequest || Identifier() != aIdent || Sequence() != aSeq)
					{
					ret = EFalse;
					}
				else
					{
					ret = ETrue;
					}
				}
			SetCode(static_cast<TUint8>(code));
			SetType(static_cast<TUint8>(type));
			}
		}

	return ret;
	}


void HTraceRtHeader::FormatSend(TUint aSeq, TUint aIdent)
//
// Format an ICMP Header
//
	{
	
	TChecksum sum;

	// Fill header
	SetType(KTraceRtTypeEchoRequest);
	SetCode(KTraceRtCodeEcho);
	SetIdentifier(static_cast<TUint16>(aIdent));
	SetSequence(static_cast<TUint16>(aSeq));

	// Compute cheksum
	SetChecksum(0);
	sum.Add(reinterpret_cast<TUint16*>(this), HeaderLength());
	SetChecksum(sum.Sum());

	// Copy the ICMP Header in the buffer
	iData->Des().Copy((TUint8*)this, HeaderLength());
	}

