bluetooth/btsdp/database/responsesizevisitor.cpp
author hgs
Wed, 13 Oct 2010 16:20:29 +0300
changeset 51 20ac952a623c
parent 0 29b1cd4cb562
permissions -rw-r--r--
201040_02

// Copyright (c) 2000-2010 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 <btsdp.h>
#include "responsesizevisitor.h"
#include "SDPDatabase.h"
#include "mignorer.h"
#include "DataEncoder.h"

#include <bluetooth/logger.h>
DEFINE_LOG_COMPONENT(LOG_COMPONENT_SDPDATABASE)

DEBUG_PANIC_CATEGORY("sdprspvis")


// Class CHandleItem

CHandleItem* CHandleItem::NewLC(TSdpServRecordHandle aHandleID, CSdpServRecord* aRecord)
	{
	LOG_STATIC_FUNC
	CHandleItem* self = new(ELeave) CHandleItem(aHandleID, aRecord);
	CleanupStack::PushL(self);
	self->ConstructL();
	return self;
	}

CHandleItem::CHandleItem(TSdpServRecordHandle aHandleID, CSdpServRecord* aRecord)
	: CBase()
	, iHandleID(aHandleID)
	, iRecord(aRecord)
	{
	LOG_FUNC
	}

void CHandleItem::ConstructL()
	{
	LOG_FUNC
	iAttrSizeList = new (ELeave) CArrayPtrFlat<TAttrSizeItem>(KSDPAttListGran);
	}

CHandleItem::~CHandleItem()
	{
	LOG_FUNC
	if (iAttrSizeList)
		{
		iAttrSizeList->ResetAndDestroy();
		delete iAttrSizeList;
		}
	}

void CHandleItem::AddAttrItemL(TAttrSizeItem* aItem)
	{
	iAttrSizeList->AppendL(aItem);
	iRecordSize += aItem->Size() + 3; // add the attrID header size;
	}

void CHandleItem::AddAttrItemL(const TSdpAttributeID aAttributeID, TUint aSize, CSdpAttr* aAttribute)
	{
	TAttrSizeItem* sizeItem = new (ELeave) TAttrSizeItem(aAttributeID, aSize, aAttribute);
	CleanupStack::PushL(sizeItem);
	AddAttrItemL(sizeItem);
	CleanupStack::Pop(sizeItem);
	}


// Class CSizeAccumulator

EXPORT_C CSizeAccumulator* CSizeAccumulator::NewLC()
	{
	CSizeAccumulator* self = new(ELeave)CSizeAccumulator();
	CleanupStack::PushL(self);
	self->ConstructL();
	return self;
	}

EXPORT_C CSizeAccumulator* CSizeAccumulator::NewL()
	{
	CSizeAccumulator* self = CSizeAccumulator::NewLC();
	CleanupStack::Pop();
	return self;
	}

void CSizeAccumulator::ConstructL()
	{
	iHandleList = new (ELeave) CArrayPtrFlat<CHandleItem>(KSDPHandListGran);
	}

CSizeAccumulator::~CSizeAccumulator()
	{
	if ( iHandleList )
		{
		iHandleList->ResetAndDestroy();
		delete iHandleList;
		}
	}

void CSizeAccumulator::AddHandleL(CHandleItem* aHandleItem)
	{
	iHandleList->AppendL(aHandleItem);
	}

// do a crc across all the attributes
EXPORT_C TUint16 CSizeAccumulator::CrcAttribs()
	{
	TUint16 totalCrc=0;
	for (TInt i = 0; i<HandleCount(); i++)
		{
		for (TInt j = 0; j<AttrCount(i); j++)
			{
			TAttrSizeItem* attr = AttributeOf(i,j);
			CSdpAttrValue& theVal = attr->Attr()->Value();
			Mem::Crc(totalCrc, reinterpret_cast<const TAny*>(theVal.Des()[0]), attr->Size());
			}
		}
	return totalCrc;
	}

/**
	CSizeAccumulator::SizeLeft
	calculate the size of the attribute list.

	Foundation of the continuation handling. 
	It is used together	with StartAt.
	It walks through the list of records, each should contain
	a list of attributes. There are internal start offsets for
	both the record and attribute lists. These are set with StartAt.

	The method returns the size (in bytes) of the attributes, together with
	3 bytes for each attribute ID (1 byte header, 2 byte UInt) and the correct
	number of bytes for each DES header for the attributes of each record.

	It does NOT calculate the DES header for a list of records. So an extra
	DES must be created for Searvice Search Attribute.

	This allows the total length to be written before the rest of the response
	packet. It also allows the request handler to decide if it can fit the whole
	response in to the response packet with or without continuation.
	Return format is
 @verbatim
		Size left					TUint
 @endverbatim
**/
EXPORT_C TUint CSizeAccumulator::SizeLeft()
	{
	TUint totalSize=0;
	TInt handleCount = HandleCount();	// for testing
	for (TInt i = iFirstRec; i < handleCount; i++)
		{
		TUint recordSize = 0;
		TInt attrCount = AttrCount(i); // for testing
		for (TInt j = iFirstAtt; j < attrCount; j++)
			{
			TAttrSizeItem* attr = AttributeOf(i,j);
			recordSize += attr->Size();
			recordSize +=3;		// the size of a attribute ID in the list
			}
		if (iFirstAtt == 0)
			{ // are we sizing the whole record ?
			TUint recRecSize = iHandleList->At(i)->CHandleItem::iRecordSize; // for testing
			if (recordSize != recRecSize) DbPanic(ESdpDbBadSearchPattern);
			}
		if (recordSize)
			{ // only add the header if there is any data
			TUint desSize;
			desSize = (recordSize > 0xff) ? 3 : 2;
			desSize = (recordSize > 0xffff) ? 5 : desSize;
			totalSize += recordSize + desSize;	// size of the DES header for this record
			}
		}
	return totalSize;

	}

	
/**
	CSizeAccumulator::StartAt
	Supplied with the data sent so far.
	This walks through the list of service search record handles
	and the the list of attributes associated with each record.

	Once it has found the location where the size is less than or
	equal to the parameter supplied, it sets CSizeAccumulators
	internal record and attribute offsets to the point it has
	reached. It will not update the offsets if the data is greater
	than the input offset.
	
	The method returns the two offsets and the data already sent
	from the current attribute.

	The method counts the DES header at the start of any record. It
	does NOT count the overall DES header if there is more than one
	record. To do this would require a count of all bytes to evaluate
	the DES header size. This action is the same as that of SizeLeft.

	******************************************************** 
	IMPORTANT NOTE: If aOffset traverses the database and  
	STOPS IN THE MIDDLE OF A NEW RECORD DES HEADER, it will return 
	THE DES HEADER OFFSET in the ATTRIBUTE OFFSET PARAMETER.
	This is ambiguous...
	****A calling function should ensure aOffset cannot do this***

	The method returns a bool to indicate the setting was OK. It will
	be false if the two index parameters were used and they would
	have set the lists beyond their end. Also it returns false if the
	offset was beyond the end of the data. If the method returns EFalse,
	the two index parameters are set to 0.

	This method can reset the parameters by being called with offset 0.
	However it will then return false.
	Parameter format is
 @verbatim
		Offset in bytes from start of attribute data		TUint
		Record index (0...)									TInt
		Attribute index (0...)								TInt
 @endverbatim
	Return format is
 @verbatim
		Were the offsets set OK								TBool
		Record index is set									TInt
		Attribute index is set								TInt
 @endverbatim

**/
EXPORT_C TBool CSizeAccumulator::StartAt(TUint aOffset, TUint& aPartSent, TInt& aRec, TInt& aAtt)
	{
	// calculates and sets the next record and attribute
	if (aOffset == 0)
		{
		aRec = 0; iFirstRec = 0;
		aAtt = 0; iFirstAtt = 0;
		aPartSent = 0;
		return EFalse;
		}
	TUint totalSize=0;
	TBool more = ETrue;
	TInt topRec = HandleCount();
	if (aRec > topRec)
		{
		aRec = 0; iFirstRec = 0;
		aAtt = 0; iFirstAtt = 0;
		aPartSent = 0;
		return EFalse;
		}
	else if (aRec == topRec)
		{
		TInt partAtt = AttrCount(topRec);
		if (aAtt > partAtt)
			{
			aRec = 0; iFirstRec = 0;
			aAtt = 0; iFirstAtt = 0;
			aPartSent = 0;
			return EFalse;
			}
		}
	iFirstRec = aRec;
	TInt firstAttForNextRecord = aAtt;
	while (more && (iFirstRec < topRec))
		{
		TUint recordSize = iHandleList->At(iFirstRec)->CHandleItem::iRecordSize;
		TInt topAtt = AttrCount(iFirstRec);
		iFirstAtt = firstAttForNextRecord < topAtt ? firstAttForNextRecord : topAtt;
// except for first record we always start with a record's first attribute
		firstAttForNextRecord = 0; 
// add in the DES header size for this record
		TUint desSize;
		desSize = (recordSize > 0xff) ? 3 : 2;
		desSize = (recordSize > 0xffff) ? 5 : desSize;
		if (totalSize + desSize > aOffset)
			//see "IMPORTANT NOTE" above this method
			{
			more = EFalse;
			break;
			}
		totalSize += desSize;
		while (more && (iFirstAtt < topAtt))
			{
			TAttrSizeItem* attr = AttributeOf(iFirstRec, iFirstAtt);
			TUint nextSize = attr->Size();
			nextSize += 3;	// Attribute ID and its header
			if (totalSize + nextSize > aOffset)
				{
				more = EFalse;
				break;
				}
			totalSize += nextSize;
			iFirstAtt++;
			}
		if (more) iFirstRec++;
		}
	if (totalSize == aOffset) more = EFalse;
	if (more)
		{
		aRec = 0; iFirstRec = 0;
		aAtt = 0; iFirstAtt = 0;
		aPartSent = 0;
		return EFalse;
		}
	aRec = iFirstRec;
	aAtt = iFirstAtt;
	aPartSent = aOffset - totalSize;
	return ETrue;
	}

EXPORT_C TInt CSizeAccumulator::HandleCount()
	{
	return iHandleList->Count();
	}

EXPORT_C TSdpServRecordHandle CSizeAccumulator::HandleAt(TInt aOffset)
	{
	__ASSERT_DEBUG(aOffset <= iHandleList->Count(), DbPanic(ESdpDbBadSearchPattern)); // FIXME different panic code please
	return iHandleList->At(aOffset)->CHandleItem::iHandleID;
	}

EXPORT_C TUint CSizeAccumulator::HandleSize(TInt aOffset)
	{
	return iHandleList->At(aOffset)->CHandleItem::iRecordSize;
	}

EXPORT_C TInt CSizeAccumulator::AttrCount(TInt aOffset)
	{
	if (iHandleList->Count() == 0)
		{
		return 0;
		}
	__ASSERT_DEBUG(aOffset <= iHandleList->Count(), DbPanic(ESdpDbBadSearchPattern));
	
	if (!iHandleList->At(aOffset))
		{
		return 0;
		}
	
	return iHandleList->At(aOffset)->iAttrSizeList->Count();
	}

EXPORT_C TAttrSizeItem* CSizeAccumulator::AttributeOf(TInt aHandleOffset, TInt aAttOffset)
	{
	if (iHandleList->Count() == 0)
		{
		return NULL;
		}
	__ASSERT_DEBUG(aHandleOffset < iHandleList->Count(), DbPanic(ESdpDbBadSearchPattern));
	
	CHandleItem* hnd = iHandleList->At(aHandleOffset);
	
	if (hnd->iAttrSizeList->Count() == 0)
		{
		return NULL;
		}
	__ASSERT_DEBUG(aAttOffset < hnd->iAttrSizeList->Count(), DbPanic(ESdpDbBadSearchPattern));
	
	return hnd->iAttrSizeList->At(aAttOffset);
	}


// Class CResponseSizeVisitor 

CResponseSizeVisitor::CResponseSizeVisitor()
	: CBase()
	, iAdapter(*this)
	{
	LOG_FUNC
	}

CResponseSizeVisitor::~CResponseSizeVisitor()
	{
	LOG_FUNC
	delete iFoundIndex;
	delete iParser;
	}

CResponseSizeVisitor* CResponseSizeVisitor::NewLC()
	{
	LOG_STATIC_FUNC
	CResponseSizeVisitor* self = new(ELeave) CResponseSizeVisitor();
	CleanupStack::PushL(self);
	self->ConstructL();
	return self;
	}

void CResponseSizeVisitor::ConstructL()
	{
	LOG_FUNC
	iParser = CElementParser::NewL(&iAdapter);
	}

// Iterate thru attributes in record
/* Search through all attributes in a record
   either to find if it matches the UUIDlist,
   or if the attributes match the AttrIdMatchList
   or both. If there is no UUIDList then the sizes
   of matched attributes is saved to the size collector
*/
void CResponseSizeVisitor::SearchRecordL(CSdpServRecord& aRec)
	{
	LOG_FUNC
	// could be in calling routine
	if (iSearchPattern)
		{
		delete iFoundIndex;
		iFoundIndex = NULL;
		iFoundIndex = CBitMapAllocator::NewL(iSearchSize);
		iUseThis = EFalse;
		}
	else
		{
		iUseThis = ETrue;
		}
		
	CHandleItem* currentRec = CHandleItem::NewLC(aRec.Handle(), &aRec);
	
	for(TServAttrIter attrIter(aRec.AttributeIter()); attrIter; attrIter++)
		{
		// we are checking every attribute, searching for the UUIDs. We don't exit if
		// we find them all because we also want to get the sizes of any attributes which
		// match our list if we have one.
		// The array of sizes is built, then thrown away if it doesn't match.
		
		CSdpAttr* attr = attrIter;
		
		if (iAtMatList)
			{
			TSdpAttributeID theAttrID = attr->AttributeID();
			if(iAtMatList->InMatchList(theAttrID))
				{// create a new array entry
				TUint size = attr->Value().DataSize();
				TSdpElementType type = attr->Value().Type();
				if (type != ETypeEncoded) 
					{
					// some server records may be un-encoded
					size += TElementEncoder::HeaderSize(type, size); // add the header size
					}
				if(type != ETypeNil && !(type == ETypeEncoded && size <= 1))
					{
					// only include attribute if not either null or encoded null ...
					// (size of encoded (which INCLUDES header) in all non-null cases is > 1
					//  because other attribute types either have
					//  to have a header plus at least one byte, or they have at least a two byte header.)
					currentRec->AddAttrItemL(theAttrID, size, attr);
					}
				}
			}
		// check if any UUIDs match only if we have a UUIDList.
		if (iSearchPattern)
			{
			attr->AcceptVisitorL(*this);
			}
		}
	
	if (iUseThis)
		{
		// now we have to copy the array(s) we've built up.
		iCollector->AddHandleL(currentRec);
		CleanupStack::Pop(currentRec);
		}
	else
		{
		CleanupStack::PopAndDestroy(currentRec);
		}
	}


EXPORT_C void CResponseSizeVisitor::SizeRespSSL(CSdpDatabase& aDb, const CSdpSearchPattern& aPattern, CSizeAccumulator& aCollector)
	{
	LOG_STATIC_FUNC
	if (aPattern.Count() == 0)
		{
		User::Leave(KErrArgument); // this is part of spec.
		}
	
	LOG1(_L8("Sizing SDP DB for pattern with %d entries"), aPattern.Count());
	CResponseSizeVisitor* theVisitor = CResponseSizeVisitor::NewLC();
	
	theVisitor->iCollector = &aCollector;
	theVisitor->iAtMatList = NULL; // using it as a flag...
	theVisitor->iSearchPattern = &aPattern;
	theVisitor->iSearchSize = theVisitor->iSearchPattern->Count();
	
	for(TServRecordIter recIter(aDb.RecordIter()); recIter; recIter++)
		{// Iterate thru records in Db
		theVisitor->SearchRecordL(*recIter);
		}
	
	CleanupStack::PopAndDestroy(theVisitor);
	}

EXPORT_C void CResponseSizeVisitor::SizeRespARL(CSdpServRecord& aRec, const CSdpAttrIdMatchList& aList, CSizeAccumulator& aCollector)
	{
	LOG_STATIC_FUNC
	
	LOG(_L8("Sizing SDP DB for single record"));
	CResponseSizeVisitor* theVisitor = CResponseSizeVisitor::NewLC();
	
	theVisitor->iCollector = &aCollector;
	theVisitor->iAtMatList = &aList; // using it as a flag...
	theVisitor->iSearchPattern = NULL;
	theVisitor->iSearchSize = 0;
	
	theVisitor->SearchRecordL(aRec);
	
	CleanupStack::PopAndDestroy(theVisitor);
	}

EXPORT_C void CResponseSizeVisitor::SizeRespSAL(CSdpDatabase &aDb, const CSdpSearchPattern &aPattern, const CSdpAttrIdMatchList& aList, CSizeAccumulator& aCollector)
	{
	LOG_STATIC_FUNC
	if (aPattern.Count() == 0)
		{
		User::Leave(KErrArgument); // this is part of spec.
		}
	
	LOG1(_L8("Sizing SDP DB (and attribs) with %d UUIDs"), aPattern.Count());
	CResponseSizeVisitor* theVisitor = CResponseSizeVisitor::NewLC();
	
	theVisitor->iCollector = &aCollector;
	theVisitor->iAtMatList = &aList; // using it as a flag...
	theVisitor->iSearchPattern = &aPattern;
	theVisitor->iSearchSize = theVisitor->iSearchPattern->Count();
	
	for(TServRecordIter recIter(aDb.RecordIter()); recIter; recIter++)
		{// Iterate thru records in Db
		theVisitor->SearchRecordL(*recIter);
		}
	
	CleanupStack::PopAndDestroy(theVisitor);
	}

void CResponseSizeVisitor::VisitAttributeL(CSdpAttr& /*aAttribute*/)
	{
	}

void CResponseSizeVisitor::VisitAttributeValueL(CSdpAttrValue &aValue, TSdpElementType aType)
	{
	switch (aType)
		{
	case ETypeUUID:
		FoundUUIDL(aValue.UUID());
		break;
		
	case ETypeEncoded:
		// parse out any UUIDs in this encoded attribute
		iParser->Reset();
		/*rem = */iParser->ParseElementsL(aValue.Des());
		break;
		
	default:
		break;
		}
	}

void CResponseSizeVisitor::StartListL(CSdpAttrValueList &/*aList*/)
	{
	}

void CResponseSizeVisitor::EndListL()
	{
	}

void CResponseSizeVisitor::FoundUUIDL(const TUUID& aUUID)
	{
	ASSERT_DEBUG(iSearchPattern);
	ASSERT_DEBUG(iFoundIndex);
	TInt pos;
	if (iSearchPattern->Find(aUUID, pos)==0 &&
		iFoundIndex->IsFree(pos))
		{
		iFoundIndex->AllocAt(pos);
		if (iFoundIndex->Avail() == 0)
			{// We've found what we were searching for.
			iUseThis = ETrue;
			}
		}
	}


TSizeEncVisitorAdaptor::TSizeEncVisitorAdaptor(CResponseSizeVisitor& aVisitor)
	:iVisitor(aVisitor)
	{}

MSdpElementBuilder* TSizeEncVisitorAdaptor::BuildUUIDL(const TUUID& aUUID)
	{
	iVisitor.FoundUUIDL(aUUID);
	return this;
	}