diff -r 000000000000 -r 29b1cd4cb562 bluetooth/btstack/avctp/avctpsap.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bluetooth/btstack/avctp/avctpsap.cpp Fri Jan 15 08:13:17 2010 +0200 @@ -0,0 +1,1605 @@ +// Copyright (c) 2005-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: +// Implements the code for AVCTP SAPs +// +// + +/** + @file + @internalTechnology +*/ + +#include +#include +#include "Avctp.h" +#include "avctpsap.h" +#include "avctpmuxer.h" +#include "avctpcommon.h" +#include "avctputils.h" +#include "avctppacket.h" +#include "avctpPacketMgr.h" +#include "AvctpMessageParameters.h" + +#ifdef __FLOG_ACTIVE +_LIT8(KLogComponent, LOG_COMPONENT_AVCTP); +#endif + +#ifdef _DEBUG +PANICCATEGORY("avctpsap"); +#endif + +using namespace SymbianAvctp; + +/** +Avctp Sap Security policy +We don't need to check LocalServices as BluetoothSAP does that for us +*/ +_LIT_SECURITY_POLICY_S0(KAvctpSapSecurityPolicy, KDefaultSidforPids); + +/** Diagnostic string identifying this module when calling security policy checking methods, +in builds without platsec diagnostics this will be NULL */ +const char * const KAvctpSapSecurityPolicyDiagnostic = __PLATSEC_DIAGNOSTIC_STRING("Avctp Sap"); + +/** +Static AVCTP SAP factory function + + @internalComponent + @leave KErrNoMemory if the SAP object could not be allocated + @param aProt Lower protocol object + @return A pointer to the new SAP +*/ +CAvctpSap* CAvctpSap::NewL(CAvctpProtocol& aProt) + { + LOG_STATIC_FUNC + + CAvctpSap* sap = new(ELeave) CAvctpSap(aProt); + CleanupStack::PushL(sap); + sap->ConstructL(); + CleanupStack::Pop(sap); + return sap; + } + +/** +Second-phase construction of a SAP + +Set up the async. callback. + + @internalComponent +*/ +void CAvctpSap::ConstructL() + { + LOG_FUNC + + CBluetoothSAP::ConstructL(); + + TCallBack cb(SendAsyncCallBack, this); + iSendAsyncCallBack = new(ELeave) CAsyncCallBack(cb, EActiveHighPriority); + } + +/** +Constructor for an AVCTP SAP + + @internalComponent + @param aProt The AVCTP protocol object +*/ +CAvctpSap::CAvctpSap(CAvctpProtocol& aProtocol) + : CBluetoothSAP(aProtocol.SecMan(), aProtocol.CodMan()), + iProtocol(aProtocol), + iOutgoingSdus(), + iIncomingSdus(_FOFF(HAvctpIncomingSdu, iQueLink)), + iChannel(KAvctpInvalidChannel), + iIsInList(EFalse) + { + LOG_FUNC + // we set the channel because "0" is a real channel, so we want to know that iChannel is unset at time of construction + } + + +/** +Destructor for an AVCTP SAP + +Called when ESock deletes the SAP, when user app calls RSocket::Close() + + @internalAll +*/ +CAvctpSap::~CAvctpSap() + { + LOG_FUNC + + // Delete any remaining packets that the sap owns + TDblQueIter outgoingIter = iOutgoingSdus.Iter(); + while (outgoingIter) + { + delete outgoingIter++; // deques sdu + } + TDblQueIter incomingIter(iIncomingSdus); + while (incomingIter) + { + delete incomingIter++; // deques sdu + } + + iSendAsyncCallBack->Cancel(); + delete iSendAsyncCallBack; + } + +MSocketNotify& CAvctpSap::Socket() const + { + LOG_FUNC + __ASSERT_DEBUG(iSocket, Panic(EAvctpDataSAPNullSocket)); + return *iSocket; + } + +/****************************************************************************/ +/* Implementation of CControlledServProvider methods */ +/****************************************************************************/ + + +/** +Set the local name to anAddr. + +@internalAll +@param anAddr The address with the PID +@return KErrNone if successful +*/ +TInt CAvctpSap::SetLocalName(TSockAddr& aAddr) + { + LOG_FUNC + + TAvctpSockAddr avctpAddr = TAvctpSockAddr::Cast(aAddr); + iChannel = avctpAddr.Channel(); + __ASSERT_DEBUG(iChannel == KAvctpPrimaryChannel || iChannel == KAvctpSecondaryChannel, Panic(EAvctpInvalidChannel)); + + TInt err = KErrNone; + if (iChannel != KAvctpPrimaryChannel && iChannel != KAvctpSecondaryChannel) + { + err = KErrInvalidChannel; + } + if (err == KErrNone) + { + err = SetPid(avctpAddr.Pid()); + } + if (err == KErrNone) + { + err = iProtocol.AddSap(*this); + } + return err; + } + +/** +Read the Local Name into anAddr. + +AVCTP knows only the PID on which the device is +registered but this is OK since that's all they want. + + @internalAll + @param anAddr The address to read into +*/ +void CAvctpSap::LocalName(TSockAddr& aAddr) const + { + LOG_FUNC + + // Copy iPid into TSockAddr and return + TAvctpSockAddr avctpAddr(aAddr); + avctpAddr.SetChannel(iChannel); + avctpAddr.SetPid(iPid); + aAddr = avctpAddr; + } + +/** +Read the remote name into anAddr. + +AVCTP knows only the PID on which the device is +registered but this is OK since that's all they want. + + @internalAll + @param anAddr The address to read into +*/ +void CAvctpSap::RemName(TSockAddr& aAddr) const + { + LOG_FUNC + // no BT Addr to fill on for data saps :-) + TAvctpSockAddr avctpAddr(aAddr); + avctpAddr.SetPid(iPid); + aAddr = avctpAddr; // Convert back + } + +/** +Set the remote name to anAddr. + + @internalAll + @param anAddr The address to set +*/ +TInt CAvctpSap::SetRemName(TSockAddr& aAddr) + { + LOG_FUNC + + // Copy this over + TAvctpSockAddr avctpAddr = TAvctpSockAddr::Cast(aAddr); + + iChannel = avctpAddr.Channel(); + __ASSERT_DEBUG(iChannel == KAvctpPrimaryChannel || iChannel == KAvctpSecondaryChannel, Panic(EAvctpInvalidChannel)); + + TInt err = KErrNone; + if (iChannel != KAvctpPrimaryChannel && iChannel != KAvctpSecondaryChannel) + { + err = KErrInvalidChannel; + } + + if (err == KErrNone) + { + err = SetPid(avctpAddr.Pid()); + } + + return err; + } + +/** +Auto bind from ESock. + +We're not expecting this as a connectionless protocol so panic + + @internalAll +*/ +void CAvctpSap::AutoBind() + { + LOG_FUNC + __PANIC_UNEXPECTED_CALL + } + +/** +Get a socket option. + +AVCTP doesn't support socket options, so fail it if it's for AVCTP. + + @internalAll + @param aLevel The socket option level + @param aName The socket option name + @param aOption The socket option data +*/ +TInt CAvctpSap::GetOption(TUint /*aLevel*/, TUint /*aName*/, TDes8& /*aOption*/) const + { + LOG_FUNC + + return KErrNotSupported; + } + +/** +Set a socket option. (BluetoothSAP version) + +AVCTP doesn't support socket options, so fail it if it's for AVCTP. + + @internalAll + @param aLevel The socket option level + @param aName The socket option name + @param aOption The socket option data +*/ +TInt CAvctpSap::SAPSetOption(TUint /*aLevel*/, TUint /*aName*/, const TDesC8& /*aOption*/) + { + LOG_FUNC + return KErrNotSupported; + } + + +// start-up/Shutdown + +// NB: All ActiveOpen / PassiveOpen / Start calls require a connection- +// oriented socket, so we just panic! + +/** +Active open an AVCTP socket... + +Wait a minute! AVCTP is connectionless, so panic! + + @internalAll +*/ +void CAvctpSap::ActiveOpen() + { + LOG_FUNC + Panic(EActiveOpenNotSupported); // control sap does this + } + +/** +Active open an AVCTP socket (data overload)... + +Wait a minute! AVCTP is connectionless, so panic! + + @internalAll + @param aConnectionData Data to send on connection +*/ +void CAvctpSap::ActiveOpen(const TDesC8& /*aConnectionData*/) + { + LOG_FUNC + Panic(EActiveOpenNotSupported); + } + +/** +Passive open an AVCTP socket... + +Wait a minute, passive opens are only for connection-oriented protocols too! Panic. + + @internalAll + @param aQueSize How many connections to complete before they are Accept()ed... + @return KErrNotSupported, obviously... +*/ +TInt CAvctpSap::PassiveOpen(TUint /*aQueSize*/) + { + LOG_FUNC + Panic(EPassiveOpenNotSupported); + return KErrNotSupported; + } + +/** +Passive open an AVCTP socket (data overload)... + +Wait a minute, passive opens are only for connection-oriented protocols too! Panic. + + @internalAll + @param aQueSize How many connections to complete before they are Accept()ed... + @param aConnectionData Data to send on connection + @return KErrNotSupported, obviously... +*/ +TInt CAvctpSap::PassiveOpen(TUint /*aQueSize*/, const TDesC8& /*aConnectionData*/) + { + LOG_FUNC + Panic(EPassiveOpenNotSupported); + return KErrNotSupported; + } + +/** +We ignore this. + + @internalAll +*/ +void CAvctpSap::Start() + { + LOG_FUNC + } + +/** +Close the SAP down. + + @internalAll + @param aCloseType How fast we're going down +*/ +void CAvctpSap::Shutdown(TCloseType aCloseType) + { + LOG_FUNC + LOG1(_L("option = %d"), aCloseType); + + if (aCloseType != EImmediate) + { + // We need to remember we've been asked to close so: + iIsClosing = ETrue; + + // Normal Shutdown - ESOCK will wait for us to say we can end + // Esock is no longer interested in our incoming packets so delete them + TDblQueIter incomingIter(iIncomingSdus); + while (incomingIter) + { + delete incomingIter++; // deques sdu + } + + CheckForCanClose(); + } + else + { + // Note: Esock doesn't require us to call CanClose on an EImmediate Shutdown + iClosed = ETrue; + + iProtocol.RemoveSap(*this); + } + } + +/** +Close the SAP down (data overload). + + @internalAll + @param aCloseType How fast we're going down + @param aDisconnectionData Data to send on disconnect +*/ +void CAvctpSap::Shutdown(TCloseType /*aCloseType*/, const TDesC8& /*aDisconnectionData*/) + { + LOG_FUNC + Panic(EDisconnectDataNotSupported); + } + +/** +Send some data. + +Just pass this over to our state. + + @internalAll + @param aDesc The data to send + @param aOptions The message parameters + @param aAddr The address to send to + @return 0 if the write failed or 1 if we took ownership of the data +*/ +TInt CAvctpSap::Write(RMBufChain& aData, TUint aOptions, TSockAddr* aAddr) + { + LOG_FUNC + + // We've been asked to close so esock shouldn't be giving us any more data + __ASSERT_ALWAYS(!iIsClosing, Panic(EUnexpectedEsockEvent)); + + TUint ret = 0; + + if (iOutgoingSdus.Count() < KSapOutboundQHighMark) + { + TAvctpMessageParameters parameters(aOptions); + TAvctpNormalHeaderInfo headerInfo = TAvctpNormalHeaderInfo( + parameters.iTransaction, + ENormalPkt, + parameters.Type(), + ETrue, // hasValidPid + Pid()); + + HAvctpOutgoingSdu* sdu = NULL; + + TRAPD(err,sdu = HAvctpOutgoingSdu::NewL(headerInfo, + TAvctpSockAddr(*aAddr).BTAddr(), + aData)); + if (err == KErrNone) + { + iOutgoingSdus.Insert(*sdu); + StartSendAsyncCallBack(); + ret = 1; //one sdu + } + } + + if (!ret) + { + iSendBlocked = ETrue; + iPendingSendAddress = TAvctpSockAddr::Cast(*aAddr).BTAddr(); + } + return ret; + } + +/** +This function is called on the Sap by Esock when it wants to receive the +data we told it about in NewData upcalls from us. + +Rather than letting Esock call the descriptor overload, this function +deals with the request directly for two reasons. The intention is to +implement AVCTP in terms of MBufs in future but also because the descriptor +overload doesn't get called correctly by esock since it doesn't know how +long our datagrams will be and so presents us with a TDes8 which is too +short. + + @internalAll + @param aData The data to be received + @param aOptions The message parameters + @param aAddr The address the data is received from + @return the error associated with the read. Could be KErrNoMBufs (a likely case) in which case esock + would attempt an allocation for us and would then try again. If it's some other error then it's up to us + to tell esock when to retry (by signalling with NewData(0)). If it was some error that we couldn't + handle at all then we should have set the error upon the socket +*/ +TInt CAvctpSap::GetData(RMBufChain& aData, TUint /*aLength*/, TUint /*aOptions*/, TSockAddr* aAddr) + { + LOG_FUNC + + // We've been asked to close so esock shouldn't be asking us for more data + __ASSERT_ALWAYS(!iIsClosing, Panic(EUnexpectedEsockEvent)); + __ASSERT_DEBUG(!iClosed, Panic(EAvctpSapClosed)); + + HAvctpIncomingSdu* sdu = iIncomingSdus.First(); + __ASSERT_ALWAYS(sdu, Panic(ENullPacket)); + + aData.Assign(const_cast(sdu->Data())); + + if (aAddr) + { + TAvctpSockAddr avctpAddr = TAvctpSockAddr::Cast(*aAddr); + avctpAddr.SetPid(iPid); + avctpAddr.SetBTAddr(sdu->BTAddr()); + avctpAddr.SetChannel(iChannel); + (*aAddr) = avctpAddr; // Convert back + } + delete sdu; // deques pkt in ~HAvctpIncomingSdu + + return KErrNone; + } + +/** +Perform an Ioctl. + + @internalAll + @param aLevel The Ioctl level + @param aName The Ioctl name + @param aOption The Ioctl data +*/ +void CAvctpSap::Ioctl(TUint /*aLevel*/, TUint /*aName*/, TDes8* /*aOption*/) + { + LOG_FUNC + Socket().Error(KErrNotSupported, MSocketNotify::EErrorIoctl); + __ASSERT_DEBUG(NULL, Panic(EIoctlsNotSupported)); + } + +/** +Cancel an Ioctl. + +Just pass it to the state machine. + + @internalAll + @param aLevel The Ioctl level + @param aName The Ioctl name +*/ +void CAvctpSap::CancelIoctl(TUint /*aLevel*/, TUint /*aName*/) + { + LOG_FUNC + __ASSERT_DEBUG(NULL, Panic(EIoctlsNotSupported)); + } + +TInt CAvctpSap::SecurityCheck(MProvdSecurityChecker* aSecurityChecker) + { + LOG_FUNC + + // Store for later use when asked to bind to a particular PID. + iSecurityChecker = aSecurityChecker; + return KErrNone; + } + +/************************************************************************ + + Events interface (between muxer and Sap via Protocol) + + ************************************************************************/ + +/** +There is new data from the muxer for us. +We take ownership if the packet is suitable for us otherwise ignore + + @internalComponent + @param aData The new data from the muxer + @param aMux The mux this data came from + @return ETrue if we took ownership of the data, or EFalse otherwise +*/ + +TBool CAvctpSap::NewData(HAvctpIncomingSdu* aSdu, TInt aChannel) + { + LOG_FUNC + LOG1(_L("New Sdu 0x%08x"), aSdu); + + __ASSERT_DEBUG(!iClosed, Panic(EAvctpSapClosed)); + + TBool acceptsPdu = EFalse; + + if (SymbianAvctp::Pid(CAvctpPacket::GetHeader(aSdu->Data())) == Pid() + && aChannel == iChannel) + { + if (!iIsClosing) + { + iIncomingSdus.AddLast(*aSdu); + Socket().NewData(1); + acceptsPdu = ETrue; + } + } + + return acceptsPdu; + } + + + + +TBool CAvctpSap::HasDataFor(const TBTDevAddr& aRemoteAddr) + { + LOG_FUNC + + TBool ans = EFalse; + + TDblQueIter iter = iOutgoingSdus.Iter(); + HAvctpOutgoingSdu* sdu; + + while (iter) + { + sdu = iter++; + if (sdu->BTAddr() == aRemoteAddr) + { + ans = ETrue; + break; + } + } + + LOG1(_L("result %d"), ans); + return ans; + } + +/** +This function is called when a muxer goes down and is used to get rid of +any packets that are now stale. If a packet remains in the system after +the muxer to it's remote device goes down, it'll cause a reconnection +attempt which we don't want. + +We also need to delete any partial incoming packets from this muxer since +they obviously won't be competed. +*/ +void CAvctpSap::MuxerDown(const TBTDevAddr& aRemoteAddr) + { + LOG_FUNC + + HAvctpOutgoingSdu* sdu; + + TDblQueIter iter = iOutgoingSdus.Iter(); + while (iter) + { + sdu = iter++; + if (sdu->BTAddr() == aRemoteAddr) + { + delete sdu; + } + } + + if (!iIsClosing && iSendBlocked && iPendingSendAddress == aRemoteAddr) + { + Socket().Error(KErrDisconnected, MSocketNotify::EErrorSend); + } + + CheckForCanSend(); + CheckForCanClose(); + } + +/****************************************************************************/ +/* Implementation of private methods */ +/****************************************************************************/ + +/** +This function checks the Secure ID of the client process that is trying to +use an RAvctp object on aPid. Currently all PIDs are checked against the +default SID provided in avctpconstants.h + +If a licensee wanted to change behaviour, they would have to branch the stack +to do so. This is obviously undesirable though they should question why they +aren't using the RemCon server in the first place. + +This function should only be called once when the Pid of the Sap is first set. + +TODO: provide a resource / SPD file matching PIDs <-> SIDs, using KRemConSrvUid as the default SID +*/ +TInt CAvctpSap::CheckPidAllowed(TPid /*aPid*/) + { + LOG_FUNC + // iSecurityChecker should've been provided in SecurityCheck by now + ASSERT_DEBUG(iSecurityChecker); + TBool ret = iSecurityChecker->CheckPolicy(KAvctpSapSecurityPolicy, KAvctpSapSecurityPolicyDiagnostic); + iSecurityChecker = NULL; // we're finished with it and it's not owned by us + return ret; + } + +void CAvctpSap::CheckForCanSend() + { + LOG_FUNC + + if (!iIsClosing && iOutgoingSdus.Count() < KSapOutboundQLowMark) + { + Socket().CanSend(); // Okay to call at any time from Esock's point of view + iSendBlocked = EFalse; + } + } + +void CAvctpSap::CheckForCanClose() + { + LOG_FUNC + + if ((iIsClosing && iOutgoingSdus.Count() == 0) && !iClosed) + { + iClosed = ETrue; // Ensure CanClose() is only called once, otherwise it hits an assert + iProtocol.RemoveSap(*this); + Socket().CanClose(); + } + } + +/** +Asynchronous callback function to deal with actually sending stuff to +the muxers for delivery. + +Attempts to send all packets that it can from the iOutgoingSdus Q + + @internalComponent + @param aSap The SAP being called back + @return EFalse - i.e. the callback should not be called again +*/ +/*static*/ TInt CAvctpSap::SendAsyncCallBack(TAny* aSap) + { + LOG_STATIC_FUNC + LOG1(_L("for Sap: 0x%08x"), aSap); + + CAvctpSap& sap = *static_cast(aSap); + + TDblQueIter iter = sap.iOutgoingSdus.Iter(); + HAvctpOutgoingSdu* sdu = NULL; + MAvctpSDUSender* sduSender = NULL; + + while (iter) + { + sdu = iter++; + const TBTDevAddr& addr = sdu->BTAddr(); + // Get a SDU Sender (tied to transport) if we don't have one yet + if (!sduSender) + { + sduSender = sap.iProtocol.GetSDUSender(addr, sap.iChannel); + } + + if (!sduSender) // means the muxer has not been created + { + delete sdu; // I cannot send it due to a muxer creation failure + continue; + } + + // Try to send the sdu + if (sduSender->IsClearToSend(sap.iChannel)) + { + // Note if the write succeeds the ownership of the sdu is transferred and the sdu will + // be deleted which will deque the sdu from the sap's outboundQ. If the write fails + // the sap retains ownership and the packet will remain on the outboundQ. + // Hence we don't need to check the return value + + (void)sduSender->Write(sdu, sap.iChannel); + } + else // may as well skip all sdus that are for the same transport + { + while (iter) + { + sdu = iter++; + if (sdu->BTAddr() != addr) + { + iter--; // don't want to skip this sdu when we go round the outer loop again + sduSender = NULL; // we're finished with this muxer + break; // inner while + } + } + } + } + + sap.CheckForCanSend(); + sap.CheckForCanClose(); + + return EFalse; + } + +/** +Set the PID for this SAP & add us to the Protocol's Q + + @internalComponent + @param aPid The SAP's PID + @return KErrInUse if there is already a Sap on aPid, otherwise KErrNone +*/ +TInt CAvctpSap::SetPid(TPid aPid) + { + LOG_FUNC + LOG1(_L("to PID: 0x%x"), aPid); + + __ASSERT_DEBUG(iPid == 0 || iPid == aPid, Panic(ESapAlreadyBound)); + + TInt err = KErrNone; + if (iPid == 0 || iPid == aPid) + { + err = CheckPidAllowed(aPid); + if (err == KErrNone) + { + iPid = aPid; + } + } + else + { + err = KErrSapAlreadyBound; + } + + return err; + } + + +// Pure virtuals From MSocketNotify +// It's not used in favour of the public overloaded NewData() +void CAvctpSap::NewData(TUint /*aCount*/) + { + LOG_FUNC + __ASSERT_DEBUG(NULL, Panic(ENewDataNotSupported)); + } + +/** +Notification from a muxer that we can send again. +*/ +void CAvctpSap::CanSend() + { + LOG_FUNC + StartSendAsyncCallBack(); + } + +// Not implemented because CAvctpSap is a connectionless sap +void CAvctpSap::ConnectComplete() + { + LOG_FUNC + } + +// Not implemented because CAvctpSap is a connectionless sap +void CAvctpSap::ConnectComplete(const TDesC8& /*aConnectData*/) + { + LOG_FUNC + } + +// Not implemented because CAvctpSap is a connectionless sap +void CAvctpSap::ConnectComplete(CServProviderBase& /*aSSP*/) + { + LOG_FUNC + } + +// Not implemented because CAvctpSap is a connectionless sap +void CAvctpSap::ConnectComplete(CServProviderBase& /*aSSP*/,const TDesC8& /*aConnectData*/) + { + LOG_FUNC + } + +// Not implemented because CAvctpSap is a connectionless sap +void CAvctpSap::CanClose(TDelete /*aDelete*/) + { + LOG_FUNC + } + +// Not implemented because CAvctpSap is a connectionless sap +void CAvctpSap::CanClose(const TDesC8& /*aDisconnectData*/,TDelete /*aDelete*/) + { + LOG_FUNC + } + +void CAvctpSap::Error(TInt aError, TUint aOperationMask) + { + iSocket->Error(aError, aOperationMask); + } + +// Not implemented because CAvctpSap is a connectionless sap +void CAvctpSap::Disconnect() + { + LOG_FUNC + } + +// Not implemented because CAvctpSap is a connectionless sap +void CAvctpSap::Disconnect(TDesC8& /*aDisconnectData*/) + { + LOG_FUNC + } + +// Not implemented because CAvctpSap is a connectionless sap +void CAvctpSap::IoctlComplete(TDesC8* /*aBuf*/) + { + LOG_FUNC + // if we forward ioctls (NO!) then we'd expect this + } + +void CAvctpSap::NoBearer(const TDesC8& /*aConnectionInfo*/) {} +void CAvctpSap::Bearer(const TDesC8& /*aConnectionInfo*/) {} + +CAvctpControlSAP* CAvctpControlSAP::NewL(CAvctpProtocol& aProtocol) + { + LOG_STATIC_FUNC + return new (ELeave) CAvctpControlSAP(aProtocol); + } + +CAvctpControlSAP::CAvctpControlSAP(CAvctpProtocol& aProtocol) +:CBluetoothSAP(aProtocol.SecMan(), aProtocol.CodMan()) +,iProtocol(aProtocol), +iIndicationQueue(_FOFF(HQueuedIndication, iLink)) + { + LOG_FUNC + } + +/** + This method is called by CAvctpControlSAP::Ioctl() when the aName is an active request + (i.e. an attach request, a detach request, for both channels). In this case we want to set + an oustanding ioctl to be completed later. And it has priority on other incoming events, so we + want it to be served first. We can have only one oustanding ioctl. This method creates it and put + it at first in the queue. + **/ +void CAvctpControlSAP::SetOutstandingIndication(const TControlIoctlMessage& aMessage) + { + LOG_FUNC + HQueuedIndication* ind = HQueuedIndication::New(aMessage); + if (ind) + { + iIndicationQueue.AddFirst(*ind); + } + else + { + Error(KErrNoMemory); + } + } + +void CAvctpControlSAP::CompleteIoctlWithError(const TControlIoctlMessage& aMessage) + { + LOG_FUNC + TPckgC pck(aMessage); + iSocket->IoctlComplete(&pck); + ClearIoctl(); + } + +/** + In some cases the Ioctl (i.e. an AttachAgreement) does not wait for a reply. So, + AvctpRemoteDevices, after sending an AgreeAttachment, cancels the ioctl and submit a new Listen one. + Hence, we need to queued the error completion, and when the listening ioctl comes we complete it with + the queued error. It is queued with priority as we want it to be the first completed. + */ +void CAvctpControlSAP::QueueErrorIoctlComplete(const TControlIoctlMessage& aMessage) + { + SetOutstandingIndication(aMessage); + } + +/** + Dequeues an iocl and if it is an oustanding iocl (and not an incoming indication) then it + completes it with the error code passed on. + In some cases the outstanding ioctl will be already completed and hence not queued anymore. + for example, if we are trying to attach to ourself (so the address passed in the attach request + is the local device's address) the transport immediately notify an error up that completes the + outstanding ioctl. Then the transport is destroyed, and so iProtocol.ProvideTransport returns an + error. So this method is called but it won't find the outstanding ioctl because already completed + but NotifyError(). In this case we do nothing as the client has been already notified. + */ +void CAvctpControlSAP::CompleteQueuedIoctlWithError(TInt aError) + { + LOG_FUNC + if (!iIndicationQueue.IsEmpty()) + { + HQueuedIndication* ind = iIndicationQueue.First(); + SymbianAvctp::TControlIoctls ioctl = ind->Indication().iIoctl; + + if (ioctl == EPrimaryChannelAttachToTransport || + ioctl == ESecondaryChannelAttachToTransport || + ioctl == EPrimaryChannelDetachFromTransport || + ioctl == ESecondaryChannelDetachFromTransport) + { + iIndicationQueue.Remove(*ind); + ind->Indication().iError = aError; + CompleteIoctlWithError(ind->Indication()); + delete ind; + ind = NULL; + } + + } + } + +/** + This method process the queue and it complete the ioctl. + In some cases it changes the indication type (i.e. from ELinkUp to EAttachIndicate). + If the ioctl type is EError it doesn't do anything because we want the error to be propagated to + the client. + */ +void CAvctpControlSAP::ProcessIoctlQueue() + { + LOG_FUNC + if (!iIndicationQueue.IsEmpty()) + { + HQueuedIndication* ind = iIndicationQueue.First(); + iIndicationQueue.Remove(*ind); + + switch(ind->Indication().iIoctl) + { + case ELinkUp: + ind->Indication().iIoctl = EAttachIndicate; + break; + case ELinkDown: + ind->Indication().iIoctl = EDetachIndicate; + break; + default: + break; // propagate it + } + + + TPckgC pck(ind->Indication()); + iSocket->IoctlComplete(&pck); + delete ind; + ClearIoctl(); + } + } + +TBool CAvctpControlSAP::IsActiveRequest(TUint aName) + { + LOG_FUNC + return (aName == EPrimaryChannelAttachToTransport || + aName == ESecondaryChannelAttachToTransport || + aName == EPrimaryChannelDetachFromTransport || + aName == ESecondaryChannelDetachFromTransport); + } + +void CAvctpControlSAP::Ioctl(TUint aLevel,TUint aName, TDes8* aOption) + { + LOG_FUNC + + TInt err = KErrNone; + + TControlIoctlMessage msg; + TPckg pck(msg); + pck.Copy(*aOption); + + TBTDevAddr addr = pck().iAddr.BTAddr(); + TPid pid = pck().iAddr.Pid(); + TInt channel = pck().iAddr.Channel(); + + if (aLevel == KSolBtAVCTP) + { + iIoctlLevel = aLevel; + iIoctlName = aName; + + if (IsActiveRequest(aName)) + { + SetOutstandingIndication(pck()); + } + + switch (aName) + { + case EPrimaryChannelAttachToTransport: + { + __ASSERT_DEBUG(channel == KAvctpPrimaryChannel, Panic(EAvctpInvalidChannelIoctl)); + // we should complete the ioctl with an error if this function returns an error + if ((err = iProtocol.ProvideTransport(addr, pid, *this)) != KErrNone) + { + CompleteQueuedIoctlWithError(err); + } + } + break; + case EAwaitProvidedTransport: + { + __ASSERT_DEBUG(channel == KAvctpPrimaryChannel, Panic(EAvctpInvalidChannelIoctl)); + iProtocol.AwaitTransport(pid, *this); + } + break; + case EPrimaryChannelAgreeAttachment: + { + __ASSERT_DEBUG(channel == KAvctpPrimaryChannel, Panic(EAvctpInvalidChannelIoctl)); + LOG(_L("EPrimaryChannelAgreeAttachment, then reset aName")) + if ((err = iProtocol.PrimaryChannelAgreeAttachment(pid, addr)) == KErrNone) + { + ClearIoctl(); // this ioctl is not expecting an answer + iIoctlName = EUndefinedIoctl; // just used to not execute the iIoctlName update + } + else + { + pck().iError = err; + QueueErrorIoctlComplete(pck()); + } + } + break; + case ESecondaryChannelAttachToTransport: + { + __ASSERT_DEBUG(channel == KAvctpSecondaryChannel, Panic(EAvctpInvalidChannelIoctl)); + // note this SAP is only managing the transport: the demuxing of control and second AVCTP data is handled + // by having different data SAPs for each traffic flow + // rare that we'd actively connect the second channel, but using at least for test purposes + + // should return the error through the confirm completion than via Error()? + if ((err = iProtocol.ActiveExtendTransport(addr, pid, *this)) != KErrNone) + { + CompleteQueuedIoctlWithError(err); + } + } + break; + case ESecondaryChannelAgreeAttachment: + { + __ASSERT_DEBUG(channel == KAvctpSecondaryChannel, Panic(EAvctpInvalidChannelIoctl)); + if ((err = iProtocol.SecondaryChannelAgreeAttachment(pid, addr)) == KErrNone) + { + ClearIoctl(); // this ioctl is not expecting an answer + iIoctlName = EUndefinedIoctl; // just used to not execute the iIoctlName update + } + else + { + pck().iError = err; + QueueErrorIoctlComplete(pck()); + } + } + break; + case EAwaitExtendedTransport: + { + __ASSERT_DEBUG(channel == KAvctpSecondaryChannel, Panic(EAvctpInvalidChannelIoctl)); + iProtocol.AwaitForExtendedTransport(pid, *this); + } + break; + case EPrimaryChannelRefuseAttach: + { + __ASSERT_DEBUG(channel == KAvctpPrimaryChannel, Panic(EAvctpInvalidChannelIoctl)); + iProtocol.PrimaryChannelRefuseAttach(addr, pid); + } + break; + case ESecondaryChannelRefuseAttach: + { + __ASSERT_DEBUG(channel == KAvctpSecondaryChannel, Panic(EAvctpInvalidChannelIoctl)); + iProtocol.SecondaryChannelRefuseAttach(addr, pid); + } + break; + case EPrimaryChannelDetachFromTransport: + { + __ASSERT_DEBUG(channel == KAvctpPrimaryChannel, Panic(EAvctpInvalidChannelIoctl)); + if ((err = iProtocol.ReleaseTransport(addr, pid)) != KErrNone) + { + CompleteQueuedIoctlWithError(err); + } + } + break; + case ESecondaryChannelDetachFromTransport: + { + __ASSERT_DEBUG(channel == KAvctpSecondaryChannel, Panic(EAvctpInvalidChannelIoctl)); + if ((err = iProtocol.ReleaseExtendedTransport(addr, pid)) != KErrNone) + { + CompleteQueuedIoctlWithError(err); + } + } + break; + default: + { + iSocket->Error(KErrNotSupported, MSocketNotify::EErrorIoctl); + } + break; + } + + if (err == KErrNone && iIoctlName != EUndefinedIoctl) + { + if (!IsActiveRequest(aName)) + { + ProcessIoctlQueue(); + } + } + else + { + LOG(_L("No outstanding ioctl")); + } + } + } + +void CAvctpControlSAP::CancelIoctl(TUint/* aLevel*/,TUint /*aName*/) + { + LOG_FUNC + // do nothing for now, though should in future inform protocol that a transport is not needed + HQueuedIndication* ind = iIndicationQueue.First(); + if (ind && IsActiveRequest(ind->Indication().iIoctl)) + { + iIndicationQueue.Remove(*ind); + delete ind; + ind = NULL; + } + ClearIoctl(); + } + +TInt CAvctpControlSAP::SetRemName(TSockAddr& /*aAddr*/) + { + LOG_FUNC + __ASSERT_DEBUG(EFalse, Panic(EAvctpControlSAPOpetationNotSupported)); + return KErrNone; + } + +// CAvctpControlSAP inherited from CBluetoothSAP to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +void CAvctpControlSAP::ActiveOpen() + { + LOG_FUNC + __ASSERT_DEBUG(EFalse, Panic(EAvctpControlSAPOpetationNotSupported)); + } + +// CAvctpControlSAP inherited from CBluetoothSAP to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +TInt CAvctpControlSAP::PassiveOpen(TUint /*aQueueSize*/) + { + LOG_FUNC + __ASSERT_DEBUG(EFalse, Panic(EAvctpControlSAPOpetationNotSupported)); + return KErrNotSupported; + } + +// CAvctpControlSAP inherited from CBluetoothSAP to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +void CAvctpControlSAP::ActiveOpen(const TDesC8& /*aConnectionData*/) + { + LOG_FUNC + __ASSERT_DEBUG(EFalse, Panic(EAvctpControlSAPOpetationNotSupported)); + } + +// CAvctpControlSAP inherited from CBluetoothSAP to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +TInt CAvctpControlSAP::PassiveOpen(TUint /*aQueueSize*/,const TDesC8& /*aConnectionData*/) + { + LOG_FUNC + __ASSERT_DEBUG(EFalse, Panic(EAvctpControlSAPOpetationNotSupported)); + return KErrNotSupported; + } + + +// CAvctpControlSAP inherited from CBluetoothSAP to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented or left empty cause called by the framework +void CAvctpControlSAP::Start() + { + LOG_FUNC + } + +// CAvctpControlSAP inherited from CBluetoothSAP to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +void CAvctpControlSAP::LocalName(TSockAddr& /*aAddr*/) const + { + LOG_FUNC + } + +// CAvctpControlSAP inherited from CBluetoothSAP to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +TInt CAvctpControlSAP::SetLocalName(TSockAddr& /*aAddr*/) + { + LOG_FUNC + __ASSERT_DEBUG(EFalse, Panic(EAvctpControlSAPOpetationNotSupported)); + return KErrNotSupported; + } + +// CAvctpControlSAP inherited from CBluetoothSAP to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +void CAvctpControlSAP::RemName(TSockAddr& /*aAddr*/) const + { + LOG_FUNC + __ASSERT_DEBUG(EFalse, Panic(EAvctpControlSAPOpetationNotSupported)); + } + +TInt CAvctpControlSAP::GetOption(TUint aLevel,TUint aName, TDes8& aOption) const + { + LOG_FUNC + TInt err = KErrNotSupported; + + TOptionMessage msg; + TPckg pck(msg); + pck.Copy(aOption); + + TInt mtu; + if (aLevel == KSolBtAVCTP) + { + switch(aName) + { + case KAvctpBaseOutboundMTU: + { + err = iProtocol.GetChannelMtu(KAvctpPrimaryChannel, pck().iAddr, mtu); + break; + } + case KAvctpExtendOutboundMTU: + { + err = iProtocol.GetChannelMtu(KAvctpSecondaryChannel, pck().iAddr, mtu); + break; + } + default: + break; + }; + } + + if (err == KErrNone) + { + pck().iMtu = mtu; + } + aOption = pck; + return err; + } + +void CAvctpControlSAP::Shutdown(CServProviderBase::TCloseType aOption) + { + LOG_FUNC + + if (aOption != EImmediate) + { + + iSocket->CanClose(); + } + else + { + // If immediate don't need to call back + } + } + +// CAvctpControlSAP inherited from CBluetoothSAP to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +void CAvctpControlSAP::Shutdown(CServProviderBase::TCloseType /*aOption*/, const TDesC8& /*aDisconnectionData*/) + { + LOG_FUNC + __ASSERT_DEBUG(EFalse, Panic(EAvctpControlSAPOpetationNotSupported)); + } + +// CAvctpControlSAP inherited from CBluetoothSAP to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +void CAvctpControlSAP::AutoBind() + { + LOG_FUNC + __ASSERT_DEBUG(EFalse, Panic(EAvctpControlSAPOpetationNotSupported)); + } + +// CAvctpControlSAP inherited from CBluetoothSAP to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +TUint CAvctpControlSAP::Write(const TDesC8& /*aDesc*/,TUint /*aOptions*/, TSockAddr* /*aAddr*/) + { + LOG_FUNC + __ASSERT_DEBUG(EFalse, Panic(EAvctpControlSAPOpetationNotSupported)); + return 0; + } + +// CAvctpControlSAP inherited from CBluetoothSAP to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +TInt CAvctpControlSAP::GetData(RMBufChain& /*aData*/, TUint /*aLength*/, TUint /*aOptions*/, TSockAddr* /*aAddr*/) + { + LOG_FUNC + __ASSERT_DEBUG(EFalse, Panic(EAvctpControlSAPOpetationNotSupported)); + return KErrNotSupported; + } + +// CAvctpControlSAP inherited from CBluetoothSAP to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +TInt CAvctpControlSAP::SecurityCheck(MProvdSecurityChecker* /*aSecurityChecker*/) + { + LOG_FUNC + return KErrNone; + } + +// CAvctpControlSAP inherited from CBluetoothSAP to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +TInt CAvctpControlSAP::SAPSetOption(TUint /*aLevel*/,TUint /*aName*/,const TDesC8& /*aOption*/) + { + LOG_FUNC + __ASSERT_DEBUG(EFalse, Panic(EAvctpControlSAPOpetationNotSupported)); + return KErrNotSupported; + } + +// Pure virtuals From MSocketNotify + +// CAvctpControlSAP inherited from MSocketNotify to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +void CAvctpControlSAP::NewData(TUint /*aCount*/) + { + LOG_FUNC + __ASSERT_DEBUG(EFalse, Panic(EAvctpControlSAPOpetationNotSupported)); + } + +// CAvctpControlSAP inherited from MSocketNotify to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +void CAvctpControlSAP::CanSend() + { + LOG_FUNC + // the AVCTP Transport should not have given us tghis signal + // only data saps for this + __ASSERT_DEBUG(EFalse, Panic(EAvctpControlSAPOpetationNotSupported)); + } + +// CAvctpControlSAP inherited from MSocketNotify to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +void CAvctpControlSAP::ConnectComplete() + { + LOG_FUNC + __ASSERT_DEBUG(EFalse, Panic(EAvctpControlSAPOpetationNotSupported)); + } + +// CAvctpControlSAP inherited from MSocketNotify to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +void CAvctpControlSAP::ConnectComplete(const TDesC8& /*aConnectData*/) + { + LOG_FUNC + __ASSERT_DEBUG(EFalse, Panic(EAvctpControlSAPOpetationNotSupported)); + } + +// CAvctpControlSAP inherited from MSocketNotify to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +void CAvctpControlSAP::ConnectComplete(CServProviderBase& /*aSSP*/) + { + LOG_FUNC + __ASSERT_DEBUG(EFalse, Panic(EAvctpControlSAPOpetationNotSupported)); + } + +// CAvctpControlSAP inherited from MSocketNotify to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +void CAvctpControlSAP::ConnectComplete(CServProviderBase& /*aSSP*/,const TDesC8& /*aConnectData*/) + { + LOG_FUNC + __ASSERT_DEBUG(EFalse, Panic(EAvctpControlSAPOpetationNotSupported)); + } + +// CAvctpControlSAP inherited from MSocketNotify to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +void CAvctpControlSAP::CanClose(TDelete /*aDelete*/) + { + LOG_FUNC + __ASSERT_DEBUG(EFalse, Panic(EAvctpControlSAPOpetationNotSupported)); + } + +// CAvctpControlSAP inherited from MSocketNotify to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +void CAvctpControlSAP::CanClose(const TDesC8& /*aDisconnectData*/,TDelete /*aDelete*/) + { + LOG_FUNC + __ASSERT_DEBUG(EFalse, Panic(EAvctpControlSAPOpetationNotSupported)); + } + +void CAvctpControlSAP::Error(TInt aError, TUint aOperationMask) + { + LOG_FUNC + iSocket->Error(aError, aOperationMask); + } + +// CAvctpControlSAP inherited from MSocketNotify to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +void CAvctpControlSAP::Disconnect() + { + LOG_FUNC + __ASSERT_DEBUG(EFalse, Panic(EAvctpControlSAPOpetationNotSupported)); + } + +// CAvctpControlSAP inherited from MSocketNotify to take advantage of the socket paradigm, +// but it is used just as control channel between the client side and the server side. +// so, many socket functions are not implemented +void CAvctpControlSAP::Disconnect(TDesC8& /*aDisconnectData*/) + { + LOG_FUNC + __ASSERT_DEBUG(EFalse, Panic(EAvctpControlSAPOpetationNotSupported)); + } + +/*inline*/ void CAvctpControlSAP::ClearIoctl() + { + LOG_FUNC + iIoctlLevel = 0; + iIoctlName = 0; + } + +/** + Create an HQueuedIndication object with the aInd argument (incoming device address) + and put it at the end of the queue. It is used just for the incoming Ioctl complete + **/ +void CAvctpControlSAP::QueueIncomingIndication(const TControlIoctlMessage& aMessage) + { + LOG_FUNC + HQueuedIndication* ind = HQueuedIndication::New(aMessage); + if (ind) + { + LOG1(_L("Adding Indication to queue: "), aMessage.iIoctl); + iIndicationQueue.AddLast(*ind); + } + else + { + // OOM'd - better tell client...they've lost an indication + Error(KErrNoMemory); + } + } + +/** + This method is called to notify back something to the client. both remote indications and + confirmation for actions requested from the client + */ +void CAvctpControlSAP::IoctlComplete(TDesC8* aBuf) + { + LOG_FUNC + + TControlIoctlMessage msg; + TPckg pck(msg); + + pck.Copy(*aBuf); + + TBTDevAddr addr = pck().iAddr.BTAddr(); + + // we can have the following cases: + // 1) We are not waiting for a completion + // Action: queue the indication to be served later + // 2) The queue is not empty and there is an outstanding ioctl + // a) we had asked for a connection to a remote device + // I) the ioctl complete is from the device we had requested. + // II) this ioctl complete is from another device + // b) we were listening on an incoming connection + // 3) we were listening on an incoming connection + if (!iIoctlName) // case 1 + { + LOG(_L("iIoctlName is empty")); + QueueIncomingIndication(msg); + } + else if (!iIndicationQueue.IsEmpty() ) // case 2 + { + HQueuedIndication* ind = iIndicationQueue.First(); + + __ASSERT_DEBUG(addr != TBTDevAddr(0), Panic(EAvctpInvalidAddress)); + + if (ind->Indication().iAddr.BTAddr() == addr) // case 2aI + { + iIndicationQueue.Remove(*ind); + switch(ind->Indication().iIoctl) + { + case EPrimaryChannelAttachToTransport: + case ESecondaryChannelAttachToTransport: + __ASSERT_DEBUG(pck().iIoctl == ELinkUp || pck().iIoctl == EAttachConfirm || pck().iIoctl == EError, Panic(EAvctpUnexpectedIoctlCompletion)); + pck().iIoctl = EAttachConfirm; + break; + case EPrimaryChannelDetachFromTransport: + case ESecondaryChannelDetachFromTransport: + __ASSERT_DEBUG(pck().iIoctl == EDetachConfirm, Panic(EAvctpUnexpectedIoctlCompletion)); + break; // do nothing the event is the same that goes back + default: + __ASSERT_DEBUG(EFalse, Panic(EAvctpUnexpectedIoctlCompletion)); + break; + } + iSocket->IoctlComplete(&pck); + delete ind; + } + else // case 2aII + { + LOG(_L("iIoctlName complete is for another device")); + QueueIncomingIndication(msg); + } + } + else // case 3 + { + // tell client straight away + if (iSocket) + { + TControlIoctls ioctlValue = pck().iIoctl; + + switch(pck().iIoctl) + { + case ELinkUp: + pck().iIoctl = EAttachIndicate; // notify an indication + break; + case ELinkDown: + pck().iIoctl = EDetachIndicate; // notify an indication + break; + default: + break; // pass it as it is + } + iSocket->IoctlComplete(&pck); + } + } + ClearIoctl(); + } + +HQueuedIndication* HQueuedIndication::New(const TControlIoctlMessage& aMessage) + { + LOG_STATIC_FUNC + HQueuedIndication* ind = new HQueuedIndication; + if (ind) + { + TRAPD(err, ind->ConstructL(aMessage)); + if (err) + { + delete ind; + ind=NULL; + } + } + return ind; + } + +void HQueuedIndication::ConstructL(const TControlIoctlMessage& aMessage) + { + LOG_FUNC + iMessage = new (ELeave) TControlIoctlMessage(aMessage); + } + +HQueuedIndication::~HQueuedIndication() + { + LOG_FUNC + delete iMessage; + } + +// Not used for avctp +void CAvctpControlSAP::NoBearer(const TDesC8& /*aConnectionInfo*/) {} +// Not used for avctp +void CAvctpControlSAP::Bearer(const TDesC8& /*aConnectionInfo*/) {} +