diff -r 000000000000 -r 29b1cd4cb562 bluetooth/btstack/sdp/sdpclient.cpp --- /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 +#include +#include +#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 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<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(aAttribute));// Single attribute ID + } + pdu->PutByte(static_cast(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 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 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 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 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... +
+	  Byte  |  0   |  1  |  2   |  3  ....
+	        | Error Code | Error info (dependent on code)
+	
+**/ + { + 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(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; + } +