bluetooth/btstack/sdp/sdpclient.cpp
changeset 0 29b1cd4cb562
child 48 22de2e391156
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bluetooth/btstack/sdp/sdpclient.cpp	Fri Jan 15 08:13:17 2010 +0200
@@ -0,0 +1,652 @@
+// 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:
+//
+
+#include <bluetooth/logger.h>
+#include <bt_sock.h>
+#include <btsdp.h>
+#include "sdp.h"
+#include "sdpnetdb.h"
+#include "sdppdu.h"
+#include "sdpstackutil.h"
+#include "sdpclient.h"
+
+#ifdef __FLOG_ACTIVE
+_LIT8(KLogComponent, LOG_COMPONENT_SDP);
+#endif
+
+CSdpClient::CSdpClient(CSdpProtocol& aProtocol, CProtocolBase& aL2CAP)
+	: iNetDbProviders(_FOFF(CSdpNetDbProvider, iLink)),
+	iOutboundQ(_FOFF(CSdpPdu, iLink)),
+	iProtocol(aProtocol),
+	iL2CAP(aL2CAP)
+	{
+	TCallBack cb(IdleTimerExpired, this);
+	iIdleTimerEntry.Set(cb);
+	}
+
+void CSdpClient::ConstructL()
+	{
+	iBoundSAP=iL2CAP.NewSAPL(KSockSeqPacket);
+	iBoundSAP->SetNotify(this);
+	CheckForIdle(KSDPIdleTimeout*4);// No point hanging about if no one connects
+	}
+
+CSdpClient* CSdpClient::NewL(CSdpProtocol& aProt, CProtocolBase& aL2CAP)
+	{
+	CSdpClient* self = new(ELeave) CSdpClient(aProt, aL2CAP);
+	CleanupStack::PushL(self);
+	self->ConstructL();
+	CleanupStack::Pop();
+	return self;
+	}
+
+CSdpClient::~CSdpClient()
+	{
+	delete iNextPacket;
+	delete iBoundSAP;
+	while (!iOutboundQ.IsEmpty())
+		{
+		delete iOutboundQ.First();
+		}
+	while (!iNetDbProviders.IsEmpty())
+		{// Tell any lingering netdb's that we're gone.
+		iNetDbProviders.First()->ClientDown();
+		// Note -- this will cause idle timer to be re-queued, 
+		// so we must cancel it *after* taking netdbs down.
+		}
+	CancelIdleTimer();
+	}
+
+/****************************************************************************/
+/* 
+   Commands from protocol
+*/
+
+void CSdpClient::Open(TBTDevAddr& aAddr)
+/**
+    Create lower-layer link to the remote device.
+   
+	The bound sap will be used to
+    create an L2CAP link to the remote end over which the SDP
+    PDUs will flow.  The client will then run this channel.
+**/
+	{
+	__ASSERT_DEBUG(!ChannelUp(), Panic(ESdpClientAlreadyConnected));
+	iRemoteAddr=aAddr;
+	iBoundSAP->AutoBind();
+	TL2CAPSockAddr remote;
+	remote.SetBTAddr(aAddr);
+	remote.SetPort(KSDPPSM);  // Always 0x01
+	if(iBoundSAP->SetRemName(remote)!=KErrNone)
+		{
+		Panic(ESdpErrorSettingAddress);
+		}
+	iBoundSAP->ActiveOpen();  // Signals ConnectComplete eventually!
+	}
+
+void CSdpClient::AddNetDbProvider(CSdpNetDbProvider& aNetDbProvider)
+	{
+	CancelIdleTimer();
+	iNetDbProviders.AddFirst(aNetDbProvider);
+	if (ChannelUp())
+		aNetDbProvider.ClientUp();
+	}
+
+void CSdpClient::RemoveNetDbProvider(CSdpNetDbProvider& aNetDbProvider)
+	{
+	
+   	if(!iOutboundQ.IsEmpty())
+		{
+
+		CSdpPdu* request = iOutboundQ.First();
+		CSdpNetDbProvider* netdb = request->NetDbProvider();
+		if(netdb == &aNetDbProvider)
+			request->iNetDbProvider = 0;
+
+		TDblQueIter<CSdpPdu> iter(iOutboundQ);
+		while(iter)
+			{
+			request = iter++;
+			if(request->NetDbProvider()==&aNetDbProvider)
+				//NB 1st one is NULL so won't be deleted
+				delete request;
+			}
+		}
+
+	aNetDbProvider.iLink.Deque();
+	if (iNetDbProviders.IsEmpty())
+		{
+		CheckForIdle(KSDPIdleTimeout);
+		}
+	}
+
+/****************************************************************************/
+/* 
+   Commands for netdb provider
+*/
+
+inline TUint8 DEHeader(TUint8 aType, TUint8 aSizeIndex)
+/**
+	Make a data element header from aType and aSizeIndex.
+	aType occupies top 5 bits, aSizeIndex the bottom 3 bits.
+**/
+	{// Move aType 3 bits across.
+	return (TUint8)((aType << 3) | (aSizeIndex & 0x7));
+	}
+
+void CSdpClient::ServiceSearchRequest(CSdpNetDbProvider& aNetDbProvider,
+		TUint16 aMaxCount, const TUUID &aUUID, const TDesC8& aContState)
+	{
+	if (!ChannelUp())
+		{
+		aNetDbProvider.Error(KErrSdpClientNotConnected);
+		return;
+		}
+	CSdpPdu* pdu = new CSdpPdu(&aNetDbProvider);
+	if (!pdu)
+		{
+		aNetDbProvider.Error(KErrNoMemory);
+		return;
+		}
+
+	TPtrC8 uuid(aUUID.ShortestForm());
+	TInt len = uuid.Length();
+	TUint8 szIndex = 0;
+	while (len >>= 1)
+		++szIndex;
+	__ASSERT_DEBUG(uuid.Length() == (1<<szIndex), Panic(ESdpBadUUID));
+
+	pdu->SetPduId(EServiceSearchRequest);
+	pdu->SetTransid(NextTransId());
+	pdu->PutByte(DEHeader(6,5));  // Header: DES, 1 byte length
+	pdu->PutByte(TUint8(1+uuid.Length()));// Size: Length of one UUID element
+	pdu->PutByte(DEHeader(3,szIndex)); //	 Header: 'len' byte UUID
+	pdu->PutData(uuid);			  //   Data: The UUID
+	pdu->PutBigEndian16(aMaxCount);	// Maximum Service record count
+	pdu->PutByte((TUint8)aContState.Length());	// Cont state length
+	pdu->PutData(aContState);
+	pdu->SetLength();
+	EnqueOutboundPdu(*pdu);
+	}
+
+void CSdpClient::ServiceAttributeRequest(CSdpNetDbProvider& aNetDbProvider,
+		TUint aRecordHandle, TUint16 aMaxLength, TBool aRange,
+		TUint aAttribute, const TDesC8& aContState)
+	{
+	if (!ChannelUp())
+		{
+		aNetDbProvider.Error(KErrSdpClientNotConnected);
+		return;
+		}
+	CSdpPdu* pdu = new CSdpPdu(&aNetDbProvider);
+	if (!pdu)
+		{
+		aNetDbProvider.Error(KErrNoMemory);
+		return;
+		}
+
+	pdu->SetPduId(EServiceAttributeRequest);
+	pdu->SetTransid(NextTransId());
+	pdu->PutBigEndian32(aRecordHandle);
+	pdu->PutBigEndian16(aMaxLength);
+	pdu->PutByte(DEHeader(6,5));        // Header: DES, 1 byte length
+	if (aRange)
+		{// Range of attribute IDs. Send 1 4-byte uint.
+		pdu->PutByte(5);			    // Size: Length of 16 uint element
+		pdu->PutByte(DEHeader(1,2));    //	 Header: 4 byte integer
+		pdu->PutBigEndian32(aAttribute);//   Attribute ID range
+		}
+	else
+		{// Single attribute ID. Send 1 2-byte uint.
+		pdu->PutByte(3);			    // Size: Length of 32 uint element
+		pdu->PutByte(DEHeader(1,1));    //	 Header: 2 byte integer
+		pdu->PutBigEndian16(static_cast<TUint16>(aAttribute));//   Single attribute ID
+		}
+	pdu->PutByte(static_cast<TUint8>(aContState.Length()));	// Cont state length
+	pdu->PutData(aContState);
+	pdu->SetLength();
+	EnqueOutboundPdu(*pdu);
+	}
+
+void CSdpClient::EncodedRequest(CSdpNetDbProvider& aNetDbProvider,
+								TUint8 aPduRequestId,
+								const TDesC8& aEncodedData)
+	{
+	if (!ChannelUp())
+		{
+		aNetDbProvider.Error(KErrSdpClientNotConnected);
+		return;
+		}
+	CSdpPdu* pdu = new CSdpPdu(&aNetDbProvider);
+	if (!pdu)
+		{
+		aNetDbProvider.Error(KErrNoMemory);
+		return;
+		}
+
+	pdu->SetPduId(aPduRequestId);
+	pdu->SetTransid(NextTransId());
+	pdu->PutData(aEncodedData);
+	pdu->SetLength();
+	EnqueOutboundPdu(*pdu);
+	}
+
+
+/****************************************************************************/
+/* 
+   Protocol MSocketNotify functions
+   Upcalls from L2CAP
+*/
+void CSdpClient::NewData(TUint aCount)
+/**
+	New data arrived on lower SAP.
+	Read out the data straight away.
+	@param	aCount Number of packets that have arrived.
+**/
+	{
+	LOG1(_L("SDP: New data, count %d"), aCount);
+
+	TPckgBuf<TInt> mru;
+	TInt err = iBoundSAP->GetOption(KSolBtL2CAP, KL2CAPInboundMTU, mru);
+	if (err == KErrNone && (!iNextPacket || mru() > iNextPacket->Size()))
+		{
+		delete iNextPacket;
+		iNextPacket = HBufC8::New(mru());
+		if (iNextPacket == 0)
+			{
+			err = KErrNoMemory;
+			}
+		}
+	if(err)
+		{
+		Error(err, EErrorFatal);
+		return;
+		}
+
+	TPtr8 data = iNextPacket->Des();
+	while (aCount)
+		{
+		data.SetMax();
+		iBoundSAP->GetData(data, 0);
+		LOGHEXDESC(data)
+		ParseNextPacket();
+		--aCount;
+		}
+	}
+
+void CSdpClient::CanSend()
+	{
+	L2CAPBlocked(EFalse);
+	TryToSend();
+	}
+
+void CSdpClient::ConnectComplete()
+/**
+	Right, we're connected.
+	Let all the netdbs waiting on us know.
+	Should check L2CAP MTU's now, but for now we're just
+	trusting that they'll be large enough to hold our very
+	small requests and responses, as we can't fragment anyway.
+**/
+	{
+	ChannelUp(ETrue);
+	TDblQueIter<CSdpNetDbProvider> iter(iNetDbProviders);
+	
+	while(iter)
+		{
+		(iter++)->ClientUp();
+		}
+	}
+
+void CSdpClient::ConnectComplete(const TDesC8&)
+	{
+	Panic(ESdpConnectWithData);
+	}
+
+void CSdpClient::ConnectComplete(CServProviderBase&)
+	{
+	Panic(ESdpClientPassiveConnect);
+	}
+
+void CSdpClient::ConnectComplete(CServProviderBase&,const TDesC8& )
+	{
+	Panic(ESdpClientPassiveConnect);
+	}
+
+void CSdpClient::CanClose(TDelete /*aDelete*/)
+/**
+	Line ourselves up ready for deletion.
+	The bound SAP calling this upcall will be tided up when we are,
+	so there's no point getting carried away in doing it here.
+**/
+	{
+	iRemoteAddr.Reset();
+	ChannelUp(EFalse);
+	TDblQueIter<CSdpNetDbProvider> iter(iNetDbProviders);
+	while(iter)
+		{//Tell the netdb's we're down.
+		(iter++)->ClientDown();
+		}
+	CheckForIdle(0);	// Async call back to delete ourselves!
+	}
+
+void CSdpClient::CanClose(const TDesC8&, TDelete)
+	{
+	Panic(ESdpDisconnectWithData);
+	}
+
+void CSdpClient::Error(TInt aError,TUint anOperationMask)
+/**
+   Something's gone wrong.
+
+   We get told whether it's just this operation or all that are
+   affected, but we'll just assume that all of them are bust for
+   now.
+**/
+	{
+	LOG2(_L("SDP: L2CAP socket error: %d, mask: 0x%x"), aError, anOperationMask);
+	if (!(anOperationMask & ~EErrorIoctl))
+		return;//Ignore Ioctl errors.
+	if (anOperationMask & EErrorFatal)
+		ChannelUp(EFalse);
+	TDblQueIter<CSdpNetDbProvider> iter(iNetDbProviders);
+	CSdpNetDbProvider* netdb;
+	
+	while(iter)
+		{
+		netdb=iter++;
+		netdb->Error(aError);
+		}
+
+	iRemoteAddr.Reset();
+	CheckForIdle(0);
+	}
+
+void CSdpClient::Disconnect()
+/**
+	We're no longer connected
+**/
+	{
+	LOG(_L("SDP bearer disconnected. Closing..."))
+	CanClose();
+	}
+
+void CSdpClient::Disconnect(TDesC8& /*aDisconnectData*/)
+	{
+	Panic(ESdpDisconnectWithData);
+	}
+
+void CSdpClient::IoctlComplete(TDesC8* /*aBuf*/)
+	{
+	// This must be an internally generated ioctl
+	LOG(_L("SDP: Unexpected IoctlComplete upcall!"));
+	}
+
+/****************************************************************************/
+/* 
+   Internal functions
+*/
+
+void CSdpClient::ParseNextPacket()
+/**
+	Parse PDUs out of the L2CAP packet received.
+	This is actually a bit general, as we can have at most
+	one PDU per packet (SDP v1.1).
+**/
+	{
+	TPtr8 packet(iNextPacket->Des());
+	TInt rem = packet.Length();
+	TUint8* ptr = rem ? &packet[0] : 0;
+	while (rem >= KSdpPduHeaderSize)
+		{
+		TUint8  pduid   = ptr[KSdpPduIdOffset];
+		TUint16 transid = BigEndian::Get16(ptr+KSdpPduTransIdOffset);
+		TUint16 paramlen= BigEndian::Get16(ptr+KSdpPduParamLengthOffset);
+		if (rem < KSdpPduHeaderSize + paramlen)
+			break;
+		ptr += KSdpPduHeaderSize;
+		TPtrC8 params(ptr, paramlen);
+		HandlePDU(pduid, transid, params);
+		ptr += paramlen;
+		rem -= (KSdpPduHeaderSize+paramlen);
+		}
+	if (rem)
+		{//Partial pdu received. Bin it
+		LOG1(_L("SDP: Frame error: %d extra bytes received\n"), rem);
+		}
+	}
+
+void CSdpClient::HandlePDU(TUint8 aPduId, TUint16 aTransId, const TDesC8& aParams)
+	{
+ 	if(!RequestOutstanding())
+ 		{// The server is spontaneously sending us data. DROP it
+ 		return;
+ 		}
+
+	// There can only be one oustanding pdu,
+	// So the first pdu on the list must be the one.
+	__ASSERT_DEBUG(!iOutboundQ.IsEmpty(),Panic(ESdpNoRequestPdu));
+   	CSdpPdu* request = iOutboundQ.First();
+	CSdpNetDbProvider* netdb = request->NetDbProvider();
+	TUint16 reqTransId = request->TransId();
+
+	RequestOutstanding(EFalse);
+	// Request complete. So delete. (Removes from Q, so we can carry on sending).
+	delete request;
+	TryToSend();
+
+	if(!netdb)
+		return;
+
+	if (reqTransId != aTransId)
+   		{//DROP
+		LOG2(_L("SDP: Unexpected PDU; pdu id %d, transaction id: %d\n"), aPduId, aTransId);
+		netdb->Error(KErrSdpBadResultData);
+		return;
+		}
+
+	switch (aPduId)
+		{
+	case EErrorResponse:
+		HandleErrorResponse(netdb, aParams);
+		break;
+
+	case EServiceSearchResponse:
+		HandleServiceSearchResponse(netdb, aParams);
+		break;
+
+	case EServiceAttributeResponse:
+		HandleServiceAttributeResponse(netdb, aParams);
+		break;
+
+	default:
+		LOG1(_L("SDP: Unexpected PDU type; pdu id: %d\n"), aPduId);
+		netdb->Error(KErrSdpBadResultData);
+		};
+
+	}
+
+void CSdpClient::HandleErrorResponse(CSdpNetDbProvider* aNetDbProv, const TDesC8& aParams)
+/**
+	We've got an error response.
+
+	Parameter format is...
+	<pre>
+	  Byte  |  0   |  1  |  2   |  3  ....
+	        | Error Code | Error info (dependent on code)
+	</pre>
+**/
+	{
+	const TInt KMinLength = 2;
+	const TInt KErrorCodeOffset = 0;
+
+	TInt err = KErrGeneral; // The code to send up...
+
+	if (aParams.Length() < KMinLength)
+		{// Way! Bad error response! drop it..
+		err = KErrSdpPeerError;
+		}
+	else
+		{
+		TSdpErrorCodes code = (TSdpErrorCodes)BigEndian::Get16(&aParams[KErrorCodeOffset]);
+		switch (code)
+			{
+		case EInvalidSdpVersion:
+			err=KErrSdpUnsupportedVersion;
+			break;
+		case EInvalidServiceRecordHandle:
+			err=KErrSdpBadRecordHandle;
+			break;
+		case EInvalidContinuationState:
+			err=KErrSdpBadContinuationState;
+			break;
+		case EInvalidRequestSyntax:
+		case EInvalidPduSize:
+		default:
+			err=KErrSdpServerRejectedRequest;
+			}
+		}
+
+	if (!aNetDbProv)
+		{
+		LOG1(_L("SDP: Error response with no owning aNetDbPr %d\nWill error them all!\n"), err);
+		Error(err, EErrorAllOperations);
+		return;
+		}
+	aNetDbProv->Error(err);
+	}
+
+void CSdpClient::HandleServiceSearchResponse(CSdpNetDbProvider* aNetDbProv, const TDesC8& aParams)
+/**
+	Got service search response.
+	Pass it right on up to the net db.
+**/
+	{
+	if (!aNetDbProv)
+		{
+		LOG(_L("SDP: Service search response with no owning aNetDbPr\n"));
+		return;
+		}
+	aNetDbProv->QueryComplete(aParams);
+	}
+
+void CSdpClient::HandleServiceAttributeResponse(CSdpNetDbProvider* aNetDbProv, const TDesC8& aParams)
+/**
+	We've got an attribute request response.
+	Pass the params up to the net db. It'll be dealt with client side!
+
+**/
+	{
+	if (!aNetDbProv)
+		{
+		LOG(_L("SDP: Attribute response with no owning aNetDbPr\n"));
+		return;
+		}
+	aNetDbProv->QueryComplete(aParams);
+	}
+
+CSdpPdu* CSdpClient::NewPdu(CSdpNetDbProvider* aNetDb)
+	{
+	return new CSdpPdu(aNetDb);
+	}
+
+TUint16 CSdpClient::NextTransId()
+	{
+	return ++iTransId ? iTransId : ++iTransId;
+	}
+
+void CSdpClient::EnqueOutboundPdu(CSdpPdu& aPdu)
+	{
+	iOutboundQ.AddLast(aPdu);
+	TryToSend();
+	}
+
+void CSdpClient::TryToSend()
+/**
+	Try to send out a pdu.
+	Can do this if we have something to send, L2CAP is not blocked,
+	and there is no outstanding request.
+	There are 3 different places TryToSend can be called,
+	corresponding to invalidation of these three guards.
+
+	Pdu tries to emit for is one. It can make this if we will have something to emit, L2CAP is
+	not obstructed, and it does not have no prominent order. It has 3 different places that
+	TryToSend can be called, corresponding to invalidation of these three holds. 
+**/
+	{
+	if (RequestOutstanding())
+		return;
+	if (iOutboundQ.IsEmpty())
+		return;
+	if (L2CAPBlocked())
+		return;
+
+	// To pipeline, we should iterate here, until no more can fit in the packet,
+	// then send packet. Should really mark the pdus as sent too.
+	CSdpPdu& pdu = *iOutboundQ.First();
+	// We just try to write, regardless of MTU! If it doesn't fit, we'll
+	// get back an error from remtoe. But our requests generally should fit.
+	if (!iBoundSAP->Write(pdu.Data(), 0))
+		{
+		L2CAPBlocked(ETrue);
+		return;
+		}
+	// PDU will be dequed when it's response returns.
+	// This is easy to do, as we're not pipelining requests in L2CAP packets,
+	// so at most only one pdu can be outstanding at once.
+	RequestOutstanding(ETrue);
+	}
+
+void CSdpClient::CheckForIdle(TInt aTimeoutInSeconds)
+	{
+	CancelIdleTimer();
+	iIdleQueued = ETrue;
+	// Add 10 to allow aTimeInSeconds to be 0.
+    BTSocketTimer::Queue((aTimeoutInSeconds*1000000)+10, iIdleTimerEntry);
+	}
+
+void CSdpClient::CancelIdleTimer()
+	{
+	if (iIdleQueued)
+		{
+	    BTSocketTimer::Remove(iIdleTimerEntry);
+		iIdleQueued = EFalse;
+		}
+	}
+
+TInt CSdpClient::IdleTimerExpired(TAny* aPtr)
+/**
+	Static function called whenever idle timer expires.
+	Take us down. Gracefully, mind!
+**/
+	{
+	CSdpClient* self = static_cast<CSdpClient*>(aPtr);
+	__ASSERT_DEBUG(self->iIdleQueued, Panic(ESdpClientBadIdleState));
+	self->iIdleQueued = EFalse;
+	if (self->ChannelUp())
+		{// Take it down, first.
+		// Clear remote address, so we don't accept further netdbs.
+		self->iRemoteAddr.Reset();
+		self->ChannelUp(EFalse);
+		self->iBoundSAP->Shutdown(CServProviderBase::ENormal);
+		self->CheckForIdle(KSDPIdleTimeout*2);
+		}
+	else
+		{// Channels down. Say good bye!
+		self->iProtocol.ClientDown(*self);	// Who'll promtly delete 'self'
+		}
+	return FALSE;
+	}
+