// Copyright (c) 2000-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:
// pingmodel.cpp - icmp echo client engine
// This software has been implemented in the 6PACK
// project at the Mobile Networks Laboratory (MNW)
// http://www.research.nokia.com/projects/6pack/
// REMARKS:
// The application is prepared to admit the option Hop Limit and
// Numeric output (no name resolution), but these options are not used
// because the socket is open in ICMP mode.  To use them need an IP
// socket and IP packets to set the options. Some code is commented and
// can be used in case of need
//



#ifdef IAPSETTING
#include <commdb.h>
#endif


#include <e32math.h>
#include <e32std.h>
#include <eikenv.h>
//#include <netdial.h>

// IPv6 changes CPing::iType, ComposeFirstIcmpPacket()- ICMPType, Socket Open()

#include "pingmodel.h"
#include <pingapp.rsg>
#include "ping.h"


CPing::CPing():CActive(EPriorityStandard),iPacket(0,0),iReceivedData(0,0)
{
	CActiveScheduler::Add(this);	//Adds itself to the scheduler only the first time
}



//Sets all default values. Actually it's no more a L function
void CPing::ConstructL(TPreferences aPref)
{

	// Base class second-phase construction.

	//iHostname=_L("bart.research.nokia.com");	//default initial addr
	//iHostname=_L("127.0.0.1");	//default initial addr
	//iHostname=_L("dead::beef");	//default initial addr
	iHostname=aPref.iHostname;
	iPackLimit = aPref.iFlags & KPingPackLimit;		//Unlimited packets
	iTotalPackets=aPref.iTotalPackets;	//Default packet number when limited number
	
	iSecWait=aPref.iSecWait;		//Default 1 second
	iPacketDataSize=aPref.iPacketDataSize;	//Default Size
	iQuiet=aPref.iFlags & KPingQuiet;	//No packet info, just statistics
		
	iVerbose=aPref.iFlags & KPingVerbose;	//Verbose Output. All ICMP packets, not only Echo reply. Default no
	iPattern.Copy(aPref.iPattern);			//Pattern 0xFF (1-filled byte)
	
	iDebug=aPref.iFlags & KPingDebug;		//Set SO_DEBUG flag
	iLastSecWait=aPref.iLastSecWait;	//Default time to wait for the last packet

#ifdef IAPSETTING
	iIAP = aPref.iIAP;		
#endif

	//Not used by now
	iNumericOutput=EFalse;	//Resolve adresses by default
	iHopLimit=0;			//Time-to-live in hops default 255 ( 1 to 255) (0 means not set)

	iDupPackets=0;			//Duplicated packets
	iRunning=EFalse;		// Tells if there's an Instance of Ping Running


	
/*
	iReceivedDataBuffer= HBufC8::NewL(iPacketDataSize + ICMP_ECHO_HEADER_SIZE);	//Maximum size of a return packet
	TPtr8 auxPtr(iReceivedDataBuffer->Des());	//must be used here because the buffer changes
	iReceivedData.Set(auxPtr);			//we use an aux var because can't use Des() directly Why??
*/
}

//return the current preferences
void CPing::GetPreferences(TPreferences &aPref)
{	
	aPref.iFlags=0;
	if (iQuiet)
		aPref.iFlags|=KPingQuiet;
	if (iVerbose)
		aPref.iFlags|=KPingVerbose;
	if (iPackLimit)
		aPref.iFlags|=KPingPackLimit;
	if (iDebug)
		aPref.iFlags|=KPingDebug;

	aPref.iSecWait=iSecWait;               //Time between sent packets (Default 1 second)
	aPref.iPacketDataSize=iPacketDataSize;//Default Data Size (not including ICMP header)
	aPref.iTotalPackets=iTotalPackets;  //Number of ICMP Echo Request packets to send
	aPref.iLastSecWait=iLastSecWait;   //Time to wait for the Last packet. Default 2
	aPref.iHostname=iHostname;		//Address to Ping
	aPref.iPattern.Copy(iPattern);

#ifdef IAPSETTING	
	aPref.iIAP = iIAP;
#endif
		
}

void CPing::DefaultPreferences(TPreferences &aPref)
{	
	aPref.iFlags=KPingVerbose|KPingPackLimit;

	aPref.iSecWait=1;               //Time between sent packets (Default 1 second)
	aPref.iPacketDataSize=56;//Default Data Size (not including ICMP header)
	aPref.iTotalPackets=5;  //Number of ICMP Echo Request packets to send
	aPref.iLastSecWait=2;   //Time to wait for the Last packet. Default 2
	aPref.iPattern=_L("FF");
	aPref.iHostname=_L("127.0.0.1");
#ifdef IAPSETTING
	aPref.iIAP=1;
#endif
}

void CPing::Statistics()
{
	TBuf<300> aux(_L("==========================================\n"));
	
	TReal rLoss=iSentPackets-iRecvPackets-iChksumErrors;	// An error is a received but wrong packet
										//is not masked as received because of time calculations
	if (rLoss<0) 
		rLoss=0;
	TReal rSent=iSentPackets;
	TReal r;
	if (rSent>0)
		r=rLoss/rSent;
	else
		r=0;

	aux.AppendFormat(_L("Lost: %.1f%% Bad: %d"), r*100, iChksumErrors);

	//If there's a timestamp and data received
	if ((iRecvPackets>0) && (iPacketDataSize >= TIMESTAMP_SIZE))
	{
		r=(iTimeSum/iRecvPackets);	//average in ms
		aux.AppendFormat(_L(" Max: %d Min: %d Avg: "),iMaxTime,iMinTime);
		TRealFormat format;	//no decimals
		format.iType|=KDoNotUseTriads;	// no thousands separatorlast
		aux.AppendNum(r,format);
		aux.Append(_L(" ms"));
	}

	if (iDupPackets>0)
		aux.AppendFormat(_L(" Dup: %d"),iDupPackets);

	aux.Append(_L("\n==========================================\n"));
	iConsole->WriteLine(aux);
}

// Use when data is not going to be displayed in Quiet mode
void CPing::WriteLineIfNotQuiet(const TDesC& abuf)
{
	if (!iQuiet)
		iConsole->WriteLine(abuf);
}

TDes* CPing::GetHostName()
{
	return &iHostname;
}

void CPing::SetHostName(TDes& ahostname)
{
	iHostname=ahostname;
}

void CPing::SetConsole(CPingContainer* aConsole)
{
	iConsole=aConsole;
}

CPing::~CPing()
{
/*
	if (IsAdded())		//Remove from the A.S
		Deque();	//Dequeues the active object of the Scheduler. Calls DoCancel()	
*/
	if (IsActive())
		Cancel();

	delete iPacketData;		// If something leaves.
	delete iReceivedDataBuffer;
}

//Shows the error and set the application as not running. 
//Requires a return after calling it!
void CPing::ErrorL(const TDesC& string,TInt error)
{
	
	TBuf<150> aux;
	TBuf<100> errtxt;

	CEikonEnv::Static()->GetErrorText( errtxt,error);
	aux.Format(string);	
	aux.Append(_L(": "));
	aux.Append(errtxt);
	aux.AppendFormat(_L(" (%d)\n"), error);
	WriteLineIfNotQuiet(aux);
	//iRunning=EFalse;

	iSockErrors++;

	if (error==KErrAbort)	//Critical Error
	{

/*		RNetDial xnetdial;
		TBool active;

		xnetdial.Open();
		TInt err=xnetdial.NetworkActive(active);
	
		if (!active)
		{
			xnetdial.Start();
			err=xnetdial.NetworkActive(active);
		}
*/
		EndPingL();
		User::Leave(0);	// NOT SURE IF IT'S THE BEST WAY!!!
	}

	if (iSockErrors>5)	//To avoid a chain of errors that blocks everything. Should never happen though 
	{
		EndPingL();
		User::Leave(0);	// NOT SURE IF IT'S THE BEST WAY!!!
	}
	
}


// checksum of the ICMP packet (ICMP header + data)
TUint16 CPing::in_chksum(TUint16* data, TUint len)
{
	
	TUint16 *d=data;
	TUint left=len;
	TUint32 sum=0;
	TUint16 aux=0;
	
	while (left > 1)
	{
		sum += *d++;
		left -= 2;		//because adding 16 bits numbers (2 bytes)
	}

	if (left==1)	//If odd length
	{
		*(TUint8*) &aux = *(TUint8*) d;
		sum += aux;
	}
	
	sum = (sum >> 16) + (sum & 0x0ffff);
	sum += (sum >> 16);
	aux = (TUint16) ~sum;

	return aux;
	
}

// Clears a bit from the iDup table

void CPing::CLR(TUint16 num)
{
	TInt num256 = num % 256;		//num modulus 256
	TInt pos = num256 >> 5;		//Position of vector iDup
	iDup[pos] &= ~((1 << num % 32 ));
}

// Sets a bit from the iDup table

void CPing::SET(TUint16 num)
{
	TInt num256= num % 256;		//num modulus 256
	TInt pos=num256 >> 5;		//Position of vector iDup
	iDup[pos] |= (1 << num % 32 );
}

// Tests if the bit is set or not

TBool CPing::TEST(TUint16 num)
{
	TUint num256= num % 256;		//num modulus 256
	TInt pos=num256 >> 5;		//Position of vector iDup
	return ((iDup[pos] & (1 << num % 32)) == (TUint32)(1 << num % 32));
}

//Generates a random number using iSeed
TUint16 CPing::RandomNumber()
{
	if (iSeed==0)	//Initialize seed randomly with time
	{
		TTime time;
        time.HomeTime();
		iSeed=time.Int64();
    }
    return ((TUint16)Math::Rand(iSeed));	//Just take the lowest 16 bits)
}

// Composes the whole ICMP packet except time stamp and checksum

void CPing::ComposeFirstICMPPacket()
{
	ThdrICMP *hdr;
	TUint firstPos = 0;
	
	hdr = (ThdrICMP *)&iPacket[0];	// Can use this one for IPv6 because is the same format

	if (iType == IPv4)
		hdr->NetSetType(KICMPTypeEchoRequest);
	else
		hdr->NetSetType(KInet6ICMP_EchoRequest);

	hdr->NetSetCode(KICMPCode);

	iId = RandomNumber();
	((ThdrICMP_Echo *)hdr)->NetSetId(iId);	
	((ThdrICMP_Echo *)hdr)->NetSetSeq(0);
	CLR(0);	// Clears the bit in the received/dup buffer
	
	// Jump over possible timestamp

	if (iPacketDataSize >= TIMESTAMP_SIZE)
	{
		firstPos = TIMESTAMP_SIZE;
	}

	// The rest is filled with a pattern
	// The last part of the packet may only be a part of the pattern

	TInt i;
	TInt j;

	//First transform the pattern from text to Hexadecimal
	TLex lex;
	//TUint8 hex_num;
	TBuf<2> hex_digit;
	TBuf<2 * MAX_PATTERN_LENGTH> text_pattern;
	TUint8 hex_pattern[MAX_PATTERN_LENGTH];
	if (iPattern.Length() % 2 == 0)
		text_pattern.Copy(iPattern);
	else	//odd size so the pattern is doubled and we have a even size
	{
		text_pattern.Copy(iPattern);
		text_pattern.Append(iPattern);
	}

	for (i = 0; i < text_pattern.Length(); i += 2)
	{
		hex_digit.Copy(text_pattern.Ptr() + i, 2);	//Copy 2 text digits 1 byte
		lex.Assign(hex_digit);
		lex.Val(hex_pattern[i/2], EHex);		//Can't fail because only 2 digits
		//hex_pattern.Append((TUint8 *)&hex_num, sizeof(TUint8));	//Append the hex digit (byte)
	}


	
	for (j = 0, i = firstPos; (TUint)i < iPacketDataSize; i++, j++)
	{
		iPacket[ICMP_ECHO_HEADER_SIZE + i] = !text_pattern.Length()
			? (TUint8)(i % 256)
			: hex_pattern[j % (text_pattern.Length()/2)];	//hex_pattern is half the size of text pattern
	}
//	TBuf8<150> prova;
//	prova.Copy(iPacket);
	StampPacket();
}

// Only composes timestamp and sequence number and calculates checksum

void CPing::ComposeICMPPacket()
{
	ThdrICMP *hdr = (ThdrICMP *)&iPacket[0];
	TUint16 seq = (TUint16)(((ThdrICMP_Echo *)hdr)->NetGetSeq() + 1);

	((ThdrICMP_Echo *)hdr)->NetSetSeq(seq);	// Next seq. Number
	CLR(seq);	// Clears the bit in the received/dup buffer

	StampPacket();
}

void CPing::StampPacket()
{
	ThdrICMP *hdr = (ThdrICMP *)&iPacket[0];

	if (iPacketDataSize >= TIMESTAMP_SIZE)
	{
		TTime time;
		time.UniversalTime();
		*(TInt64*)&iPacket[8] = time.Int64();	// Converts the time to a Int64 value
	}

	hdr->NetSetChecksum(0);
	hdr->NetSetChecksum(in_chksum((TUint16 *)&iPacket[0], iPacketDataSize + ICMP_ECHO_HEADER_SIZE));
}

//Puts the next seq number in the packet (iPacket)
void CPing::NextSeq()
{
	ThdrICMP_Echo *hdr;

	hdr=(ThdrICMP_Echo *)&iPacket[8];
	hdr->SetSeq((TUint16)((hdr->NetGetSeq()) + 1));
}

//TPtrC CPing::PacketTypev6(TInet6HeaderICMP_Echo *aHdr)
void CPing::PacketTypev6(TDes& buf,ThdrICMP *aHdr)
{
	//TBuf<40> buf;
	TInet6HeaderICMP *hdr = (TInet6HeaderICMP *)aHdr;
	//TInet6HeaderICMP_Echo *echoHdr;

	TInt8 code=hdr->Code();
	switch(hdr->Type())
	{
		//Errors 0-127
		case KInet6ICMP_Unreachable: 
			//buf.Format(_L("type= "));
			buf.Append(_L("Dest. Unreachable: "));
			//buf.Format(_L("type=%d "),type);
			switch (code)
			{
			case KInet6ICMP_NoRoute:
				//buf.Append(_L("code= "));
				buf.Append(_L("No Route "));
				break;
			case KInet6ICMP_AdminProhibition:
				//buf.Append(_L("code= "));
				buf.Append(_L("Admin. Prohibition "));
				break;
			case KInet6ICMP_NotNeighbour:
				//buf.Append(_L("code= "));
				buf.Append(_L("Not a Neighbour "));
				break;
			case KInet6ICMP_AddrUnreach:
				//buf.Append(_L("code= "));
				buf.Append(_L("Addr. Unreachable "));
				break;
			case KInet6ICMP_PortUnreach:
				//buf.Append(_L("code= "));
				buf.Append(_L("Port Unreachable "));
				break;
			default: buf.AppendFormat(_L("code=%d "),code);
			}
			break;
		case KInet6ICMP_PacketTooBig:
			//buf.Format(_L("type= "));
			buf.Append(_L("Pack. Too big "));
			break;
		case KInet6ICMP_TimeExceeded:
			//buf.Format(_L("type= "));
			buf.Append(_L("Time exceeded: "));
			switch (code)
			{
			case KInet6ICMP_HopLimitExceeded:
				//buf.Append(_L("code= "));
				buf.Append(_L("Hop Limit "));
				break;
			case KInet6ICMP_FragReassExceeded:
				//buf.Append(_L("code= "));
				buf.Append(_L("Frag. Reassembly "));
				break;
			default: buf.AppendFormat(_L("code=%d "),code);
			}
			break;
		case KInet6ICMP_ParameterProblem:
			//buf.Format(_L("type= "));
			buf.Append(_L("Parameter problem: "));
			switch (code)
			{
			case KInet6ICMP_ErrHdrField:
				//buf.Append(_L("code= "));
				buf.Append(_L("Bad header filed"));
				break;
			case KInet6ICMP_NextHdrUnknown:
				//buf.Append(_L("code= "));
				buf.Append(_L("Unknown Next Header "));
				break;
			case KInet6ICMP_OptionUnkown:
				//buf.Append(_L("code= "));
				buf.Append(_L("Unknown Option"));
				break;
			default: buf.AppendFormat(_L("code=%d "),code);
			}
			break;

		//Information 128-255
		case KInet6ICMP_EchoRequest:
			//echoHdr=(TInet6HeaderICMP_Echo *)hdr;
			buf.Append(_L("Echo Request "));
			//buf.AppendFormat(_L("id= %d "),echoHdr->Identifier());
			//buf.AppendFormat(_L("Seq= %d "),echoHdr->Sequence());
			break;
		case KInet6ICMP_EchoReply:
			//echoHdr=(TInet6HeaderICMP_Echo *)hdr;
			buf.Append(_L("Echo Reply "));
			//buf.AppendFormat(_L("id= %d "),echoHdr->Identifier());
			//buf.AppendFormat(_L("Seq= %d "),echoHdr->Sequence());
			break;
	
		case KInet6ICMP_Redirect:
 			buf.Append(_L("Redirect "));
			break;

		case KICMPTypeRouterAdvert: 
		case KInet6ICMP_RouterAdv:
			buf.Append(_L("Router advertisement "));
			break;

		case KICMPTypeRouterSolicit: 
		case KInet6ICMP_RouterSol:
			buf.Append(_L("Router solicitation "));
			break;

		case KInet6ICMP_GroupQuery:
			buf.Append(_L("KInet6ICMP_GroupQuery "));
			break;

		case KInet6ICMP_GroupReport:
			buf.Append(_L("KInet6ICMP_GroupReport "));
			break;

		case KInet6ICMP_GroupDone:
			buf.Append(_L("KInet6ICMP_GroupDone "));
			break;

		case KInet6ICMP_NeighborSol:
			buf.Append(_L("Neighbor Solicitation "));
			break;

		case KInet6ICMP_NeighborAdv:
			buf.Append(_L("Neighbor Advertisement "));
			break;

		default: //buf.Format(_L("Unknown ICMP Type"));
			buf.Format(_L("Unknown ICMP Type"));
			//buf.Format(_L("type=%d "),hdr->Type());
	}
	//buf.Append(_L("\n"));

}

TPtrC CPing::PacketType(ThdrICMP *aHdr)
{
	switch(aHdr->NetGetType())
	{
		case KICMPTypeEchoReply: return _L("ICMP Echo reply");
		case KICMPTypeUnreachable: 
			switch (aHdr->NetGetCode())
			{
				case KICMPCodeUnreachNet: return _L("Network Unreachable");
				case KICMPCodeUnreachHost: return _L("Host Unreachable");
				case KICMPCodeUnreachProtocol: return _L("Protocol Unreachable");
				case KICMPCodeUnreachPort: return _L("Port Unreachable");
				case KICMPCodeUnreachNeedFrag: return _L("Message too long. Fragmentation needed");
				case KICMPCodeUnreachSrcRouteFail: return _L("Source Route Failed");
				case KICMPCodeUnreachNetUnknown: return _L("Destination Network Unknown");
				case KICMPCodeUnreachHostUnknown: return _L("Destination Host Unknown");
				case KICMPCodeUnreachSrcHostIsolated: return _L("Source host isolated");
				case KICMPCodeUnreachNetProhibited: return _L("Destination Network Administatively prohibited");
				case KICMPCodeUnreachHostProhibited: return _L("Destination Host Administatively prohibited");
				case KICMPCodeUnreachNetTOS: return _L("Network Unreachable for TOS");
				case KICMPCodeUnreachHostTOS: return _L("Host Unreachable for TOS");
				case KICMPCodeUnreachProhibited: return _L("Communication Administatively prohibited");
				case KICMPCodeUnreachPrecVolation: return _L("Host Precedence violation");
				case KICMPCodeUnreachPrecCutoff: return _L("Precedence cutoff in effect");
				default: return _L("Unknown code for Destination Unreachable");
			}
		case KICMPTypeSourceQuench: return _L("Source Quench");
		case KICMPTypeRedirect: 
			switch (aHdr->NetGetCode())
			{
				case KICMPCodeRedirectNet: return _L("Redirect for network");
				case KICMPCodeRedirectHost: return _L("Redirect for Host");
				case KICMPCodeRedirectNetTOS: return _L("Redirect for TOS and Network");
				case KICMPCodeRedirectHostTOS: return _L("Redirect for TOS and Host");
				default: return _L("Unknown code for ICMP Redirect");
			}
		case KICMPTypeEchoRequest: return _L("Echo Request");
		case KICMPTypeRouterAdvert: return _L("Router advertisement");
		case KICMPTypeRouterSolicit: return _L("Router solicitation");
		case KICMPTypeTimeExceeded: 
			switch (aHdr->NetGetCode())
			{
				case KICMPCodeExceedInTransit: return _L("TTL 0 during Transit");
				case KICMPCodeExceedInReasm: return _L("TTL 0 during Reassembly");
				default: return _L("Unknown Code for Time exceeded type");
			}
		case KICMPTypeBadParameter: return _L("Parameter Problem");
		case KICMPTypeTimeRequest: return _L("Timestamp Request");
		case KICMPTypeTimeReply: return _L("Timestamp Reply");
		case KICMPTypeInfoRequest: return _L("Information Request");
		case KICMPTypeInfoReply: return _L("Information Reply");
		case KICMPTypeMaskRequest: return _L("Adress Mask Request");
		case KICMPTypeMaskReply: return _L("Adress Mask Reply");
		default: return _L("Unknown ICMP Type");
	}
}

// Shows ICMP data in the Packet and calculates time related info

void CPing::PrintICMPData(const TDesC8& data)
{
	TBuf<300> aux;
	ThdrICMP *hdr;	// Use only this one because the packet format for ICMP_Echo_reply is 
					// identical in both ICMPv4 and ICMPv6

	hdr=(ThdrICMP*)&data[0];	// ICMP packet format

	TUint type;
	if (iType==IPv4)
		type=KICMPTypeEchoReply;
	else	// ICMPv6
		type=KInet6ICMP_EchoReply;

	if (hdr->NetGetType()!=type)
	{
		// We want to list other packets than ICMP Echo Reply
		if (iVerbose)
		{
			if (iType==IPv4)
				aux.Format(PacketType(hdr)); // Return a description of the packet Type and Code
			else
			{
				TBuf<40> auxBuf;
				PacketTypev6(auxBuf,hdr);
				aux.Format(auxBuf);	// Return a description of the packet Type and Code
			}
			aux.Append(_L("\n"));
			WriteLineIfNotQuiet(aux);
		}	// else we ignore them
		return;
	}
	
	// Checks if it's a the packet have a correct Id
	// Useful if two instances if Ping running (not possible in this version)

	if (((ThdrICMP_Echo *)hdr)->NetGetId()!=iId)
	{
		aux.Append(_L("Packet with wrong id received\n"));
		WriteLineIfNotQuiet(aux);
		return;
	}

	// Correct packet type and code

	aux.AppendFormat(_L("Seq: %u"),((ThdrICMP_Echo *)hdr)->NetGetSeq());

	// Checks if chksum is correct must be 0 (because includes the checksum field)
	// else there's something wrong

	if (iType==IPv4)
	{
		if (in_chksum((TUint16 *)&data[0], iPacketDataSize + ICMP_ECHO_HEADER_SIZE)!=0)
		{
			aux.Append(_L(" Checksum Error\n"));
			WriteLineIfNotQuiet(aux);
			iChksumErrors++;
			return;
		}
	}
	// ICMPv6 checks checksum internally

	// Timestamp calculation

	if (iPacketDataSize >= TIMESTAMP_SIZE)
	{
		TTime now;
		TTime time(*(TInt64*)&data[8]);
		now.UniversalTime();

		TTimeIntervalMicroSeconds interval;
		interval = now.MicroSecondsFrom(time);
#ifdef I64LOW
		TUint num = I64LOW(interval.Int64()) / 1000;
#else
		TUint num = interval.Int64().GetTInt() / 1000;
#endif

		if (num > iMaxTime)
			iMaxTime = num;

		if (num < iMinTime)
			iMinTime = num;

		iTimeSum += num;

		aux.AppendFormat(_L("\tTime: %d ms"),num);
	}
	
	iRecvPackets++;

	// Test if duplicated

	if (TEST(((ThdrICMP_Echo *)hdr)->NetGetSeq()))
	{
		aux.Append(_L("\tDUPLICATED"));
		iRecvPackets--;	// because duplicated
		iDupPackets++;  // to show it in statistics
	}
	else
	{
		SET(((ThdrICMP_Echo *)hdr)->NetGetSeq());	// Marks the packet as received
	}

	aux.Append(_L("\n"));
	WriteLineIfNotQuiet(aux);
}

void CPing::Stop()
{
	iRunning=EFalse;
	CEikonEnv::Static()->BusyMsgCancel();
	/*
	delete iPacketData;
	iPacketData=NULL;
	delete iReceivedDataBuffer;
	iReceivedDataBuffer=NULL;
	*/
}

void CPing::BeginL()
{
	TInt err=0;

	if (IsRunning())	// There's another instance running
		return;
	else
		iRunning=ETrue;

	//INITIALIZATION
	iSentPackets=0;		//ICMP Echo Request Packets sent
	iRecvPackets=0;		//ICMP Echo Reply Packets received
	iChksumErrors=0;	//Packets with errors when checking checksum
	iSockErrors=0;		//Errors when writing/reading to/from the sockets
	iDupPackets=0;		//Duplicated packets

	iMaxTime=0;			//Time-related vars
	iMinTime=KMaxTUint32;
	iTimeSum=0;

	if (iPacketDataSize > MAX_ICMP_PACKETSIZE)  //Is prevented in .rss file
		iPacketDataSize = MAX_ICMP_PACKETSIZE;

	
	delete iPacketData;
	iPacketData= HBufC8::NewL(iPacketDataSize + ICMP_HDRLEN); 
						//Allocates space for the ICMP packet
	TPtr8 aux(iPacketData->Des());	//weird but necessary. Cannot use Des() directly in iPacket
	iPacket.Set(aux);
	//iPacket.SetMax();	//because it'll be written directly using [] in Compose...() and it would crash.
	//HUOM!!! Cannot be SetMax because sometimes the reserved size is slightly bigger. Because of block size?
	iPacket.SetLength(iPacketDataSize + ICMP_HDRLEN);	//because it'll be written directly using [] in Compose...() and it would crash.


	delete iReceivedDataBuffer;
	iReceivedDataBuffer= HBufC8::NewL(iPacketDataSize + ICMP_ECHO_HEADER_SIZE);	//Maximum size of a return packet
	TPtr8 auxPtr(iReceivedDataBuffer->Des());	//must be used here because the buffer changes
	iReceivedData.Set(auxPtr);			//we use an aux var because can't use Des() directly Why??
	
	iConsole->WriteHostL(iHostname);
	iConsole->WriteLine(_L("Connecting...\n"));	
	iConsole->UpdateStatisticsL();
	
	
#ifdef IAPSETTING
	//CStoreableOverrideSettings *settings = NULL;
	//CCommDbOverrideSettings::TParamList ParamList = CCommDbOverrideSettings::TParamList::EParamListPartial;
	//CCommDbOverrideSettings::TParamList ParamList = CCommDbOverrideSettings::TParamList::EParamListPartial;
	TCommDbDatabaseType Type = EDatabaseTypeIAP;
	CStoreableOverrideSettings *settings = CStoreableOverrideSettings::NewL(CCommDbOverrideSettings::EParamListPartial,Type);
	
	CleanupStack::PushL(settings);
	
	CCommsDbConnectionPrefTableView::TCommDbIapConnectionPref conPref;
	conPref.iRanking = 1;
	conPref.iDirection = ECommDbConnectionDirectionOutgoing;
	CCommsDbConnectionPrefTableView::TCommDbIapBearer bearer;
	bearer.iIapId = iIAP;
	conPref.iBearer = bearer;

	err = settings->SetConnectionPreferenceOverride(conPref);
	User::LeaveIfError(err);
	User::LeaveIfError(iGenericAgent.Open());
    iGenericAgent.StartOutgoing(*settings,iStatus);
	
	err = iGenericAgent.DisableTimers();
    if (err != KErrAlreadyExists)
        User::LeaveIfError(err);
	CleanupStack::PopAndDestroy();	
	User::WaitForAnyRequest();
#endif


	//connecting the Socket Server
	err=iSockServ.Connect();	//KESockDefaultMessageSlots
	if (err!=KErrNone)
	{
		ErrorL(_L("Socket Server Error (Connect)"),err);
		Stop();
		return;
	}

	err=iHostResolv.Open(iSockServ, KAfInet, KProtocolInetIcmp);	// Address Resolver 
	
	if (err!=KErrNone)
	{
		ErrorL(_L("Resolver Error (Open)"),err);

		Stop();
		return;
	}

	//Report(_L("Resolver Open"));

	iHostResolv.GetByName(iHostname,iEntry,iStatus);	//synchronous
	//Report(_L("Resolver GetByName"));
	iStage=0;
	CEikonEnv::Static()->BusyMsgL(R_RESOLVING_NAME);

	
//-------------------------------------------------
	//DO NOT REMOVE the next commented code!!!!!!!!!!!!!!!!!!
	/*
	//Never will be used because the socket is opened as ICMP not IP so is not usable 
	//unless some changes are made to the socket and packet format.
	
	
	if (iHopLimit!=0)	// 0 means value not set
	{	//Only setable for multicast adresses
		err=iSocket.SetOpt(KSoIpTTL,KSOLSocket,iHopLimit);	//Set TTL (max. hops)
		
		
		if (err==KErrNotSupported)
			iConsole->WriteLine(_L("TTL can only be set with multicast adress (Not used)\n"));
		else
			if (err<0)
			{
				ErrorL(_L("Socket Error (SetOpt)"),err);
				return;
			}
		
	}
--------------------------------------------------------	*/
	IssueRequest();
/*	iStage=0;
	TRequestStatus *xst=&iStatus;
	User::RequestComplete(xst,KErrNone);*/
}

void CPing::CreateSocketAOL()
{
	
	iPingSender=new(ELeave) CPingSender;
    iPingSender->ConstructL(this);	//2nd phase
	iPingSender->FirstRunL();	//Starts packet sending
}

//Issues next RunL execution
void CPing::IssueRequest()
{
	SetActive();	//Sets the object as Active.
					//RunL will be executed when iStatus!=KRequestPending (set by CPingSender)
}


void CPing::RunL()
{
	TInt err=KErrNone;
	TBuf<39> textIPaddress;	//text address to be displayed
	TBuf<356> aux;
	TInetAddr anyaddr;
	TRequestStatus xStatus;

	switch (iStage)
	{
	case 0:
		//Report(_L("Resolver GetByName end"));
		if (iStatus==KErrNotFound)
		{//The Nameserver couldn't find the Host. 
			TBuf<100> warn(iHostname);
			warn.Append(_L(" not found!\n"));
			iConsole->WriteLine(warn);
			iHostResolv.Close();
			Stop();
			return;
		}	
		if (iStatus!=KErrNone)
		{
			ErrorL(_L("Resolver Error (GetByName)"),iStatus.Int());
			iHostResolv.Close();
			//iSockServ.Close();
			Stop();
			return;
		}

		iHostResolv.Close();
		iHostAddr = TInetAddr::Cast(iEntry().iAddr);	//host address

		// The type of PING (ICMPv4 or ICMPv6) depens purely on the destination address.
		// ...for IPv4 (or IPv4 mapped), use ICMPv4
		// ...for true IPv6, use ICMPv6
		iType = (iHostAddr.Family() == KAfInet6 && !iHostAddr.IsV4Mapped()) ? IPv6 : IPv4;

		iHostAddr.Output(textIPaddress);
		aux.Append(iEntry().iName);   // maybe the main name is not the entered
		aux.Append(_L(" is "));
		aux.Append(textIPaddress);

		aux.AppendFormat(_L("\nUsing %d data bytes"), iPacketDataSize);
		if (iPacketDataSize < TIMESTAMP_SIZE)
			aux.AppendFormat(_L(": timestamps disabled (min %d bytes)"), TIMESTAMP_SIZE);

		aux.Append(_L("\n\n"));
		iConsole->WriteLine(aux);

		if (iType==IPv4)
		{
			err=iSocket.Open(iSockServ,_L("icmp"));
		}
		else
		{
			err=iSocket.Open(iSockServ,_L("icmp6"));
		}

		if (err!=KErrNone)
		{
			ErrorL(_L("Socket Error (Open)"),err);
			Stop();
			return;
		}
		//iStage++;
		
		anyaddr.SetAddress(KInetAddrAny);	//Sniffs all packets
		anyaddr.SetPort(KProtocolInetIcmp);	//Sniffs all packets

		err=iSocket.Bind(anyaddr);
		if (err!=KErrNone)
		{	
			ErrorL(_L("Socket Error (Bind)"),err);
			Stop();
			return;
		}

		iSocket.Connect(iHostAddr,xStatus);	//must be wait here or panic esock14
		User::WaitForRequest(xStatus);
		if (xStatus.Int()!=KErrNone)
		{
			ErrorL(_L("Socket Error (Connect)"),xStatus.Int());
			Stop();
			return;
		}
	
		// Socket Options setting
		if (iDebug)
			err=iSocket.SetOpt(KSODebug,KSOLSocket,1);	//Enable debugging
		else
			err=iSocket.SetOpt(KSODebug,KSOLSocket,0);	//disable debugging (DEFAULT)
            
		if (err!=KErrNone)
		{
			ErrorL(_L("Socket Error (SetOpt)"),err);
			//iHostResolv.Close();
			iSocket.Close();
			//iSockServ.Close();
#ifdef IAPSETTING			
			iGenericAgent.Close();
#endif 			
			Stop();
			return;
		}
		CEikonEnv::Static()->BusyMsgCancel();	//Cancel the resolving name msg
		CreateSocketAOL();	//Creates the send A.O.
		iSocket.Read(iReceivedData,iStatus);
		iStage++;
		IssueRequest();	//Prepares to receive it in RunL()
		break;

	case 1:
		//Report(_L("Socket Read end"));
		if (iStatus==KErrNone)
		{
			PrintICMPData(iReceivedData);	//The previous packet
		}
		else
			ErrorL(_L("Read (Recv)"),iStatus.Int());

		iConsole->UpdateStatisticsL();
		
		iSocket.Read(iReceivedData,iStatus);	// NEXT Packet
		IssueRequest();	// Prepares to receive it in RunL()
		// No more stages!
		break;

	default:
		CEikonEnv::Static()->InfoMsg(_L("Bad Stage!!!"));
		EndPingL();
	}

}

void CPing::DoCancel()
{
	if (iStage==0)
	{
		iHostResolv.Cancel();
		iHostResolv.Close();
	}
	iStatus=KErrNone;
}

void CPing::CloseAll()
{
	//Closes all the resources used in the begin function
	//If any not opened it crashes!!!!
	iHostResolv.Close();
	//iSocket.CancelAll();	//Cancel all outstanding requests 
	iSocket.Close();	
	iSockServ.Close();
#ifdef IAPSETTING
	iGenericAgent.Close();
#endif
}

// Stops Ping

void CPing::EndPingL()
{	
	CEikonEnv::Static()->BusyMsgCancel();	// Cancel the resolving name msg in case it wasn't

	Statistics();
	iConsole->UpdateStatisticsL();
	
	if (iPingSender!=0)	// Not needed if control dimmed because can't launch 2 pings
	{
		delete iPingSender;
		iPingSender=0;
	}

	if (IsActive())	// To stop the socket from waiting
		Cancel();

	CloseAll();
	iRunning = EFalse;
}

// Just checks if sending packets from a previous ping

TBool CPing::IsRunning()
{
	return iRunning;
}

// Sends packets with an active Timer
// Created by CPing
// Not intended to run alone

CPingSender::CPingSender():CTimer(EPriorityStandard)//,iSentData(0,0)
{
	CActiveScheduler::Add(this);	//Adds itself to the scheduler only the first time
}

CPingSender::~CPingSender()
{
	if (IsActive())
		Cancel();

	delete iSender;
}

void CPingSender::ConstructL(CPing *aPingModel)
{
	//Base class 2nd phase constructor
	CTimer::ConstructL();

	iPingModel=aPingModel;	// Poiter to the model which contains data
	//iSentDataBuffer= HBufC8::NewL(iPingModel->iPacketDataSize + ICMP_HDRLEN);
							//Allocates the maximum space needed the size chosen + ICMP Header
	iSender=new(ELeave) CPingSingleSender(iPingModel);
}


//Issues next RunL execution
void CPingSender::IssueRequest()
{
	After(iPingModel->iSecWait*SECOND);	//Also sets the object as Active
}

//Issues last RunL execution
void CPingSender::IssueLastRequest()
{
	After(iPingModel->iLastSecWait*SECOND);	//Also sets the object as Active
}


void CPingSender::FirstRunL()
{	
	
	SendFirstPacketL();
	
	if ((iPingModel->iSentPackets==iPingModel->iTotalPackets) && (iPingModel->iPackLimit))
		IssueLastRequest();	//Last RunL	have a special waiting time. to receive the last reply
	else
		IssueRequest();	//First RunL
}



// will send all the packets. One packet each Time
void CPingSender::RunL()
{
	if ((iPingModel->iSentPackets>=iPingModel->iTotalPackets) && (iPingModel->iPackLimit))
	{
		iPingModel->EndPingL();
		return;
	}
	else	//There are packets to send or number unlimited
	{		
		SendPacket();

		if ((iPingModel->iSentPackets>=iPingModel->iTotalPackets) && (iPingModel->iPackLimit))
			IssueLastRequest();	//Last RunL	have a special waiting time. to receive the last reply
		else
			IssueRequest();	//Next RunL
	}

}


//Creates a AO that sends a packets waits for it to be send and dies
void CPingSender::SendFirstPacketL()
{
	iSender->FirstRunLD();	//Starts packet sending. Destroys itself
}


//Creates a AO that sends a packets waits for it to be send and dies
void CPingSender::SendPacket()
{
	iSender->NextPacket();
}

//Cancel Packet Sending
void CPingSender::DoCancel()
{
	CTimer::DoCancel();

}




// Used by CPingSender
// Sends packets. Cannot be done directly by CPingSender because there are conflicts with
//					diferent TRequestStatus.

CPingSingleSender::CPingSingleSender(CPing *aPingModel):CActive(EPriorityStandard)
{
	iPingModel=aPingModel;	// Pointer to the model which contains data
	CActiveScheduler::Add(this);	//Adds itself to the scheduler only the first time
}

CPingSingleSender::~CPingSingleSender()
{
	if (IsActive())
		Cancel();
}


// Issues next RunL execution

void CPingSingleSender::IssueRequest()
{
	SetActive();	// Sets the object as Active.
}

void CPingSingleSender::FirstRunLD()
{	
	iPingModel->ComposeFirstICMPPacket();
	iPingModel->iSocket.Write(iPingModel->iPacket, iStatus); // iStatus used by CTimer
	iPingModel->iSentPackets++;
	iUnsent=0;
	IssueRequest();
}

void CPingSingleSender::NextPacket()
{	
	if (IsActive())		// Still a packet being sent
		iUnsent++;		// Cannot sent here because iSatus would be overwritten
	else
	{
		iPingModel->ComposeICMPPacket();
		iPingModel->iSocket.Write(iPingModel->iPacket, iStatus); // No other request waiting
		iPingModel->iSentPackets++;
		IssueRequest();
	}
}

// will send all the packets. One packet each Time

void CPingSingleSender::RunL()
{
//	TBuf<50> aux;
	
//	if (iStatus==KErrNone)
//		iPingModel->iSentPackets++;
	//else if (iStatus!=KRequestPending)
	if (iStatus!=KErrNone)
	{
		iPingModel->iSentPackets--;	//Packet sending failed
		iPingModel->ErrorL(_L("Write"),iStatus.Int());
	}

	//aux.Format(_L("Write end (st=%d, sent=%d)"),iStatus,iPingModel->iSentPackets);
	//iPingModel->WriteLineIfNotQuiet(aux);
	iPingModel->iConsole->UpdateStatisticsL();
	//Ignores the timer request because there are packets that should already be sent
	if (iUnsent)
	{
		iPingModel->ComposeICMPPacket();
		iPingModel->iSocket.Write(iPingModel->iPacket, iStatus); // iStatus used by CTimer
		iPingModel->iSentPackets++;
		iUnsent--;
		IssueRequest();
	}
}

// Cancel Packet Sending

void CPingSingleSender::DoCancel()
{
	iPingModel->iSocket.CancelWrite();
	iStatus=KErrNone;
}
