diff -r 000000000000 -r 29b1cd4cb562 bluetooth/btstack/linkmgr/hostresolver.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bluetooth/btstack/linkmgr/hostresolver.cpp Fri Jan 15 08:13:17 2010 +0200 @@ -0,0 +1,2745 @@ +// Copyright (c) 1999-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: +// L2CAP Host resolver. +// Implements Inquiry and Name lookup +// +// + +#include "hostresolver.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Command Events +#include +#include +#include + +// Command Complete Events +#include +#include +#include + +#include +#include +#include +#include +#include "debug.h" +#include "linkutil.h" +#include "physicallinksmanager.h" // to get baseband freed up for inquiries +#include "BTSec.h" + +//Diagnostic string for security check failures, in builds without platsec +//diagnostics this will be NULL. +const char* const KBT_HOSTRESOLVER_NAME_DIAG = __PLATSEC_DIAGNOSTIC_STRING("Bluetooth Host Resolver"); + +const static TUint KHostResDontBlock = 4; + +#ifdef __FLOG_ACTIVE +_LIT8(KLogComponent, LOG_COMPONENT_HOSTRESOLVER); +#endif + + + +// +// BT Inquiry Result record. +// + +CBTInqResultRecord::CBTInqResultRecord(const TBTDevAddr& aAddr) + : iCodec(iEntry.iExtendedInquiryResponse) + { + LOG_FUNC + iEntry.iBdaddr = aAddr; + // Default to page scan repetition mode R2 as recommended by Bray to give best + // chance of success for host resolver needs. + // Also allow other default values to be entered here + iEntry.iPageScanRepetitionMode = KDefaultBluetoothPageScanRepMode; + iEntry.iPageScanMode = KDefaultBluetoothPageScanMode; + iEntry.iClockOffset = KDefaultBluetoothClockOffset; + iEntry.iCoD = KDefaultBluetoothClassOfDevice; + + if(aAddr!=TBTDevAddr()) + { + iJuiceFromHCIMask = EBluetoothAddr; + } + //else 0 cos 'C' class + + SetNameValid(EFalse); + } + +CBTInqResultRecord::~CBTInqResultRecord() + { + LOG_FUNC + iIACs.Close(); + } + +inline void CBTInqResultRecord::Open() +/** + Some reference is now pointing at us. + Inc the ref count. +**/ + { + LOG_FUNC + ++iRefCount; + } + +void CBTInqResultRecord::Close() +/** + Reference closing. + Auto delete if this was the last reference pointing to us. +**/ + { + LOG_FUNC + --iRefCount; + if (iRefCount <= 0) + delete this; + } + +TInquiryLogEntry& CBTInqResultRecord::LogEntry() +/** + The inquiry result. + This is stored as it was returned from the HCI. + The host resolver will convert it into an InquirySockAddr + when a client actually wants to retreve it. +**/ + { + LOG_FUNC + return iEntry; + } + +inline void CBTInqResultRecord::SetName(const TDesC8& aName) + { + LOG_FUNC + iName = aName; + } + +inline const TDesC8& CBTInqResultRecord::Name() const +/** + The UTF name of the remote device. + This is stored as it comes in off the wire, the host resolver + converts to Unicode if & when a client actually requires it. +**/ + { + LOG_FUNC + return iName; + } + +inline TInt CBTInqResultRecord::IncFlushes() +/** + Age the result record + Returns it's new age. +**/ + { + LOG_FUNC + return ++iFlushes; + } + +inline TBool CBTInqResultRecord::IsNameValid() const + { + LOG_FUNC + return iNameLookupResultCode == KErrNone; + } + +inline void CBTInqResultRecord::SetNameValid(TBool aBool) + { + LOG_FUNC + if(aBool) + iNameLookupResultCode = KErrNone; + else + iNameLookupResultCode = 1; + } + +void CBTInqResultRecord::SetNameLookupResultCode(TInt aResultCode) + {// Only record error, if we don't have a valid name already + LOG_FUNC + if(!IsNameValid()) + iNameLookupResultCode = aResultCode; + } + +inline TBool CBTInqResultRecord::IsNameRequestPending() const + { + LOG_FUNC + return iNameStatus & ENamePending; + } + +inline void CBTInqResultRecord::SetNamePending(TBool aBool) + { + LOG_FUNC + if(aBool) + iNameStatus |= ENamePending; + else + iNameStatus &= ~ENamePending; + } + +inline TBool CBTInqResultRecord::IsNameRefreshRequested() const + { + LOG_FUNC + return iNameStatus & ENameRefreshRequested; + } + +inline void CBTInqResultRecord::SetNameRefreshRequested(TBool aBool) + { + LOG_FUNC + if(aBool) + iNameStatus |= ENameRefreshRequested; + else + iNameStatus &= ~ENameRefreshRequested; + } + +inline TBool CBTInqResultRecord::IsNameComplete() const + { + LOG_FUNC + return iNameStatus & ENameComplete; + } + +inline void CBTInqResultRecord::SetNameComplete(TBool aBool) + { + LOG_FUNC + if(aBool) + iNameStatus |= ENameComplete; + else + iNameStatus &= ~ENameComplete; + } + +inline TBool CBTInqResultRecord::IsExplicitNameRequest() const + { + LOG_FUNC + return iNameStatus & ENameExplicitRequest; + } + +inline void CBTInqResultRecord::SetExplicitNameRequest(TBool aBool) + { + LOG_FUNC + if(aBool) + iNameStatus |= ENameExplicitRequest; + else + iNameStatus &= ~ENameExplicitRequest; + } + +inline TBool CBTInqResultRecord::HaveNameLookupResult() const + { + LOG_FUNC + return iNameLookupResultCode <= KErrNone; + } + +inline TInt CBTInqResultRecord::NameLookupResultCode() const + {// Only valid if HaveNameLookupResult == ETrue + LOG_FUNC + __ASSERT_DEBUG(HaveNameLookupResult(), Panic(EBTNameLookupResultNotFound)); + return iNameLookupResultCode; + } + +inline TInt CBTInqResultRecord::NameLookupAttempts() const + { + LOG_FUNC + return iNameLookupAttempts; + } + +void CBTInqResultRecord::GetInquirySockAddr(TInquirySockAddr& aAddr) +/** + Read this result record out into a TInquirySockAddr. +**/ + { + LOG_FUNC + aAddr.SetBTAddr(LogEntry().iBdaddr); + + TBTDeviceClass cod(LogEntry().iCoD); + aAddr.SetMajorServiceClass(cod.MajorServiceClass()); + aAddr.SetMajorClassOfDevice(cod.MajorDeviceClass()); + aAddr.SetMinorClassOfDevice(cod.MinorDeviceClass()); + + TUint8 resultFlags = 0; + + if(iJuiceFromHCIMask & EBluetoothRssi) + { + TInquiryLogEntry* logEntry = &(LogEntry()); + resultFlags |= TInquirySockAddr::ERssiValid; + aAddr.SetRssi(static_cast(logEntry)->iRssi.RSSI()); + } + + aAddr.SetResultFlags(resultFlags); + } + +TInt CBTInqResultRecord::GetEir(TNameRecord& aNameRec, TBool aIgnoreCachedName) + { + // Reformat the EIR (i.e. remove 0s from EIR) for the constructor + LOG_FUNC + TInt error = KErrNone; + + aNameRec.iName.Zero(); + iCodec.Set(aNameRec); + if(IsEirPresent()) + { + TInquiryLogEntry* logEntry = &(LogEntry()); + iCodec.Copy(static_cast(logEntry)->iExtendedInquiryResponse); + } + // replace device name in eir with iName if there is a complete name present + if(IsNameValid() && IsNameComplete() && !aIgnoreCachedName) + { + iCodec.SetDeviceName(iName, ETrue); + } + return error; + } + +TInt CBTInqResultRecord::GetName(TNameRecord& aNameRec) + { + LOG_FUNC + TInt err = KErrNotFound; + if(HaveNameLookupResult()) + { + err = CnvUtfConverter::ConvertToUnicodeFromUtf8(aNameRec.iName, iName); + if (err >= KErrNone) + { + err = KErrNone; + if(!IsNameComplete()) + { + aNameRec.iFlags |= TNameRecord::EPartial; + } + else + { + aNameRec.iFlags &= ~(TNameRecord::EPartial); + } + } + } + else + { + err = KErrNone; // just following the old way of working. + aNameRec.iName.Zero(); + } + return err; + } + +TInt CBTInqResultRecord::AddIAC(TUint aIAC) +/** + Adds IAC into list of those this device has responded to + @return KErrNone IAC added without any bother + @return KErrAlreadyExists IAC already in list +**/ + { + LOG_FUNC + return iIACs.InsertInOrder(aIAC); + } + +inline TBool CBTInqResultRecord::HasRespondedToIAC(TUint aIAC) + { + LOG_FUNC + return (iIACs.FindInOrder(aIAC) >= 0); + } + +inline TInt CBTInqResultRecord::NumberOfIACsRespondedTo() + { + LOG_FUNC + return iIACs.Count(); + } + +inline void CBTInqResultRecord::ClearIACs() + { + LOG_FUNC + iIACs.Reset(); + } + +// +// Inquiry result reference. +// +CBTInqResultRef::CBTInqResultRef(CBTInqResultRef& aRef) + : iRecord(aRef.iRecord) + { + LOG_FUNC + iRecord.Open(); + } + +CBTInqResultRef::~CBTInqResultRef() + { + LOG_FUNC + iRecord.Close(); + iLink.Deque(); + } + +CBTInqResultRef::CBTInqResultRef(CBTInqResultRecord& aRec) + : iRecord(aRec) + { + LOG_FUNC + iRecord.Open(); + } + +inline CBTInqResultRecord& CBTInqResultRef::Result() const + { + LOG_FUNC + return iRecord; + } + + + +// +// BT Inquiry Result Set. +// + +CBTInqResultSet::CBTInqResultSet() + : iResultRefs(_FOFF(CBTInqResultRef, iLink)), + iNextRefIter(iResultRefs) + { + LOG_FUNC + } + +CBTInqResultSet::~CBTInqResultSet() + { + LOG_FUNC + Reset(); + } + +void CBTInqResultSet::Reset() + { + LOG_FUNC + while (!iResultRefs.IsEmpty()) + delete iResultRefs.First(); + ReturnToFirstResult(); + } + +CBTInqResultRef* CBTInqResultSet::Add(CBTInqResultRecord& aRec) + { + LOG_FUNC + CBTInqResultRef* ref = new CBTInqResultRef(aRec); + if (!ref) + { + return 0; + } + iResultRefs.AddLast(*ref); + if (!iNextRefIter) + { + iNextRefIter.SetToLast(); + } + return ref; + } + +CBTInqResultRef* CBTInqResultSet::FindEntry(const TBTDevAddr& aAddr) + { + LOG_FUNC + TResultQueIter iter(iResultRefs); + while (iter) + { + CBTInqResultRef* ref = iter++; + if (ref->Result().LogEntry().iBdaddr == aAddr) + { + return ref; + } + } + return 0; + } + +CBTInqResultRef* CBTInqResultSet::NextResult() + { + LOG_FUNC + return iCurrentResult = (iNextRefIter++); + } + +inline CBTInqResultRef* CBTInqResultSet::CurrentResult() + { + LOG_FUNC + return iCurrentResult; + } + +void CBTInqResultSet::ReturnToFirstResult() + { + LOG_FUNC + iNextRefIter.SetToFirst(); + iCurrentResult = 0; + } + +inline TBool CBTInqResultSet::IsEmpty() + { + LOG_FUNC + return iResultRefs.IsEmpty(); + } + +void CBTInqResultSet::MoveToback(CBTInqResultRef& aRef) + { + LOG_FUNC + if(iNextRefIter == &aRef) + NextResult(); + aRef.iLink.Deque(); + iResultRefs.AddLast(aRef); + if (!iNextRefIter) + { + iNextRefIter.SetToLast(); + } + } + +// +// BT Host resolver. +// + + +CBTHostResolver::CBTHostResolver(CBTInquiryMgr& aInquiryMgr) + : iInquiryMgr(aInquiryMgr), + iRequestState(EIdle), + iInquiryStatus(EInquiryReady) + { + LOG_FUNC +#ifdef _DEBUG + iInquiryMgr.IncrementHRCount(); +#endif + } + +CBTHostResolver::~CBTHostResolver() + { + LOG_FUNC + iLink.Deque(); + iInquiryMgr.DeletingHostResolver(); +#ifdef _DEBUG + iInquiryMgr.DecrementHRCount(); +#endif + } + +void CBTHostResolver::GetByName(TNameRecord& /*aName*/) + { + LOG_FUNC + iNotify->QueryComplete(KErrNotSupported); + } + +void CBTHostResolver::GetByAddress(TNameRecord& aName) + { + LOG_FUNC + __ASSERT_DEBUG(iRequestState == EIdle, Panic(EHostResolverTwoRequests)); + iNameRecord = &aName; + iNameRecord->iName.Zero(); + TInquirySockAddr& sa = TInquirySockAddr::Cast(aName.iAddr); + + LOG1(_L("Host Resolver\tAction = %d"),sa.Action()); + + if(sa.Action() & KHostResCache) + //Complete immediately with info if available! + { + TInt err = KErrNotFound; + CBTInqResultRef* ref = iInquiryMgr.FindExistingCacheEntry(sa.BTAddr()); + if (ref) + {// Got a result to send up + CBTInqResultRecord& rec = ref->Result(); + rec.GetInquirySockAddr(sa); //Put BT address, CoD etc into 'aName' + // Check whether client wants EIRs instead of names + if(sa.Action() & KHostResEir) + { + // Client knows about EIR, we'll fill the TNameRecord with EIR + err = rec.GetEir(aName, EFalse); + } + else + { + // Client don't knows about EIR, we'll fill the TNameRecord with just encoded device name if present + err = rec.GetName(aName); + } + if (err != KErrNone) + {// error getting name - but other stuff fine - + // so ensure name is not gobbledigook + aName.iName.Zero(); + } + err = KErrNone; //err has new meaning now, vis "record exists" + } + else + { + //err is KErrNotFound + } + + iNotify->QueryComplete(err); + return; + } + + // Must request at least one of inquiry or name lookup + if (!(sa.Action() & (KHostResInquiry | KHostResName))) + { + iRequestState = EError; + CompleteRequest(KErrArgument); + return; + } + + // Check the hw state - can't do anything if its switched off + // + if(iInquiryMgr.HWState()==CBTInquiryMgr::EOff) + { + iRequestState = EError; + CompleteRequest(KErrHardwareNotAvailable); + return; + } + + iNameLookupMode = (sa.Action()) & KHostResName ? EDoGetNames : EDontGetNames; + TBool ignoreCache = (sa.Action() & KHostResIgnoreCache) ? ETrue : EFalse; + + if (sa.Action() & KHostResInquiry) + { + // We only support GIAC and LIAC + if (sa.IAC() != KLIAC && sa.IAC() != KGIAC) + { + iRequestState = EError; + CompleteRequest(KErrNotSupported); + return; + } + sa.SetBTAddr(TBTDevAddr()); + iRequestState = EInquiry; + if (iInquiryStatus == EInquiryReady) + {// Need to start the inquiry process + iInquiryStatus = EInquiring; + // We set this before calling StartInquiry, as StartInquiry can call InquiryComplete synchronously. + iResults.Reset(); + iInquiryMgr.StartInquiry(*this, sa.IAC(), ignoreCache); + } + } + else // Not an inquiry - just a name lookup + { + // Just name lookup -- add it to the result set, and request a lookup of it + iResults.Reset(); + + CBTInqResultRef* ref = iInquiryMgr.AddEntryToCache(sa.BTAddr()); + if(ref) + {// This ref is in cache -- need to make our own ref + ref = iResults.Add(ref->Result()); + } + if (!ref) + { + CompleteRequest(KErrNoMemory); + return; + } + iResults.NextResult(); // Move iter onto first (only) result + iRequestState = ENameLookup; + iInquiryMgr.LookupName(*this, sa.BTAddr(), ignoreCache, ETrue); + } + TryToCompleteRequest(); + } + +TInt CBTHostResolver::SetOption(TUint /*aLevel*/, TUint /*aName*/, const TDesC8& /*aOption*/) + { + LOG_FUNC + return KErrNone; + } + +void CBTHostResolver::SetHostName(TDes& aNameBuf) + { + LOG_FUNC + __ASSERT_DEBUG(iRequestState == EIdle, Panic(EHostResolverTwoRequests)); + + TBuf8 utf8Name; + TInt ret = CnvUtfConverter::ConvertFromUnicodeToUtf8(utf8Name, aNameBuf); + iRequestState=ESetLocalName; + if (ret > KErrNone) + {// Unconverted characters exist! Error the client + ret = KErrHostResNameTooLong; + } + + // Check the hw state - can't do anything if its switched off + // + if(iInquiryMgr.HWState()==CBTInquiryMgr::EOff) + { + ret = KErrHardwareNotAvailable; + } + + if (ret == KErrNone) + ret = iInquiryMgr.SetLocalName(utf8Name); + + if (ret != KErrNone) + { + CompleteRequest(ret); + } + } + +void CBTHostResolver::GetHostName(TDes& aNameBuf) + { + LOG_FUNC + __ASSERT_DEBUG(iRequestState == EIdle, Panic(EHostResolverTwoRequests)); + + iHostNameBuf = &aNameBuf; + iRequestState=EGetLocalName; + TInt ret = KErrNone; + + // Check the hw state - can't do anything if its switched off + // + if(iInquiryMgr.HWState()==CBTInquiryMgr::EOff) + { + ret = KErrHardwareNotAvailable; + } + + if(ret==KErrNone) + iInquiryMgr.GetLocalName(); + else + CompleteRequest(ret); + } + + +void CBTHostResolver::CancelCurrentOperation() +/** + Current operation cancel by user app. + Reset, so that we don't bother picking up any future results. + We don't actually bother to cancel the async. operations, as + if they succeed, the InquiryMgr may as well cache them anyway. + The next operation after this (if there is any), must be a fresh + one -- i.e. it can not be a next. +**/ + { + LOG_FUNC + iRequestState = EIdle; + CompleteCurrentOperation(); + } + +void CBTHostResolver::InquiryResult(CBTInqResultRecord& aResult) + { + LOG_FUNC + if (iInquiryStatus != EInquiring) + return; + + // Check if we've already seen this result + CBTInqResultRef* ref = iResults.FindEntry(aResult.LogEntry().iBdaddr); + if (ref) + return; + + TInquirySockAddr& sa = TInquirySockAddr::Cast(iNameRecord->iAddr); + if(!aResult.HasRespondedToIAC(sa.IAC())) + return; // It's never responded to our IAC, so ignore it + + ref = iResults.Add(aResult); + if (!ref) + { + InquiryComplete(KErrNoMemory); + return; + } + + if(iNameLookupMode == EDoGetNames) + { + // Find out whether cache was supposed to be ignored from TNameRecord that was passed into GetByAddress() previously + TBool ignoreCache = (sa.Action() & KHostResIgnoreCache) ? ETrue : EFalse; + // Handle a "special" case: Cache is to be ignored, but we got the _complete_ name during the inquiry process inside an EIR + // (which doesn't count as cached data, since it's freshly arrived from the radio). In this case we don't really want to + // launch a remote name request + if(ignoreCache && ref->Result().IsEirPresent() && ref->Result().Codec().IsDataTypePresent(EEirLocalNameComplete)) + { + // Do nothing, the entry already contains the name + } + else + { + iInquiryMgr.LookupName(*this, aResult.LogEntry().iBdaddr, ignoreCache, EFalse); + } + } + + if (iRequestState == EInquiry) + { + TryToCompleteRequest(); + } + } + +void CBTHostResolver::NameLookupResult(TInt aErr, const TBTDevAddr& aAddr, const TDesC8& aName) + { + LOG_FUNC + if (iNameLookupMode == EDontGetNames) + return; // We're not actually after names + + // Check we've got this result recorded (mostly for error cases) + CBTInqResultRef* ref = iResults.FindEntry(aAddr); + if (ref && !(ref->Result().HaveNameLookupResult() && ref->Result().IsNameComplete())) + { + // we have successfully created a reference and there isn't a complete name available. + ref->Result().SetNameLookupResultCode(aErr); + ref->Result().SetNameComplete(ETrue); + ref->Result().SetName(aName); + } + + TryToCompleteRequest(); + } + +void CBTHostResolver::InquiryComplete(TInt aErr) + { + LOG_FUNC + // LC changed - if we are just doing a name lookup, iInquiryStatus + // doesn't get set so need additional check here. + // + if ((iInquiryStatus != EInquiring) && (iRequestState != ENameLookup)) + return; + iInquiryStatus = EInquiryComplete; + iInqCompletionCode = aErr; + if(aErr==KErrHardwareNotAvailable) + iRequestState=EError; + TryToCompleteRequest(); + } + +void CBTHostResolver::SetLocalNameComplete(TInt aErr) + { + LOG_FUNC + if (iRequestState != ESetLocalName) + return; + CompleteRequest(aErr); + } + +void CBTHostResolver::GetLocalNameComplete(TInt aErr, const TDesC8& aName) + { + LOG_FUNC + if (iRequestState != EGetLocalName) + return; + if (aErr == KErrNone) + { + aErr = CnvUtfConverter::ConvertToUnicodeFromUtf8(*iHostNameBuf, aName); + // Just allow names to be truncated... + } + CompleteRequest(aErr >= 0 ? KErrNone : aErr); + } + +TUint CBTHostResolver::GetIAC() const + { + LOG_FUNC + if (!iNameRecord) + { + return 0; // avoid dereferencing a null ptr + } + + TInquirySockAddr& sa = TInquirySockAddr::Cast(iNameRecord->iAddr); + return sa.IAC(); + } + +TInt CBTHostResolver::SecurityCheck(MProvdSecurityChecker *aSecurityChecker) + { + LOG_FUNC + __ASSERT_ALWAYS(aSecurityChecker, User::Panic(KSECURITY_PANIC, EBTPanicNullSecurityChecker)); + + iSecurityChecker = aSecurityChecker; + return iSecurityChecker->CheckPolicy(KLOCAL_SERVICES, KBT_HOSTRESOLVER_NAME_DIAG); + } + +void CBTHostResolver::TryToCompleteRequest() +/** + This is where the work is done. + A big old state machine that tries to get us one step + closer to completing the query. + Gets an inquiry result out of the result set if appropriate and + there is one available. + Gets the name for the current result if required. + Completes the request if there is then nothing more to do. + Must only break out (i.e. return) if the request is completed, or + non-blocking mode requested +**/ + { + LOG_FUNC + TInquirySockAddr& sa = TInquirySockAddr::Cast(iNameRecord->iAddr); + if ((sa.Action() & KHostResInquiry) && iRequestState == EInquiry) + { + CBTInqResultRef* refNextRes = iResults.NextResult(); + if (refNextRes) + {// Got a result to send up + if (iNameLookupMode == EDoGetNames) + {// Need to get the name of this baby... + iRequestState = ENameLookup; + } + else + {//Complete! + refNextRes->Result().GetInquirySockAddr(sa); + // If EIR was requested and we got one, stick it in the name field + TInt err = KErrNone; + if(sa.Action() & KHostResEir) + { + err = refNextRes->Result().GetEir(*iNameRecord, (sa.Action() & KHostResIgnoreCache) && !(sa.Action() & KHostResName)); + } + else + { + err = refNextRes->Result().GetName(*iNameRecord); + } + if(err != KErrNone) + { + // Set name back to zero length + iNameRecord->iName.Zero(); + } + CompleteRequest(err); + return; + } + } + else if (iInquiryStatus == EInquiryComplete) + {// Bad luck. No more results availble! + if (iInqCompletionCode == KErrNone) + {// Inquiry completed fine, so report end of file. + CompleteRequest(KErrHostResNoMoreResults); + } + else + {// Some error in the inquiry, so report that. + CompleteRequest(iInqCompletionCode); + } + return; + } + } + + if ((sa.Action() & KHostResName) && iRequestState == ENameLookup) + { + CBTInqResultRef* refNextRes = iResults.CurrentResult(); + if(!refNextRes) + {// Can't get the name... no result to lookup + CompleteRequest(KErrHostResNoMoreResults); + return; + } + // Walk through results and check if we have a name to complete + CBTInqResultRef* startResult = refNextRes; + do + { + CBTInqResultRecord& rec = refNextRes->Result(); + if(rec.HaveNameLookupResult() && !rec.IsNameRequestPending() && rec.IsNameComplete()) + {// Got a name! Complete the request + rec.GetInquirySockAddr(sa); + TInt err = rec.NameLookupResultCode(); + if (err == KErrNone) + {// Copy & convert the UTF-8 name over into the result record. + if(sa.Action() & KHostResEir) + { + err = rec.GetEir(*iNameRecord, EFalse); + } + else + { + err = rec.GetName(*iNameRecord); + } + } + if (err != KErrNone) + { + iNameRecord->iName.Zero(); + } + // Name lookup complete (poss. with errors) + if(sa.Action() & KHostResInquiry) + {// Ignore name failures if we're also doing inquiry + CompleteRequest(KErrNone); + } + else + {// Report any error encountered (or none) + CompleteRequest(err); + } + return; + } + + iResults.MoveToback(*refNextRes); + refNextRes = iResults.NextResult(); + } while(refNextRes != startResult); + } + else if(iRequestState==EError) + { + CompleteRequest(KErrHardwareNotAvailable); + } + if(sa.Action() & KHostResDontBlock) + {// Complete any outstanding non-blocking operation + CompleteRequest(KErrWouldBlock); + } + + } + +void CBTHostResolver::CompleteRequest(TInt aErr) + { + LOG_FUNC + if (iRequestState == EIdle && aErr == KErrNone) + return; + iRequestState = EIdle; + if (aErr != KErrNone) + {// End of query -- tidy up + CompleteCurrentOperation(); + } + iNotify->QueryComplete(aErr); + } + +void CBTHostResolver::CompleteCurrentOperation() +/** + Operation known to be complete. + This happens through a CancelCurrentOperation, or when + the current operation has read all the results out. +**/ + { + LOG_FUNC + if (iInquiryStatus == EInquiryReady) + return; + iInquiryStatus = EInquiryReady; + iResults.Reset(); + } + + +// +// BT Inquiry Manager. +// +CBTInquiryMgr::CBTInquiryMgr(CLinkMgrProtocol& aProtocol) + : iLinkMgrProtocol(aProtocol), + iNamePageTimeout((KMaxNamePageTimeout + KMinNamePageTimeout)/2), // start with mid-point timeout + iInquiryMode(EStandardInquiryFormat), iHRs(CBTHostResolver::LinkOffset()) + { + LOG_FUNC + } + +CBTInquiryMgr::~CBTInquiryMgr() + { + LOG_FUNC + __ASSERT_DEBUG(iHRs.IsEmpty(), Panic(EBTHostControllerQueueNotEmpty)); + SetHWState(EOff); + PublishStatus();// we're not doing anything more so publish status + ClearHCICommandQueue(); + delete iFlusher; + delete iConnectingStatus; + } + +void CBTInquiryMgr::ClearHCICommandQueue() + { + LOG_FUNC + if (iCommandQueue) + { + iCommandQueue->MhcqRemoveAllCommands(*this); + } + iCommandQueue = NULL; + } + +void CBTInquiryMgr::SetHCICommandQueue(MHCICommandQueue& aCommandQueue) + { + LOG_FUNC + __ASSERT_DEBUG(!iCommandQueue, Panic(EBTCommandQueueAlreadyExist)); + iCommandQueue = &aCommandQueue; + } + +MHCICommandQueue& CBTInquiryMgr::CommandQueue() const + { + LOG_FUNC + __ASSERT_DEBUG(iCommandQueue, Panic(EBTCommandQueueNotFound)); + return *iCommandQueue; + } + +CBTInquiryMgr* CBTInquiryMgr::NewL(CLinkMgrProtocol& aProtocol) + { + LOG_STATIC_FUNC + CBTInquiryMgr* self = new(ELeave) CBTInquiryMgr(aProtocol); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(); + return self; + } + +void CBTInquiryMgr::ConstructL() + { + LOG_FUNC + iFlusher = CPeriodic::NewL(CActive::EPriorityLow); +#ifdef CONNECTION_PREEMPTS_INQUIRY + iConnectingStatus = CConnectingStatusSubscriber::NewL(*this); +#endif + } + +CBTHostResolver* CBTInquiryMgr::NewHostResolverL() + { + LOG_FUNC + // Leave if HW switched off + // + if(iHWState==EOff) + User::Leave(KErrHardwareNotAvailable); + + CBTHostResolver* r = new(ELeave) CBTHostResolver(*this); + iHRs.AddFirst(*r); + return r; + } + +void CBTInquiryMgr::DeletingHostResolver() + { + LOG_FUNC + // A host resolver is being deleted - we need to check if any other host resolvers want inquiry results from the current IAC. + if(!iRequestedInquiryIAC) + { + //No 'current' IAC exists + return; + } + TDblQueIter iter(iHRs); + TBool iacFound=EFalse; + while (iter) + { + if ((iter++)->GetIAC() == iRequestedInquiryIAC) + { + iacFound = ETrue; + } + } + if (!iacFound) + { + iRequestedInquiryIAC = 0; + } + } + +void CBTInquiryMgr::InquiryResult(TInt aErr,const TInquiryLogEntry& aEntry) +/** + Single Inquiry result received. + Called via HCI for each incoming inquiry result. Need to demultiplex these + out to all CBTHostResolver's interested in them. + + @param aErr An EPOC error code. Any HCI mapping should be done before + calling this function. + @param aEntry The log entry returned through HCI. +**/ + { + LOG_FUNC + LOG5(_L("err: %d,ScanRepM: %d ScanPerM: %d ScanM: %d Type: %d"), + aErr, + aEntry.iPageScanRepetitionMode, + aEntry.iPageScanPeriodMode, + aEntry.iPageScanMode, + aEntry.iSpare ); + + CBTInqResultRef* ref = 0; + + //Revise our cache record regardless - at least need to make sure + //it knows we have received juice from the HCI + if(aErr == KErrNone) + { + ref = AddEntryWithJuiceToCache(aEntry); + if (!ref) + { + aErr = KErrNoMemory; + } + else + { + ref->Result().iFlushes = 0; + ref->Result().iFoundDuringCurrentInquiry = ETrue; + + TInt ret = ref->Result().AddIAC(iCurrentInquiryIAC); + switch(ret) + { + case KErrNone: + ++iResultCount; + // drop through + case KErrAlreadyExists: + break; + default: // Got an error adding the IAC + aErr = ret; + } // switch + ref->Result().AddIAC(KGIAC); // This would come back on a GIAC, so try adding GIAC. + // If this doesn't work, it doesn't really matter, so ignore the error. + } // else + } // if (aErr == KErrNone) + + if (aErr != KErrNone) + { + InquiryComplete(aErr, 0); + return; + } + + // Tell host resolvers + TDblQueIter iter(iHRs); + CBTHostResolver* hr; + while ((hr = iter ++) != NULL) + { + if (hr->GetIAC() == iCurrentInquiryIAC || hr->GetIAC() == KGIAC) + { + hr->InquiryResult(ref->Result()); + } + } + } + +void CBTInquiryMgr::CoDResult(const TBTDevAddr& aAddr, TUint aCoD) +/** + We have been donated a CoD from an incoming connection + This is useful to keep in the registry for remotely + initiated pairings - otherwise we will never have found + the CoD of the device bonding with us +**/ + { + // create record if it doesn't already exist in cache. + // If this fails, we'll just not be able to store the CoD for this device. + + LOG_FUNC + AddEntryWithCoDToCache(aAddr, aCoD); + } + +void CBTInquiryMgr::ClockOffsetResult(const TBTDevAddr& aAddr, TBasebandTime aClockOffset) +/** + We have been donated a Clock Offset - maybe an update +**/ + { + LOG_FUNC + AddEntryWithClockOffsetToCache(aAddr, aClockOffset); + } + +void CBTInquiryMgr::InquiryComplete(TInt aErr, TUint8 /*aNumResponses*/) +/** + Current Inquiry has completed. + Must tell all CBTHostResolver's who may be waiting for inquiry results. + + @param aErr An EPOC error code. Any HCI mapping should be done before + calling this function. + @param aNumResponses The number of calls to InquiryResult that should + have been generated. We don't use this info atm, though. +**/ + { + LOG_FUNC + SetCacheAge(iCurrentInquiryIAC, 0); + TUint iacToComplete = iRequestedInquiryIAC; + iRequestedInquiryIAC = 0; + iFlusher->Cancel(); // Stop watchdog, start flusher + SetHWState(EIdle); + // don't publish status here, might be doing a name lookup + EnableFlusher(); + + // The inquiry has completed so clear any results that were marked found during + // the just finished inquiry + ClearCurrentInquiryResults(); + + if(aErr!=KErrHardwareNotAvailable) + { + LOG(_L("CBTInquiryMgr::InquiryComplete asking for another name lookup")); + DoNameLookup(ETrue); // Get any pending name lookups going + } + else + { + SetHWState(EOff); + LOG(_L("CBTInquiryMgr::InquiryComplete PublishState Idle")); + PublishStatus();// we're not doing name lookup so publish status + } + TDblQueIter iter(iHRs); + CBTHostResolver* hr; + while ((hr = iter++) != NULL) + { + if (hr->GetIAC() == iacToComplete) + { + hr->InquiryComplete(aErr); + } + hr++; + } + // Only queue the inquiry if we have completed all the name requests from this one. + // Otherwise, it will be issued after all the remote name requests have completed. + if (iQueuedInquiryIAC != 0 && iPendingNameRequests == 0) + { + iRequestedInquiryIAC = iQueuedInquiryIAC; + iQueuedInquiryIAC = 0; + DoInquiry(); + } + } + +/** +Name lookup result received. +Called via HCI for each incoming name result. + +The name lookup may not have been requested by the inquiry mgr as when the +stack gets a new connection it tries to solicit information about the +remote. This sort of result is just to add to our cache. + +Otherwise it's from a request on us, pass it on for handling. + +@param aErr An EPOC error code. Any HCI mapping should be done before + calling this function. +@param aAddr The address of the Bluetooth device that the name is associated + with. +@param aBuf 8 bit Descriptor up to KHCIRemoteDeviceNameMaxLength + bytes long, containing the name. +*/ +void CBTInquiryMgr::RemoteNameResult(TInt aErr, const TBTDevAddr& aAddr, const TBTDeviceName8& aBuf) + { + LOG_FUNC + LOG1(_L("aErr = %d"), aErr); + LOG6(_L(", aAddr: %02x %02x %02x %02x %02x %02x"), + TUint8(aAddr[0]), TUint8(aAddr[1]), TUint8(aAddr[2]), TUint8(aAddr[3]), TUint8(aAddr[4]), + TUint8(aAddr[5])); + + CBTInqResultRef* ref = FindExistingCacheEntry(aAddr); + if(ref) + { + CBTInqResultRecord& rec = ref->Result(); + if (rec.IsNameRequestPending()) + { + // We asked for this + HandleRemoteNameResult(aErr, *ref, aBuf); + return; + } + } + + // Unsolicited, just wham in cache if it's good + if (aErr == KErrNone) + { + CBTInqResultRef* ref = AddEntryToCache(aAddr); + if(ref) + { + CBTInqResultRecord& rec = ref->Result(); + + rec.SetName(aBuf); + rec.SetNameComplete(ETrue); + rec.iFlushes = 0; + rec.SetNameValid(ETrue); + rec.SetNameRefreshRequested(EFalse); + } + } + } + +/** +Remote host supported features notification received. + +The remote host supported feature notification may be received during a +remote name request if the remote device provides entries in the remote +host supported feature page. + +@param aErr An EPOC error code. Any HCI mapping should be done before + calling this function. +@param aAddr The address of the Bluetooth device that the name is associated + with. +@param aHostSupportedFeatures 64 bit field holding the remote host supported + features page. +*/ +void CBTInquiryMgr::RemoteHostSupportedFeatures(TInt /*aErr*/, const TBTDevAddr& /*aAddr*/, const TUint64& /*aHostSupportedFeatures*/) + { + LOG_FUNC + // This is currently not used by the stack since the optimisation it could provide would be + // difficult to take advantage of and of minimal use. + + // If to be used the result should be stored in the inquiry result record along with whether + // a name request has been made (with EIR it is possible to have retrieved the name without + // a name request) + } + +/** +Name lookup result received. + +We need to demultiplex these out to all CBTHostResolver's +interested in them. + +@param aErr An EPOC error code. Any HCI mapping should be done before + calling this function. +@param aRef The inquiry result for this info. +@param aBuf 8 bit Descriptor up to KHCIRemoteDeviceNameMaxLength + bytes long, containing the name. +*/ +void CBTInquiryMgr::HandleRemoteNameResult(TInt aErr, CBTInqResultRef& aRef, const TBTDeviceName8& aBuf) + { + LOG_FUNC + + if(iHWState != EConnecting) + { + SetHWState(EIdle); + // don't set publish status here, doinquiry will do that + } + + if(aErr == KErrNone) + { + if(iNamePageTimeout > KMinNamePageTimeout) + {// got name OK, dec the page timeout + iNamePageTimeout -= KNamePageTimeoutIncrement; + } + } + else if(aErr == KHCIErrorBase - EPageTimedOut) + { + if(iNamePageTimeout < KMaxNamePageTimeout) + {// Got pagetime, inc. pagetime a bit + iNamePageTimeout += KNamePageTimeoutIncrement; + } + } + + + CBTInqResultRecord& rec = aRef.Result(); + + --iPendingNameRequests; + rec.SetNamePending(EFalse); + ++(rec.iNameLookupAttempts); + if(iPendingNameRequests <= 0) + { + iNewPageRequestsPending = EFalse; + } + + // Update our cached version. + if (aErr == KErrNone) + { + rec.SetName(aBuf); + rec.SetNameComplete(ETrue); + rec.iFlushes = 0; + rec.SetNameValid(ETrue); + rec.SetNameRefreshRequested(EFalse); + rec.SetExplicitNameRequest(EFalse); // If we had an explicit name request on this, we've completed it now + } + else + { + // got an error + if(rec.NameLookupAttempts() < KMaxNameLookupAttempts) + {// try to get later - put to back of Que for getting + ++iPendingNameRequests; + rec.SetNamePending(ETrue); + iCurrentResults.MoveToback(aRef); + } + else + {// No more chances -- record lookup error code + rec.SetNameLookupResultCode(aErr); + rec.SetExplicitNameRequest(EFalse); // If we had an explicit name request on this, we've completed it now + } + } + + // Try to get more names, if needed + LOG(_L("CBTInquiryMgr::HandleRemoteNameResult asking for another name lookup")); + DoNameLookup(EFalse); + + if(!aRef.Result().IsNameRequestPending()) + {// Don't propogate if name request is still pending + TDblQueIter iter(iHRs); + while (iter) + { + (iter++)->NameLookupResult(aErr, rec.LogEntry().iBdaddr, aBuf); + } + } + + // In case we're now free, do inquiry + LOG(_L("CBTInquiryMgr::HandleRemoteNameResult asking for another inquiry")); + if (iPendingNameRequests == 0 && iRequestedInquiryIAC == 0) // If we've completed the current inquiry, see if we've got another one queued. + { + iRequestedInquiryIAC = iQueuedInquiryIAC; + iQueuedInquiryIAC = 0; + } + DoInquiry(); + } + +const TDesC8* CBTInquiryMgr::DeviceNameFromCache(const TBTDevAddr& aAddr) +/** + Used by other parts of the stack to get a name synchronously from cache if + there is one + + @return NULL if device is unknwon +**/ + { + LOG_FUNC + CBTInqResultRef* entry = FindExistingCacheEntry(aAddr); + if (!entry) + return NULL; + else + { + return &entry->Result().Name(); + } + } + +CBTInqResultRecord* CBTInquiryMgr::BasebandParametersFromCache(const TBTDevAddr& aAddr) + { + LOG_FUNC + CBTInqResultRef* entry = FindExistingCacheEntry(aAddr); + if (!entry) + return NULL; + else + { + return &(entry->Result()); + } + } + +void CBTInquiryMgr::SetHWState(THWState aState) + { + LOG_FUNC + iHWState = aState; + } + +void CBTInquiryMgr::PublishStatus() + { + LOG_FUNC + // sets the inquiry/discovery status to bool true or false + TBool inquiryState = EFalse; + + switch (iHWState) + { + /* deliberate fall throughs */ + case EInquiry: + case ENameLookup: + { + inquiryState = ETrue; + } + break; + case EIdle: + case EConnecting: + case EOff: + default: + { + // defaulted above to EFalse; + } + break; + } + /* only publish status if we've changed + it is however the case that if the state changes sufficiently quickly + the subscriber will miss some of the state changes + this is particularly the case while doing name searches where we toggle into + idle and back to namelookup quickly and regularly, the subscriber + sees a stream of etrues coming through, the efalses are missing + this is probably ok as long as they know because this means they are told + more than once they are inquiring, and indeed they are, its certainly better + than being told they're not if they are */ + if (inquiryState != iReportedInquiryState) + { + iLinkMgrProtocol.SetUIDiscovering(inquiryState); + iReportedInquiryState = inquiryState; + if (inquiryState) + { + LOG(_L("CBTInquiryMgr::PublishState setting status TRUE")); + } + else + { + LOG(_L("CBTInquiryMgr::PublishState setting status FALSE")); + } + } + else + { + LOG(_L("CBTInquiryMgr::PublishState not updating status")); + } + } + +void CBTInquiryMgr::SetLocalNameComplete(TInt aErr) + { + LOG_FUNC + TDblQueIter iter(iHRs); + while (iter) + { + (iter++)->SetLocalNameComplete(aErr); + } + } + +void CBTInquiryMgr::GetLocalNameComplete(TInt aErr, const TDesC8& aName) + { + LOG_FUNC + TDblQueIter iter(iHRs); + while (iter) + { + (iter++)->GetLocalNameComplete(aErr, aName); + } + } + +void CBTInquiryMgr::StartInquiry(CBTHostResolver& aResolver, TUint aIAC, TBool aIgnoreCache) + { + LOG_FUNC + // Pre-load any existing results into the CBTHostResolver + if (!aIgnoreCache) + { + // Pre-load any existing results into the CBTHostResolver + iCurrentResults.ReturnToFirstResult(); + while (CBTInqResultRef* ref = iCurrentResults.NextResult()) + { + // If GIAC is being requested, we return all results + if (aIAC == KGIAC || ref->Result().HasRespondedToIAC(aIAC)) + { + aResolver.InquiryResult(ref->Result()); + } + } + } + + if ( !aIgnoreCache && !iCurrentResults.IsEmpty() && CacheAge(aIAC) <= KCacheStaleAge) + {// Cache not yet stale, so just give these results back and dont start + LOG(_L("CBTInquiryMgr::StartInquiry giving result from cache")); + aResolver.InquiryComplete(KErrNone); + return; + } + + if (iRequestedInquiryIAC || iQueuedInquiryIAC) + { + LOG(_L("CBTInquiryMgr::StartInquiry iRequestedInquiryIAC")); + if(iRequestedInquiryIAC == aIAC) + { + // an Inquiry is ongoing, return any results already found during the + // current Inquiry if not already done so as part of the complete cache + if (aIgnoreCache) + { + iCurrentResults.ReturnToFirstResult(); + while (CBTInqResultRef* ref = iCurrentResults.NextResult()) + { + if (ref->Result().iFoundDuringCurrentInquiry) + { + aResolver.InquiryResult(ref->Result()); + } + } + } + // the current Inquiry will just continue + return; + } + if (aIAC == KGIAC) + { + // If the current IAC is GIAC, and the requested inquiry IAC isn't, it must be LIAC + __ASSERT_DEBUG(iRequestedInquiryIAC == KLIAC, Panic(EBTUnexpectedIAC)); + // Queue a general inquiry for when the current limited inquiry has finished + iQueuedInquiryIAC = aIAC; + return; + } + else if(iHWState == EInquiry) + { + // The host resolver should only allow through GIAC and LIAC, and we handle GIAC above + __ASSERT_DEBUG(aIAC == KLIAC, Panic(EBTUnexpectedIAC)); + // We favour a Limited inqiury, so interrupt the current general inquiry + TInt err = CancelHardwareInquiry(); + if(err!=KErrNone) + { + LOG(_L("CBTInquiryMgr::StartInquiry cancel didn't work")); + aResolver.InquiryComplete(err); // cancel went wrong + return; + } + // Queue a general inquiry for when the limited inquiry is complete + iRequestedInquiryIAC = KLIAC; + iQueuedInquiryIAC = KGIAC; + SetHWState(ECancellingForNewIAC); + iFlusher->Cancel(); // Stop watchdog, start flusher. + return; + } + else + { + // The host resolver should only allow through GIAC and LIAC, and we handle GIAC above + __ASSERT_DEBUG(aIAC == KLIAC, Panic(EBTUnexpectedIAC)); + iRequestedInquiryIAC = KLIAC; + iQueuedInquiryIAC = KGIAC; + } + } + + iRequestedInquiryIAC = aIAC; + iInquiryInteruptions = 0; + + DoInquiry(); + } + +void CBTInquiryMgr::Suspend() +/** + Ah - somebody wishes to make a connection - we need to clear the way + If we are doing a namerequest we can cancel. + + WARNING - The caller of this must ensure a call to Resume() ASAP +**/ + { + LOG_FUNC + + if(iHWState == EInquiry) + { + // This is a best effort service, the Connect will be tried even + // if we fail to stop the inquiry, so ignore errors. + static_cast(CancelHardwareInquiry()); + } + else if (iHWState == ENameLookup) + { + + iCurrentResults.ReturnToFirstResult(); + while(CBTInqResultRef *ref = iCurrentResults.NextResult()) + { + + CBTInqResultRecord& rec = ref->Result(); + if(rec.IsNameRequestPending()) + { + // This is a best effort service, the Connect will be tried even + // if we fail to stop the Name request, so ignore errors. + TRAP_IGNORE(CancelRemoteNameL(rec.LogEntry().iBdaddr)); + } + } + + } + + iFlusher->Cancel(); // Stop watchdog, start flusher. + SetHWState(EConnecting); // will stop inquirymanager doing anything else + // don't publish status here, will be cleared up when manager tries to do soemthing else + EnableFlusher(); + } + +void CBTInquiryMgr::Resume() +/** + We can now carry on... +**/ + { + LOG_FUNC + if(iHWState == EConnecting) + { + LOG(_L("InquiryMgr: Hardware connected; resuming operations (when a new inquiry starts")); + SetHWState(EIdle); + // don't publish status here, it will be set by namelookup + LOG(_L("CBTInquiryMgr::Resume asking for another name lookup")); + DoNameLookup(EFalse); + DoInquiry(); + EnableFlusher(); // in case no operations started + } + } + +void CBTInquiryMgr::LookupName(CBTHostResolver& aResolver, const TBTDevAddr& aAddr, TBool aIgnoreCache, TBool aExplicitNameRequest) +/** +This method is called by a CBTHostResolver to look up the name of a device with a given address. If the +name is not immediately available a name lookup request is queued within the inquiry manager. This +request will be actioned by CBTInquiryMgr::DoNameLookup(), and the remote device response will be +sent (by CBTInquiryMgr::RemoteNameResult) to all current host resolvers. +**/ + { + LOG_FUNC + LOG1(_L("CBTHostResolver: 0x%8x"), &aResolver); + LOG6(_L(", aAddr: %02x %02x %02x %02x %02x %02x"), + TUint8(aAddr[0]), TUint8(aAddr[1]), TUint8(aAddr[2]), TUint8(aAddr[3]), TUint8(aAddr[4]), + TUint8(aAddr[5])); + + CBTInqResultRef* ref = AddEntryToCache(aAddr); //N.B. Creates a new entry if one doesn't exist + + if(!ref) + { + aResolver.NameLookupResult(KErrNoMemory, aAddr, KNullDesC8); + return; + } + + // check state of cache entry + CBTInqResultRecord& rec = ref->Result(); + + if(rec.HaveNameLookupResult() && rec.IsNameComplete() && (!aIgnoreCache|| rec.iFoundDuringCurrentInquiry)) + {// Either got a name, or we made an attempt that failed + aResolver.NameLookupResult(rec.NameLookupResultCode(), + rec.LogEntry().iBdaddr, + rec.iName); + return; + } + + if (aExplicitNameRequest) + { + rec.SetExplicitNameRequest(ETrue); + } + + if(rec.IsNameRequestPending()) + { + return; // it's already happening + } + + rec.SetNamePending(ETrue); + rec.iNameLookupAttempts = 0; + ++iPendingNameRequests; + iNewPageRequestsPending = ETrue; + + if(iHWState == EInquiry) + { + if(iPendingNameRequests > iInquiryInteruptions + || iInquiryInteruptions < KImmediateNameFetch) + { + TryToInterruptInquiryForNameLookup(); + } + } + else + { + LOG(_L("CBTInquiryMgr::LookupName asking for another name lookup")); + DoNameLookup(EFalse); + } + } + +void CBTInquiryMgr::ClearCache() + { + LOG_FUNC + if(iHWState == EInquiry) + { + TInt err = CancelHardwareInquiry(); + if(err==KErrNone) + { + // Don't clear cache if we couldn't cancel inquiry. + // We'll be slightly unfaithful in the case of OOM... + iCurrentResults.Reset(); + InquiryComplete(KErrNone, 0); + } + } + else + { + iCurrentResults.Reset(); + } + } + +TInt CBTInquiryMgr::SetLocalName(const TDesC8& aName) + { + LOG_FUNC + return iLinkMgrProtocol.SetLocalDeviceName(aName); + } + +TInt CBTInquiryMgr::GetLocalName() + { + LOG_FUNC + TRAPD(err, ReadLocalNameL()); + return err; + } + +/** + This is called whenever the local HCI version is updated (usually only during startup), + so that the inquiry mode is set accordingly +**/ +void CBTInquiryMgr::SetInquiryMode() + { + LOG_FUNC + + THCIInquiryMode inquiryMode = EStandardInquiryFormat; + + if (iLinkMgrProtocol.IsExtendedInquiryResponseSupportedLocally()) + { + inquiryMode = EInquiryWithRssiOrEir; + } + else if (iLinkMgrProtocol.IsRssiWithInquiryResultsSupportedLocally()) + { + inquiryMode = EInquiryWithRssi; + } + + if (inquiryMode != EStandardInquiryFormat) + { + TRAPD(err, WriteInquiryModeL(inquiryMode)); + if (err == KErrNone) + { + iPendingInquiryMode = inquiryMode; + } + } + } +/** + This is called whenever the pending WriteInquiryMode command has completed. +**/ +void CBTInquiryMgr::WriteInquiryModeComplete(TBool aSucceeded) + { + LOG_FUNC + if (aSucceeded) + { + iInquiryMode = iPendingInquiryMode; + return; + } + + // should we maybe try to set another mode? Probably worth a try... + // let's downgrade the inquiry mode + if (iPendingInquiryMode == EInquiryWithRssiOrEir) + { + TRAPD(err, WriteInquiryModeL(EInquiryWithRssi)); + if (err == KErrNone) + { + iPendingInquiryMode = EInquiryWithRssi; + } + } + } + +void CBTInquiryMgr::DoInquiry() +/** + Try to start the inquiry. + Only starts it if there isn't already one going. +**/ + { + LOG_FUNC + + if(iRequestedInquiryIAC == 0 || iHWState != EIdle || iHRs.IsEmpty()) + { +#ifdef _DEBUG + LOG3(_L("Not starting inquiry. iRequestedInquiryIAC == %d, iHWState == %d, iHostResolverCount == %d"), iRequestedInquiryIAC, iHWState, iNumHRs); + if (iHRs.IsEmpty()) + { + LOG(_L("No HRs interested in results - Stopping discovery")); + } +#endif + if (iHWState == EIdle) + { + LOG(_L("CBTInquiryMgr::DoInquiry PublishStatus Idle")); + PublishStatus();// make sure the status says we're idle + } + return; + } + + // If pending on name lookup, we'll do it when that finishes + + // Cache stale -- update it. + TInt err = StartHardwareInquiry(); + + if (err == KErrNone) + { + SetHWState(EInquiry); + LOG(_L("CBTInquiryMgr::DoInquiry PublishState Inquiry")); + PublishStatus(); + // Use cache age to estimate time elapsed while inquiring. + // Synchronise it with the inquiry + // Stop flusher, Start inquiry wathcdog + iFlusher->Cancel(); + TCallBack cb(InquiryWatchdog, this); + iFlusher->Start(KInquiryWatchdogPeriod * 1000000, + KInquiryWatchdogPeriod * 1000000, + cb); + SetCacheAge(iCurrentInquiryIAC, 0); + iInquirySilenceCount = 0; + iResultCount = 0; + } + else + { + // Couldn't start inquiry. + // Make sure the request is completed. + iRequestedInquiryIAC = 0; + InquiryComplete(err, 0); + LOG(_L("CBTInquiryMgr::DoInquiry PublishState Idle couldn't start inquiry")); + PublishStatus(); + return; + } + } + +void CBTInquiryMgr::ClearCurrentInquiryResults() + { + LOG_FUNC + iCurrentResults.ReturnToFirstResult(); + while (CBTInqResultRef* ref = iCurrentResults.NextResult()) + { + ref->Result().iFoundDuringCurrentInquiry = EFalse; + } + } + +TInt CBTInquiryMgr::CancelHardwareInquiry() + { + LOG_FUNC + // CancelInquiryL will stop the current inquiry so clear any results that + // were marked found during the inquiry + ClearCurrentInquiryResults(); + + TRAPD(err, CancelInquiryL()); + return err; + } + +TInt CBTInquiryMgr::StartHardwareInquiry() + { + LOG_FUNC + iCurrentInquiryIAC = iRequestedInquiryIAC; + // attempt to free up baseband space for best performance discovery + iLinkMgrProtocol.PhysicalLinksMgr().RequireSlotSpace(); // will *eventually* put connections in hold - we dont wait though + TRAPD(err, StartInquiryL(iCurrentInquiryIAC, KInquiryLength, KInquiryMaxResults)); + + return err; + } + + + + +void CBTInquiryMgr::DoNameLookup(TBool aInquiryComplete) +/** +This method attempts to issue a name lookup to the HCI +**/ + { + LOG_FUNC + + if(iHWState != EIdle || iPendingNameRequests == 0 || iHRs.IsEmpty()) + { +#ifdef _DEBUG + LOG3(_L("Not starting name lookup. iCurrentInquiryIAC == %d, iHWState == %d, iHostResolverCount == %d"), iCurrentInquiryIAC, iHWState, iNumHRs); +#endif + /* if hw is idle and the inquiry is ready we are finished + and will not get another inquiry call so publish status idle + note that if doing 'inquiry with names' + in inquiry the aInquiryComplete flag stops state oscilating + thostres name request for all devices will still cause this to + oscilate because each name request is done individually, at this layer + we have no idea whether the test program will ask for another name */ + if ((iHWState == EIdle) && (aInquiryComplete)) + { + LOG(_L("CBTInquiryMgr::DoNameLookup inquiry finished with")); + PublishStatus(); + } + // else publish status will be sorted by next inquiry + return; + } + + iCurrentResults.ReturnToFirstResult(); + CBTInqResultRef *refToGet = NULL; + while(CBTInqResultRef *ref = iCurrentResults.NextResult()) + { + CBTInqResultRecord& rec = ref->Result(); + if(rec.IsNameRequestPending()) + { + if(!iRequestedInquiryIAC || + rec.NameLookupAttempts() < KMaxNameLookupAttemptsDuringInquiry) + { + // We want the first record for the current IAC or a record for an explicit name request. + // Failing that, we'll just have the first record + if (rec.HasRespondedToIAC(iCurrentInquiryIAC) || rec.IsExplicitNameRequest()) + { + refToGet = ref; + break; + } + if (!refToGet) + { + refToGet = ref; + } + } + } + } + if (refToGet) + { + CBTInqResultRecord& rec = refToGet->Result(); + + // First set the page timeout, it is a best effort attempt + iNamePageTimeout = iLinkMgrProtocol.PhysicalLinksMgr().TryToChangePageTimeout(iNamePageTimeout); + + // Request the name lookup. + TRAPD(err, LookupNameL(rec.LogEntry())); + + if (err == KErrNone) + { + SetHWState(ENameLookup); + return; + } + else + { + // got a synchronous error - put in a blank name + HandleRemoteNameResult(err, *refToGet, KNullDesC8()); + } + } + // Nothing new to do! + iNewPageRequestsPending = EFalse; + } + +void CBTInquiryMgr::TryToInterruptInquiryForNameLookup() + { + LOG_FUNC + TInt err = CancelHardwareInquiry(); + + if(err == KErrNone) + {// Start doing name lookups + iFlusher->Cancel(); //Stop watchdog, start flusher + SetHWState(EIdle); + // don't publish status here, namelookup/inquiry will deal with it + EnableFlusher(); + ++iInquiryInteruptions; + LOG(_L("CBTInquiryMgr::TryToInterruptInquiryForNameLookup asking for another name lookup")); + DoNameLookup(EFalse); + DoInquiry(); // <- In case name lookup fails + } + } + +// An inelegant workaround called from CLinkMgrProtocol::Error to get round +// the fact that the stack doesn't (yet) keep track of commands that it has +// been asked to do. If they aren't completed on a reset due to a +// KErrHardwareNotFound error then the stack hangs as it waits for the responses. +void CBTInquiryMgr::CompleteCommands(TInt aErr) + { + LOG_FUNC + SetLocalNameComplete(aErr); + GetLocalNameComplete(aErr, _L8("")); + + // Reset any inquiry results left over from a previous inquiry + iCurrentResults.Reset(); + InquiryComplete(aErr, 0); + } + + +// Check whether the controller supports Extended Inquiry Response by examing +// the supported features mask using Link Manage Protocol +TBool CBTInquiryMgr::IsExtendedInquiryResponseSupported() + { + LOG_FUNC + return iLinkMgrProtocol.IsExtendedInquiryResponseSupportedLocally(); + } + +/******************************************************************************/ +/* If UPDATING THE CACHE, please use one of the "AddEntry..." methods below. */ +/******************************************************************************/ +CBTInqResultRef* CBTInquiryMgr::FindExistingCacheEntry(const TBTDevAddr& aAddr) + { + LOG_FUNC + return(iCurrentResults.FindEntry(aAddr)); + } + +CBTInqResultRef* CBTInquiryMgr::AddEntryToCache(const TBTDevAddr& aAddr) + { + LOG_FUNC + CBTInqResultRef* ref = FindExistingCacheEntry(aAddr); + if(ref) + {// Already in there! + return ref; + } + + CBTInqResultRecord* rec = new CBTInqResultRecord(aAddr); + if (!rec) + { + return 0; + } + + ref = iCurrentResults.Add(*rec); + if (!ref) + { + delete rec; + return 0; + } + + //default CBTInqResultRecord values - provided in CBTInqResultRecord constructer (unlike v1) + //maybe overwritten later + + EnableFlusher(); // Make sure we're aging entries. + return ref; + } + +CBTInqResultRef* CBTInquiryMgr::AddEntryWithJuiceToCache(const TInquiryLogEntry& aEntry) +/** +This method should be used if you are supplying cache with all valid +baseband parameters. +Setting the valid bit in clock offset allows us to tell btman that the clock +offset value, and by inferrence the other juice values are valid - +i.e. have been supplied by the HCI, as opposed to having been supplied +as default. +**/ + { + LOG_FUNC + CBTInqResultRef* ref = AddEntryToCache(aEntry.iBdaddr); + + if (ref) + { + CBTInqResultRecord& rec = ref->Result(); + // Update all, and register that values come from HCI + rec.iJuiceFromHCIMask |= EBluetoothJuice; + // First fill in the base class members, common to all inquiry results + *static_cast(&rec.iEntry) = aEntry; + // Now complete with extra data depending on the inquiry result type + switch(aEntry.iSpare) + { + case KInqLogEntryStandard: + break; + case KInqLogEntryWithEir: + { + const TInquiryLogEntryWithEir& entryEir = static_cast(aEntry); + // Only replace EIR if it contains something, since sometimes EIR transmission fails + if(entryEir.iExtendedInquiryResponse.Length() != 0) + { + rec.iEntry.iExtendedInquiryResponse = entryEir.iExtendedInquiryResponse; + } + rec.iJuiceFromHCIMask |= EBluetoothEir; + // Now check whether we can extract a complete or partial friendly name from the EIR and add it to the cache + TPtrC8 name; + TBool complete = ETrue; + rec.Codec().Set(rec.iEntry.iExtendedInquiryResponse); + // use EirCodec doing sanity check of the eir data + TInt error = rec.Codec().DoSanityCheck(rec.iEntry.iExtendedInquiryResponse); + if(error == KErrNone) + { + error = rec.Codec().GetDeviceName(name); + if(error >= KErrNone) + { + if(error == EDataPartial) + { + complete = EFalse; + } + // Do not overwrite complete names with partial ones + if(complete || !rec.IsNameValid() || (rec.IsNameValid() && !rec.IsNameComplete())) + { + rec.SetName(name); + rec.SetNameComplete(complete); + rec.iFlushes = 0; + rec.SetNameValid(ETrue); + rec.SetNameRefreshRequested(EFalse); + } + } + } + } + // Fall through + case KInqLogEntryWithRssi: + { + const TInquiryLogEntryWithRssi& entryRssi = static_cast(aEntry); + rec.iEntry.iRssi = entryRssi.iRssi; + rec.iJuiceFromHCIMask |= EBluetoothRssi; + break; + } + default: + __ASSERT_DEBUG(EFalse, Panic(EBTInvalidInquiryLogEntry)); + break; + } + // Mark as valid, HCI does not (necessarily) do this for you + rec.iEntry.iClockOffset |= KHCIClockOffsetValidBit; + } + + return ref; + } + +CBTInqResultRef* CBTInquiryMgr::AddEntryWithCoDToCache(const TBTDevAddr& aAddr, const TUint aCoD) + { + LOG_FUNC + CBTInqResultRef* ref = AddEntryToCache(aAddr); + + if (ref) + { + // Update all, and register that values come from HCI + ref->Result().iJuiceFromHCIMask |= EBluetoothCoD; + ref->Result().iEntry.iCoD = aCoD; + } + + return ref; + } + +CBTInqResultRef* CBTInquiryMgr::AddEntryWithClockOffsetToCache(const TBTDevAddr& aAddr, const TBasebandTime aClockOffset) + { + LOG_FUNC + CBTInqResultRef* ref = AddEntryToCache(aAddr); + + if (ref) + { + //update the clock offset - useful for subsequent connections + // mark as valid + ref->Result().iJuiceFromHCIMask |= EBluetoothClockOffSet; + ref->Result().LogEntry().iClockOffset = static_cast(aClockOffset | KHCIClockOffsetValidBit); + } + + return ref; + } + + +TInt CBTInquiryMgr::InquiryWatchdog(TAny* aPtr) +/* +Called second while doing enquiry. +Considers whether to give up enquiry +*/ + { + LOG_STATIC_FUNC + CBTInquiryMgr& self = *static_cast(aPtr); + __ASSERT_DEBUG(self.iHWState == EInquiry, Panic(EHostResolverBadHWState)); + if(self.iResultCount == 0) + { + ++self.iInquirySilenceCount; + } + + if(self.iNewPageRequestsPending + && (self.iInquirySilenceCount * KInquiryWatchdogPeriod) > self.iInquiryInteruptions) + {// Silence, and names are waiting. Defer the inquiry + self.TryToInterruptInquiryForNameLookup(); + } + + self.iResultCount = 0; + return 0; + } + +TInt CBTInquiryMgr::Flush(TAny* aPtr) + { + LOG_STATIC_FUNC + CBTInquiryMgr& self = *static_cast(aPtr); + __ASSERT_DEBUG(self.iHWState != EInquiry, Panic(EHostResolverBadHWState)); + self.DoFlush(); + return 0; + } + +void CBTInquiryMgr::DoFlush() + { + LOG_FUNC + for (TInt i = 0; i < iCacheAge.Count(); i++) + { + ++iCacheAge[i].iCacheAge; + } + iCurrentResults.ReturnToFirstResult(); + + while (CBTInqResultRef* ref = iCurrentResults.NextResult()) + { + CBTInqResultRecord& rec = ref->Result(); + TInt age = rec.IncFlushes(); + if(age >= KRecordStaleAge && rec.NumberOfIACsRespondedTo() > 0) + { + // device is around, but not seen for a while + rec.ClearIACs(); + LOG(_L("CBTInquiryMgr::DoFlush - ClearIACs")); + } + + if(age >= KRecordDeathAge) + { + rec.SetNameValid(EFalse); + LOG(_L("CBTInquiryMgr::DoFlush - age >= KRecordDeathAge setting name valid False")); + } + + if(age >= KRecordDeathAge && rec.NumberOfIACsRespondedTo() == 0 && !rec.IsNameValid() + && (!rec.IsNameRequestPending() || iHRs.IsEmpty())) //Don't flush old record if name pending and HRs present + {// Record is dead. Delete it + LOG(_L("CBTInquiryMgr::DoFlush - Deleting reference")); + delete ref; + } + else if(rec.IsNameRefreshRequested() && iHWState == EIdle) + {// Try to get this name now, perhaps? + LOG(_L("CBTInquiryMgr::DoFlush - Try to get name straight away")); + rec.SetNameRefreshRequested(EFalse); + rec.SetNamePending(ETrue); + rec.iNameLookupAttempts = 0; + ++iPendingNameRequests; + LOG(_L("CBTInquiryMgr::DoFlush asking for another name lookup")); + DoNameLookup(EFalse); + } + } + if (iCurrentResults.IsEmpty()) + { + iNewPageRequestsPending = EFalse; + iPendingNameRequests = 0; + iFlusher->Cancel(); + } + } + +void CBTInquiryMgr::EnableFlusher() + { + LOG_FUNC + if (iFlusher->IsActive() || iHWState == EInquiry) + return; + static const TInt usec2sec = 1000000; + TCallBack cb(Flush, this); + iFlusher->Start(usec2sec / 4, // 1/4 sec for initial flush + KFlushTimeoutSecs * usec2sec, + cb); + } + +TInt CBTInquiryMgr::CacheAge(TUint aIAC) const + { + TInt ret = KMaxTInt; // If we haven't set a cache age for this IAC, return KMaxTInt + for (TUint i = 0; i < iCacheAge.Count(); i++) + { + if (iCacheAge[i].iIAC == aIAC) + { + ret = iCacheAge[i].iCacheAge; + break; + } + } + return ret; + } + +void CBTInquiryMgr::SetCacheAge(TUint aIAC, TInt aAge) + { + TBool found = EFalse; + for (TUint i = 0; i < iCacheAge.Count(); i++) + { + if (iCacheAge[i].iIAC == aIAC) + { + iCacheAge[i].iCacheAge = aAge; + found = ETrue; + break; + } + } + if (!found) + { + TInquiryCacheAge ageInfo; + ageInfo.iIAC = aIAC; + ageInfo.iCacheAge = aAge; + iCacheAge.Append(ageInfo); + // If we can't append, there's not a lot we can do - we'll just have + // to return KMaxTInt when someone asks for the age + } + } + + +// ----------------------------------------------------------------------------------------- + +void CBTInquiryMgr::StartInquiryL(TUint aIAC, TUint8 aLength, TUint8 aNumResponses) + { + LOG_FUNC + // Ownership of cmd transfered even if MhcqAddCommandL leaves + CInquiryCommand* cmd = CInquiryCommand::NewL(aIAC, aLength, aNumResponses); + CommandQueue().MhcqAddCommandL(cmd, *this); + } + +void CBTInquiryMgr::CancelInquiryL() + { + LOG_FUNC + // Ownership of cmd transfered even if MhcqAddCommandL leaves + CInquiryCancelCommand* cmd = CInquiryCancelCommand::NewL(); + CommandQueue().MhcqAddCommandL(cmd, *this); + } + +void CBTInquiryMgr::CancelRemoteNameL(const TBTDevAddr& aAddr) + { + LOG_FUNC + CRemoteNameRequestCancelCommand* cmd = CRemoteNameRequestCancelCommand::NewL(aAddr); + CommandQueue().MhcqAddCommandL(cmd, *this); + } + +void CBTInquiryMgr::WriteInquiryModeL(TUint8 aInquiryMode) + { + LOG_FUNC + // Ownership of cmd transfered even if MhcqAddCommandL leaves + CWriteInquiryModeCommand* cmd = CWriteInquiryModeCommand::NewL(aInquiryMode); + CommandQueue().MhcqAddCommandL(cmd, *this); + } + +/** +This function should be used by the inquiry manager to retrieve remote names. +*/ +void CBTInquiryMgr::LookupNameL(const TInquiryLogEntry& aEntry) + { + LOG_FUNC + // Ownership of cmd transfered even if MhcqAddCommandL leaves + CRemoteNameRequestCommand* cmd = CRemoteNameRequestCommand::NewL(aEntry.iBdaddr, aEntry.iPageScanRepetitionMode, aEntry.iPageScanMode, aEntry.iClockOffset); + CommandQueue().MhcqAddCommandL(cmd, *this); + } + +/** +This function should only be used when there is an established physical link (and therefore from +CPhysicalLink only) to retrieve a remote name. +*/ +void CBTInquiryMgr::ReadRemoteNameL(const TBTDevAddr& aAddr) + { + LOG_FUNC + // Ownership of cmd transfered even if MhcqAddCommandL leaves + CRemoteNameRequestCommand* cmd = CRemoteNameRequestCommand::NewL(aAddr, 0, 0, 0); + CommandQueue().MhcqAddCommandL(cmd, *this); + } + +void CBTInquiryMgr::ReadLocalNameL() + { + LOG_FUNC + // Ownership of cmd transfered even if MhcqAddCommandL leaves + CReadLocalNameCommand* cmd = CReadLocalNameCommand::NewL(); + CommandQueue().MhcqAddCommandL(cmd, *this); + } + +void CBTInquiryMgr::MhcqcCommandErrored(TInt aErrorCode, const CHCICommandBase* aCommand) + { + LOG_FUNC + __ASSERT_DEBUG(aCommand, Panic(EBTInquiryMgrUnmatchedEvent)); + LOG2(_L("MhcqcCommandErrored: error code:%d opcode:0x%x"), aErrorCode, aCommand->Opcode()); + + if (aCommand->Opcode() == KInquiryOpcode) + { + // Clear any pending stuff + CompleteCommands(aErrorCode); + // this will also set the state to Idle + } + else if (aCommand->Opcode() == KWriteInquiryModeOpcode) + { + // command had error before response -- assume not supported + WriteInquiryModeComplete(EFalse); + } + else if (aCommand->Opcode() == KRemoteNameRequestOpcode) + { + // command had error before response -- doctor a fake response and process it to restore sanity + TBuf8<256> fakeEventBuffer; + const TBTDevAddr remNameReqAddr = static_cast(aCommand)->BDADDR(); + TRemoteNameReqCompleteEvent fakeRemoteNameRequestCompleteEvent(EOK, remNameReqAddr, _L8(""), fakeEventBuffer); + RemoteNameReqCompleteEvent(fakeRemoteNameRequestCompleteEvent); + // we will not change the state so a running inquiry can complete + } + else if (aCommand->Opcode() == KReadLocalNameOpcode) + { + // command had error before response -- doctor a fake response and process it to restore sanity + TBuf8<256> fakeEventBuffer; + TReadLocalNameCompleteEvent fakeLocalNameCompleteEvent(EUnspecifiedError, 0, _L8(""), fakeEventBuffer); + ReadLocalNameOpcode(EUnspecifiedError, fakeLocalNameCompleteEvent); + // this will also set the state to Idle + } + + } + +// From MHCICommandQueueClient +void CBTInquiryMgr::MhcqcCommandEventReceived(const THCIEventBase& aEvent, + const CHCICommandBase* aRelatedCommand) + { + LOG_FUNC + __ASSERT_DEBUG(aRelatedCommand, Panic(EBTInquiryMgrUnmatchedEvent)); + static_cast(aRelatedCommand); + switch(aEvent.EventCode()) + { + case ERemoteNameReqCompleteEvent: + RemoteNameReqCompleteEvent(aEvent); + break; + + case ERemoteHostSupportedFeaturesNotificationEvent: + RemoteHostSupportedFeaturesNotificationEvent(aEvent); + break; + + case EInquiryCompleteEvent: + InquiryCompleteEvent(aEvent); + break; + + case EInquiryResultEvent: + InquiryResultEvent(aEvent); + break; + + case EInquiryResultwithRSSIEvent: + InquiryResultWithRSSIEvent(aEvent); + break; + + case EExtendedInquiryResultEvent: + ExtendedInquiryResultEvent(aEvent); + break; + + case ECommandCompleteEvent: + CommandCompleteEvent(aEvent); + break; + + case ECommandStatusEvent: + CommandStatusEvent(aEvent, *aRelatedCommand); + break; + + default: + LOG1(_L("Warning!! Unknown Command Event Received (event code:%d)"), aEvent.EventCode()); + __ASSERT_DEBUG(EFalse, Panic(EHCIUnknownCommandEvent)); + break; + } + } + +void CBTInquiryMgr::CommandCompleteEvent(const THCIEventBase& aEvent) + { + LOG_FUNC + const THCICommandCompleteEvent& completeEvent = THCICommandCompleteEvent::Cast(aEvent); + THCIOpcode opcode = completeEvent.CommandOpcode(); + THCIErrorCode hciErr = aEvent.ErrorCode(); + + switch (opcode) + { + case KWriteInquiryModeOpcode: + WriteInquiryModeOpcode(hciErr, aEvent); + break; + + case KReadLocalNameOpcode: + ReadLocalNameOpcode(hciErr, aEvent); + break; + + case KInquiryCancelOpcode: + InquiryCancelOpcode(hciErr, aEvent); + break; + + // These commands are expected to not be handled and so can be safely ignored. + case KRemoteNameRequestCancelOpcode: + LOG1(_L("ignored Command Complete event (opcode: 0x%04x)"), opcode); + break; + + // The commands below would most likely be used by the inquiry manager, however + // they currently are not used. + case KPeriodicInquiryModeOpcode: + case KExitPeriodicInquiryModeOpcode: + case KReadInquiryScanActivityOpcode: + case KWriteInquiryScanActivityOpcode: + case KReadInquiryScanTypeOpcode: + case KWriteInquiryScanTypeOpcode: + case KReadInquiryModeOpcode: + LOG1(_L("Warning!! Unhandled Command Complete Event (opcode:%d)"), opcode); + break; + + default: + LOG2(_L("Error!! Unknown Command complete event! Opcode %d error code %d"), opcode, hciErr); + __ASSERT_DEBUG(EFalse, Panic(EHCIUnknownCommandCompleteOpcode)); + break; + } + } + +void CBTInquiryMgr::CommandStatusEvent(const THCIEventBase& aEvent, const CHCICommandBase& aCommand) + { + LOG_FUNC + + const TCommandStatusEvent& commandStatusEvent = TCommandStatusEvent::Cast(aEvent); + THCIOpcode opcode = commandStatusEvent.CommandOpcode(); + THCIErrorCode hciErr = commandStatusEvent.ErrorCode(); + + __ASSERT_DEBUG(aCommand.Opcode() == opcode, Panic(EBTInquiryMgrMismatchedStatusEvent)); + + if (hciErr != EOK) + { + // got an error + // map onto the event that would have occurred: some things we will have to let the client work out + // e.g. they should check error and connection handle etc. + switch (opcode) + { + case KInquiryOpcode: + InquiryComplete(CHciUtil::SymbianErrorCode(hciErr), 0); + break; + + case KRemoteNameRequestOpcode: + { + const CRemoteNameRequestCommand& remNameReq = static_cast(aCommand); + TInt err = CHciUtil::SymbianErrorCode(hciErr); + TBTDevAddr addr = remNameReq.BDADDR(); + TBTDeviceName8 nullDevName(KNullDesC8); + + RemoteNameResult(err, addr, nullDevName); + iLinkMgrProtocol.PhysicalLinksMgr().RemoteName(hciErr, addr, nullDevName); + } + break; + + default: + // Complete any other commands with an error + CommandCompleteEvent(aEvent); + break; + } + } + } + +void CBTInquiryMgr::WriteInquiryModeOpcode(THCIErrorCode aHciErr, const THCIEventBase& /*aEvent*/) + { + LOG_FUNC + WriteInquiryModeComplete(aHciErr == EOK); + } + +void CBTInquiryMgr::ReadLocalNameOpcode(THCIErrorCode aHciErr, const THCIEventBase& aEvent) + { + LOG_FUNC + if (aHciErr == EOK) + { + const TReadLocalNameCompleteEvent& readLocalNameCompleteEvent = TReadLocalNameCompleteEvent::Cast(aEvent); + TPtrC8 localName = readLocalNameCompleteEvent.LocalName(); + + GetLocalNameComplete(CHciUtil::SymbianErrorCode(aHciErr), localName); + iLinkMgrProtocol.UpdateLocalDeviceName(localName); + } + else + { + _LIT8(KNoName, ""); + GetLocalNameComplete(CHciUtil::SymbianErrorCode(aHciErr), KNoName()); + } + } + +void CBTInquiryMgr::InquiryCancelOpcode(THCIErrorCode aHciErr, const THCIEventBase& /* aEvent */) + { + LOG_FUNC + if (aHciErr == EOK) + { + if (iHWState == ECancellingForNewIAC) + { + SetHWState(EIdle); + DoInquiry(); + } + } + // If an error comes in, it may be because the inquiry complete came in first. In that case, everything + // will be handled by the Inquiry complete logic + } + +// ---------------------------------------------------------------------------- +// Event processing functions +// ---------------------------------------------------------------------------- + +void CBTInquiryMgr::InquiryCompleteEvent(const THCIEventBase& aEvent) + { + LOG_FUNC + InquiryComplete(CHciUtil::SymbianErrorCode(aEvent.ErrorCode()), 0); + } + +void CBTInquiryMgr::InquiryResultEvent(const THCIEventBase& aEvent) + { + LOG_FUNC + + const TInquiryResultEvent& inquiryResultEvent = TInquiryResultEvent::Cast(aEvent); + TInquiryLogEntry entry; + TUint responses = inquiryResultEvent.NumResponses(); + + for (TUint index = 0; index < responses; index++) + { + entry.iSpare = KInqLogEntryStandard; + entry.iPageScanRepetitionMode = inquiryResultEvent.PageScanRepetitionMode(index); + entry.iPageScanPeriodMode = inquiryResultEvent.Reserved1(index); // v1.1 spec, no meaning in v1.2 + entry.iPageScanMode = inquiryResultEvent.Reserved2(index); // v1.1 spec, no meaning in v1.2 + entry.iClockOffset = inquiryResultEvent.ClockOffset(index); + entry.iBdaddr = inquiryResultEvent.BDADDR(index); + entry.iCoD = inquiryResultEvent.ClassOfDevice(index); + InquiryResult(CHciUtil::SymbianErrorCode(aEvent.ErrorCode()), entry); + } + } + +void CBTInquiryMgr::InquiryResultWithRSSIEvent(const THCIEventBase& aEvent) + { + LOG_FUNC + + const TInquiryResultwithRSSIEvent& inquiryResultwithRSSIEvent = TInquiryResultwithRSSIEvent::Cast(aEvent); + TInquiryLogEntryWithRssi entry; + TUint responses = inquiryResultwithRSSIEvent.NumResponses(); + + for (TUint index = 0; index < responses; index++) + { + entry.iSpare = KInqLogEntryWithRssi; + entry.iPageScanRepetitionMode = inquiryResultwithRSSIEvent.PageScanRepetitionMode(index); + entry.iPageScanPeriodMode = 0x00; + entry.iPageScanMode = 0x00; + entry.iClockOffset = inquiryResultwithRSSIEvent.ClockOffset(index); + entry.iBdaddr = inquiryResultwithRSSIEvent.BDADDR(index); + entry.iCoD = inquiryResultwithRSSIEvent.ClassOfDevice(index); + entry.iRssi = inquiryResultwithRSSIEvent.RSSI(index); + InquiryResult(CHciUtil::SymbianErrorCode(aEvent.ErrorCode()), entry); + } + } + +void CBTInquiryMgr::ExtendedInquiryResultEvent(const THCIEventBase& aEvent) + { + LOG_FUNC + + const TExtendedInquiryResultEvent& extendedInquiryResultEvent = TExtendedInquiryResultEvent::Cast(aEvent); + TInquiryLogEntryWithEir entry; + + entry.iSpare = KInqLogEntryWithEir; + entry.iPageScanRepetitionMode = extendedInquiryResultEvent.PageScanRepetitionMode(); + entry.iPageScanPeriodMode = 0x00; + entry.iPageScanMode = 0x00; + entry.iClockOffset = extendedInquiryResultEvent.ClockOffset(); + entry.iBdaddr = extendedInquiryResultEvent.BDADDR(); + entry.iCoD = extendedInquiryResultEvent.ClassOfDevice(); + entry.iRssi = extendedInquiryResultEvent.RSSI(); + entry.iExtendedInquiryResponse = extendedInquiryResultEvent.ExtendedInquiryResponse(); + InquiryResult(CHciUtil::SymbianErrorCode(aEvent.ErrorCode()), entry); + } + +void CBTInquiryMgr::RemoteNameReqCompleteEvent(const THCIEventBase& aEvent) + { + LOG_FUNC + const TRemoteNameReqCompleteEvent& remNameReqCompleteEvent = TRemoteNameReqCompleteEvent::Cast(aEvent); + THCIErrorCode err = aEvent.ErrorCode(); + const TBTDevAddr addr = remNameReqCompleteEvent.BDADDR(); + TPtrC8 buf = remNameReqCompleteEvent.RemoteName(); + + //shorten the name to the proper length so that we don't waste memory + TInt nullTerminator = buf.Locate(0); + if (nullTerminator == KErrNotFound) + { + nullTerminator = buf.Length(); + } + TPtrC8 remoteName = buf.Left(nullTerminator); + + // name requests have two customers... + // inquiry manager + RemoteNameResult(CHciUtil::SymbianErrorCode(err), addr, remoteName); + // phy manager + iLinkMgrProtocol.PhysicalLinksMgr().RemoteName(err, addr, remoteName); + } + +void CBTInquiryMgr::RemoteHostSupportedFeaturesNotificationEvent(const THCIEventBase& aEvent) + { + LOG_FUNC + const TRemoteHostSupportedFeaturesNotificationEvent& remHostSupFeatEvent = TRemoteHostSupportedFeaturesNotificationEvent::Cast(aEvent); + THCIErrorCode err = remHostSupFeatEvent.ErrorCode(); + TBTDevAddr addr = remHostSupFeatEvent.BDADDR(); + TUint64 hostSupFeats = remHostSupFeatEvent.HostSupportedFeatures(); + + RemoteHostSupportedFeatures(CHciUtil::SymbianErrorCode(err), addr, hostSupFeats); + } + + +#ifdef CONNECTION_PREEMPTS_INQUIRY +// ******************************************************************* +// ACL Connecting status subscriber +// ******************************************************************* +/*static*/ CConnectingStatusSubscriber* CConnectingStatusSubscriber::NewL(CBTInquiryMgr& aInquiryMgr) + { + LOG_STATIC_FUNC + CConnectingStatusSubscriber* self = new(ELeave) CConnectingStatusSubscriber(aInquiryMgr); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(); + return self; + } + +CConnectingStatusSubscriber::CConnectingStatusSubscriber(CBTInquiryMgr& aInquiryMgr) +: CActive(CActive::EPriorityStandard), iParent(aInquiryMgr) + { + LOG_FUNC + CActiveScheduler::Add(this); + } + +CConnectingStatusSubscriber::~CConnectingStatusSubscriber() + { + LOG_FUNC + Cancel(); + iProperty.Close(); + } + +void CConnectingStatusSubscriber::DoCancel() + { + LOG_FUNC + iProperty.Cancel(); + } + +void CConnectingStatusSubscriber::ConstructL() + { + LOG_FUNC + User::LeaveIfError(iProperty.Attach(KPropertyUidBluetoothCategory, + KPropertyKeyBluetoothGetConnectingStatus)); + Subscribe(); + } + +void CConnectingStatusSubscriber::Subscribe() + { + LOG_FUNC + iProperty.Subscribe(iStatus); + SetActive(); + } + +/** +This is looking for the connection to complete. The inquiry must be +synchronously cancelled before the CreateConnection command is issued, +but we can resume it when we get round to it. +*/ +void CConnectingStatusSubscriber::RunL() + { + LOG_FUNC + // Subscribe to the next change of state. + Subscribe(); + + TInt isConnecting; + // If this device is currently connecting an ACL suspend the + // inquiry. + iProperty.Get(isConnecting); + + if(!isConnecting) + { + iParent.Resume(); + } + } +#endif // CONNECTION_PREEMPTS_INQUIRY