// Copyright (c) 2008-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:
// cqueryhandler.cpp
// 
//
/**
@file
@internalTechnology
*/

//User include 
#include "cqueryhandler.h"
//System include
#include <mdns/cdnsquestion.h>
#include <mdns/ccacheentry.h>
#include <mdns/tdnsheader.h>
__FLOG_STMT(_LIT8(KComponent,"MDNSServer");)
/*
 * Two phase constructor
 * @param aMessageHandler reference to the message handler.
 */
CQueryHandler* CQueryHandler::NewL(CMessageHandler& aMessageHandler)
	{
	CQueryHandler* self = new(ELeave)CQueryHandler(aMessageHandler);	
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop();
	return self;
	}
	
/*
 * Destructor
 */
CQueryHandler::~CQueryHandler()
	{
	__FLOG(_L8("CQueryHandler::~CQueryHandler - Entry Exit"));
	__FLOG_CLOSE;	
	}

/*
 * Handles any query from the network 
 * @param aMessage message which contains the query.
 * @param aSockAddr address form which query id recieved.
 */
void CQueryHandler::HandleIncomingPacketL(CDnsMessage& aMessage ,const TSockAddr& aAddr)
	{
	__FLOG(_L8("CQueryHandler::HandleIncomingPacketL - Entry"));
	if(aMessage.Header().IsQuery())
		{
		CDnsMessage* ret= CDnsMessage::NewL(aMessage.Header().Id(),EFalse);
		CleanupStack::PushL(ret);
		TInt qCount = aMessage.Header().QueryCount();
		for(TInt i =0; i< qCount;i++)
			{
			const CDnsQuestion* query = aMessage.Queries()[i];
			TBufC8<100> domainName(query->Name());
			RPointerArray<CCacheEntry> cacheArray;
			TRAPD(error,MessageHandler().DnsCache().FindServiceL(cacheArray,domainName,(TDnsType)query->Type()));
			for(TInt i =0 ; i<cacheArray.Count()  ;i++)
				{
				if(cacheArray[i]->IsAuthoritative())
					{
					TBool suppress = EFalse;
					switch(query->Type())
							{
							case  EDnsType_A:
								{
								suppress = SuppressDuplicateAnswerL(aMessage.Answers(),*(cacheArray[i]->AddressRecord()));
								if(suppress == EFalse)
									 {
									 ret->AppendAnswerL(cacheArray[i]->AddressRecord()->CloneL());
									 	
									 }
								break;	
								}
							case  EDnsType_PTR:
								{
								suppress = SuppressDuplicateAnswerL(aMessage.Answers(),*(cacheArray[i]->PtrRecord()));
								if(suppress == EFalse)
									 {
									 ret->AppendAnswerL(cacheArray[i]->PtrRecord()->CloneL());
									 }
								break;	
								}
							case  EDnsType_SRV:
								{
								suppress = SuppressDuplicateAnswerL(aMessage.Answers(),*(cacheArray[i]->ServiceRecord()));
								if(suppress == EFalse)
									 {
									 ret->AppendAnswerL(cacheArray[i]->ServiceRecord()->CloneL());
									 }
								break;	
								}
							case  EDnsType_TXT:
								{
								suppress = SuppressDuplicateAnswerL(aMessage.Answers(),*(cacheArray[i]->TxtRecord()));
								if(suppress == EFalse)
									 {
									 ret->AppendAnswerL(cacheArray[i]->TxtRecord()->CloneL());
									 }
								break;
								}
							case  EDnsType_AAAA:
								{
								//HandleAAAAQuestionL();
								break;	
								}
							case  EDnsQType_Any:
								{
								HandleAnyQuestionL(ret,aMessage.Answers(),*cacheArray[i]);
								break;	
								}	
							default:
								User::Invariant();				
								
							}
					 						
					}
				}
            cacheArray.ResetAndDestroy();
            cacheArray.Close();
			}
		if(ret->Header().AnswerCount() == 0 && !IsLastTruncated)
			{
			CleanupStack::PopAndDestroy(ret);
			}
		else
			{
			TDnsHeader& header = const_cast <TDnsHeader&> (ret->Header());
			header.SetAuthoritative(ETrue);
			if(IsLastTruncated)
				{
				COutStandingQuery* query = NULL;
				TInt index = 0;
				for( index =0 ; index< iOutStandingQueryArray.Count();index++)
					{
					 query = iOutStandingQueryArray[index];
					if(query->TransactionId() == header.Id() && query->SocketAddress() == aAddr)
						{
						break;	
						}
					}
				if(index < iOutStandingQueryArray.Count())
					{
					CDnsMessage* message = &(query->DnsMessage());
					TInt i = 0;
					while(i++ <message->Header().AnswerCount())
						{
						ret->AppendAnswerL(message->Answers()[i]);	
						}
					}
				
				}
				
			if(aMessage.Header().IsTruncated())
				{
				IsLastTruncated = ETrue;
				TTime time;
				time.HomeTime();
				time + TTimeIntervalMicroSeconds(500); 
				COutStandingQuery* outstandingQuery = COutStandingQuery::NewL(ret,ret->Header().Id(),time,aAddr);
				iOutStandingQueryArray.Append(outstandingQuery);
				if(!IsActive())
					{
					SetActive();
					At(time);	
					}
				
				}
			else
				{
				const TUint32 KMDnsAddr = INET_ADDR(224, 0, 0, 251);
				TInetAddr addr(KMDnsAddr, KMdnsPort);
				CSendMessageData* data = CSendMessageData::NewL(ret,ETrue,addr,*this);
				MessageHandler().MessageQueue().QueueDnsMessageL(*data);	
				}
			CleanupStack::Pop();	
			}
		}
	else
		{
		User::Leave(KErrGeneral);	
		}
	__FLOG(_L8("CQueryHandler::HandleIncomingPacketL - Exit"));		
	}
/*
 * Constructor
 * by default clienthandle is 0
 */	
CQueryHandler::CQueryHandler(CMessageHandler& aMessageHandler):CBaseHandler(aMessageHandler)
	{
	
	iClientHandle =0 ;	
	}

/*
 * Two phase constructor
 */
void CQueryHandler::ConstructL()
	{
	__FLOG_OPEN(KMDNSSubsystem, KComponent);
	__FLOG(_L8("CQueryHandler::ConstructL - Entry"));
	CBaseHandler::ConstructL();
	__FLOG(_L8("CQueryHandler::ConstructL - Exit"));
	}
	
/*
 * compares the answer section and the authoritative entries in the cache .
 * If there is a match returns ETrue. which mean remove the entry , as the 
 * client is not interested in this record.
 * @param aAnswers array of records recieved from the external client.
 * @param aResourceData data from the cache to be checked with the answer section.
 */
TBool CQueryHandler::SuppressDuplicateAnswerL(const RPointerArray<CDnsResourceData>& aAnswers,const CDnsResourceData& aResourceData)
	{
	__FLOG(_L8("CQueryHandler::SuppressDuplicateAnswerL - Entry"));
	TBool ret =EFalse;
	TInt answersCount = aAnswers.Count();
	for(TInt i=0 ;i< answersCount;i++)
		{
		if(0 == aAnswers[i]->Name().Compare(aResourceData.Name()) && aAnswers[i]->Type() == aResourceData.Type())
			{
			switch(aResourceData.Type())
				{
					case EDnsType_A:
						{
						const CRdTypeA& rdata = (CRdTypeA&)(aResourceData);
						const CRdTypeA* rEntry = static_cast<CRdTypeA*>(aAnswers[i]);
						if(rdata.Address() == rEntry->Address() && rdata.Ttl() > rEntry->Ttl())
							{
							ret = ETrue;
							}
						break;
						}
					case EDnsType_PTR:
						{
						const CRdTypePtr& rdata = (CRdTypePtr&)(aResourceData);
						const CRdTypePtr* rEntry = static_cast<CRdTypePtr*>(aAnswers[i]);
						if(0 ==rdata.Name().Compare(rEntry->Name()) && rdata.Ttl() > rEntry->Ttl())
							{
							ret = ETrue;
							}
						break;
						}
					case EDnsType_SRV:
						{
						const CRdTypeSrv& rdata = (CRdTypeSrv&)(aResourceData);
						const CRdTypeSrv* rEntry = static_cast<CRdTypeSrv*>(aAnswers[i]);
						if(0 == rdata.Name().Compare(rEntry->Name()) && rdata.Port() == rEntry->Port() && rdata.Ttl() > rEntry->Ttl())
							{
							ret = ETrue;
							}
						break;
						}
					case EDnsType_TXT:
						{
						const CRdTypeTxt& rdata = (CRdTypeTxt&)(aResourceData);
						const CRdTypeTxt* rEntry = static_cast<CRdTypeTxt*>(aAnswers[i]);
						if(rdata.Text().Count() == rEntry->Text().Count())
							{
							for(TInt i =0; i< rdata.Text().Count();i++)
								{
								if(0 != rdata.Text()[i].Compare(rEntry->Text()[i]))
									{
									break;	
									}
								}
							ret =ETrue;	
							}
						break;
						}
					default:
						{
						User::Invariant();	
						}
									
				}
			}
		}
	__FLOG(_L8("CQueryHandler::SuppressDuplicateAnswerL - Exit"));
	return ret;
	
	}
	
/*
 * Handle query from the network of type *Any
 * @param aPacket packet to be sent to the network in response to the query.
 * @param aAnswers an array of answers in the query packet.
 * @param aEntry authoritative entries in the cache.
 */
void CQueryHandler::HandleAnyQuestionL(CDnsMessage* aPacket,const RPointerArray<CDnsResourceData>& aAnswers,const CCacheEntry& aEntry)
	{
	__FLOG(_L8("CQueryHandler::HandleAnyQuestionL - Entry"));
	if(aEntry.AddressRecord() && !SuppressDuplicateAnswerL(aAnswers,*(aEntry.AddressRecord())))
		{
		aPacket->AppendAnswerL(aEntry.AddressRecord()->CloneL());	
		}
	if(aEntry.PtrRecord() && !SuppressDuplicateAnswerL(aAnswers,*(aEntry.PtrRecord())))
		{
		aPacket->AppendAnswerL(aEntry.PtrRecord()->CloneL());	
		}
	if(aEntry.ServiceRecord() && !SuppressDuplicateAnswerL(aAnswers,*(aEntry.ServiceRecord())))
		{
		aPacket->AppendAnswerL(aEntry.ServiceRecord()->CloneL());	
		}
	if(aEntry.TxtRecord() && !SuppressDuplicateAnswerL(aAnswers,*(aEntry.TxtRecord())))
		{
		aPacket->AppendAnswerL(aEntry.TxtRecord()->CloneL());	
		}
	__FLOG(_L8("CQueryHandler::HandleAnyQuestionL - Exit"));
	}

/*
 * Handles query from the application.
 * 1.Query for the ptr entries are sent to the network by default.
 * 2.Query for the srv or txt record will be sent to the network only if it is not present in the cache
 * @param aMessage contains the query sent by the client.
 * @param aClientHandle sessionid of the client.
 */
void CQueryHandler::ServiceClientQueryL(CDnsMessage* aMessage,TInt aClientHandle)
	{
	__FLOG(_L8("CQueryHandler::ServiceClientQueryL - Entry"));
	TInt queryCount = aMessage->Header().QueryCount();
	TBool isSendQueryToNw = EFalse;
	for(TInt i =0 ; i< queryCount ; i++)
		{
		switch(aMessage->Queries()[i]->Type())
			{
			case EDnsType_A:
				{
				RPointerArray<CCacheEntry> iAddressRecords;
				TInt err = KErrNone;
				TRAPD(error ,err = MessageHandler().DnsCache().FindServiceL(iAddressRecords, aMessage->Queries()[i]->Name(), EDnsType_A));
				if(error == KErrNone && err==KErrNotFound /*&& (NULL == iAddressRecords[0]->AddressRecord())*/)	
					{
					isSendQueryToNw = ETrue;	
					}
				else
					{
					CDnsQuestion* question = aMessage->Queries()[i];
					const_cast <RPointerArray<CDnsQuestion>&>(aMessage->Queries()).Remove(i);
					delete question;	
					}
				iAddressRecords.ResetAndDestroy();
				iAddressRecords.Close();
				break;	
				}
		
			case EDnsType_PTR:
			case EDnsQType_Any:
				{
				RPointerArray<CCacheEntry> ptrRecords;
				TRAPD(error,MessageHandler().DnsCache().FindServiceL(ptrRecords,aMessage->Queries()[i]->Name(),EDnsType_PTR));
				for(TInt index =0 ; (error == KErrNone) &&  index < ptrRecords.Count(); index++)
					{
					aMessage->AppendAnswerL(ptrRecords[index]->PtrRecord()->CloneL());	
					}
				isSendQueryToNw = ETrue;	
				ptrRecords.ResetAndDestroy();
				ptrRecords.Close();
				break;
				}
			
			case EDnsType_SRV:
			case EDnsType_TXT:
				{
				RPointerArray<CCacheEntry> records;
				TRAPD(error,MessageHandler().DnsCache().FindServiceL(records,aMessage->Queries()[i]->Name(),(TDnsType)aMessage->Queries()[i]->Type()));
				if(error == KErrNone && 0 != records.Count())
					{
					CDnsQuestion* question = aMessage->Queries()[i];
					const_cast <RPointerArray<CDnsQuestion>&>(aMessage->Queries()).Remove(i);
					delete question;
					}
				else
					{
					isSendQueryToNw = ETrue;
					}	
				records.ResetAndDestroy();
				records.Close();
				break;
				}	
				
					
			}
		}
		
	if(isSendQueryToNw)	
		{
		SendQueryL(aMessage,*this);
		iClientHandle = aClientHandle;
		//On succesfull sending the packet query handler will be notified there query will be 
		// added to outstanding queue
		}
	else
		{
		delete aMessage;//as the question is not to be send .
		TTime time;
		time.HomeTime();
		ScheduleOutStandingQueryL(aClientHandle,ETrue,time);	
		}
	__FLOG(_L8("CQueryHandler::ServiceClientQueryL - Exit"));		
	}
/*
 * Any queries to be sent to the network goes through this
 * @param aMessage contains the query to be sent.
 * @param aObserver callback which is interested in getting notified when 
 * a packet is successfully sent to the network. 
 */
void CQueryHandler::SendQueryL(CDnsMessage* aMessage, MMessageHandler& aObserver)
	{
	__FLOG(_L8("CQueryHandler::SendQueryL - Entry"));
	//
	//TODO in case in case the size exceeds max size
	//
	TInetAddr addr(KMDnsAddr, KMdnsPort);
	TBuf<255> buf;
	addr.Output(buf);
	CSendMessageData* data = CSendMessageData::NewL(aMessage,EFalse,addr,aObserver);
	CleanupStack::PushL(data);
	MessageHandler().MessageQueue().QueueDnsMessageL(*data);
	CleanupStack::Pop();//data
	__FLOG(_L8("CQueryHandler::SendQueryL - Exit"));
	}	
	    

/*
 * Creates an outstanding query object from the client query and adds it to the outstanding query arry.
 * 1. For queries sent to the network ,waits for 120ms before sending the response back .
 * 2. For queries not sent to network ,reponse will be sent back immediately. 
 */
void CQueryHandler::ScheduleOutStandingQueryL(TInt aClientHandle , TBool aTriggerNow, TTime aInTime)
	{
	__FLOG(_L8("CQueryHandler::ScheduleOutStandingQueryL - Entry"));
	COutStandingQuery* outstandingQuery = COutStandingQuery::NewL(NULL,0,aInTime,NULL);
	outstandingQuery->SetClientHandle(aClientHandle);
	if(aTriggerNow)
		{
		if(IsActive())
			{
			Cancel();
			}
		iOutStandingQueryArray.Insert(outstandingQuery,0);
		TRequestStatus* status = &iStatus;
		SetActive();
		User::RequestComplete(status,KErrNone);	
		}
	else
		{
		TInt pos = 0;
		for(pos = 0 ; pos< iOutStandingQueryArray.Count(); pos++ )
			{
			if(outstandingQuery->GetAddTime().Int64() < iOutStandingQueryArray[pos]->GetAddTime().Int64() )
				{
				break;	
				}
			}
		iOutStandingQueryArray.Insert(outstandingQuery, pos);
		if(!IsActive())
			{
			TTime nextReview = iOutStandingQueryArray[0]->GetAddTime();
			At(nextReview);	
			}	
		}	
	__FLOG(_L8("CQueryHandler::ScheduleOutStandingQueryL - Exit"));	
	}

/*Only for the query recieved from the application there should be a delay
 * before sending the response .So wait for 120ms before sending the response
 * back to client.
 * Only for the query recieved from the client ,iClientHandle will not be NULL
 * @param aError is the error in case any in delivering the packet.
 */
void CQueryHandler::OnPacketSendL(TInt /*aError*/)	
	{
	__FLOG(_L8("CQueryHandler::OnPacketSendL - Entry"));
	if(iClientHandle != 0)
		{
		TTime time;
		time.HomeTime();
		time = time + TTimeIntervalMicroSeconds(120000);
		ScheduleOutStandingQueryL(iClientHandle,EFalse,time);
		iClientHandle =0 ;	
		}
	__FLOG(_L8("CQueryHandler::OnPacketSendL - Exit"));		
	}
