diff options
author | Timur Pocheptsov <timur.pocheptsov@qt.io> | 2022-01-20 15:12:42 +0100 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2022-01-27 11:46:02 +0000 |
commit | 9108693b8ce40e91d91e17217b80c1c84fe3a0e1 (patch) | |
tree | 99c08e4f0a13e840327ab33b24fe212b9e5e52e3 | |
parent | 45f95100ac99b8424221afec5215e033aa4bbcaf (diff) |
IOBluetooth: fix SDP inquiry broken by Monterey
-performSDPQuery:self - does not open a baseband connection anymore to a
device, if not connected yet (despite it's documented to do so). Now we
have to ensure isConnected == true first. An attempt to connect is not guaranteed
to ever complete (with/out errors), so we need a timeout logic in place.
-performSDPQuery:self uuids:filter is simply a noop function on Monterey,
we have to filter services manually, after SDP query finished.
In addition, it was discovered that service class ID list attribute
(its id is 0x0001) as reported by Apple may have a non-sequence type,
but UUID itself instead.
Fixes: QTBUG-99673
Change-Id: I5464faa0a2eb350f738be0531e726f464c6f2caa
Reviewed-by: Juha Vuolle <juha.vuolle@insta.fi>
(cherry picked from commit f00ea686c841e14a89bd776ac3b86766a4980bb3)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r-- | src/bluetooth/darwin/btsdpinquiry.mm | 102 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothservicediscoveryagent_macos.mm | 19 |
2 files changed, 114 insertions, 7 deletions
diff --git a/src/bluetooth/darwin/btsdpinquiry.mm b/src/bluetooth/darwin/btsdpinquiry.mm index cdae80da..025f25bb 100644 --- a/src/bluetooth/darwin/btsdpinquiry.mm +++ b/src/bluetooth/darwin/btsdpinquiry.mm @@ -43,8 +43,12 @@ #include "btdelegates_p.h" #include "btutility_p.h" +#include <QtCore/qoperatingsystemversion.h> #include <QtCore/qvariant.h> #include <QtCore/qstring.h> +#include <QtCore/qtimer.h> + +#include <memory> QT_BEGIN_NAMESPACE @@ -52,6 +56,8 @@ namespace DarwinBluetooth { namespace { +const int basebandConnectTimeoutMS = 20000; + QBluetoothUuid sdp_element_to_uuid(IOBluetoothSDPDataElement *element) { QT_BT_MAC_AUTORELEASEPOOL; @@ -78,11 +84,20 @@ QList<QBluetoothUuid> extract_service_class_ID_list(IOBluetoothSDPServiceRecord QT_BT_MAC_AUTORELEASEPOOL; IOBluetoothSDPDataElement *const idList = [record getAttributeDataElement:kBluetoothSDPAttributeIdentifierServiceClassIDList]; - if (!idList || [idList getTypeDescriptor] != kBluetoothSDPDataElementTypeDataElementSequence) - return {}; QList<QBluetoothUuid> uuids; - NSArray *const arr = [idList getArrayValue]; + if (!idList) + return uuids; + + NSArray *arr = nil; + if ([idList getTypeDescriptor] == kBluetoothSDPDataElementTypeDataElementSequence) + arr = [idList getArrayValue]; + else if ([idList getTypeDescriptor] == kBluetoothSDPDataElementTypeUUID) + arr = @[idList]; + + if (!arr) + return uuids; + for (IOBluetoothSDPDataElement *dataElement in arr) { const auto qtUuid = sdp_element_to_uuid(dataElement); if (!qtUuid.isNull()) @@ -213,6 +228,9 @@ using namespace DarwinBluetooth; QT_PREPEND_NAMESPACE(DarwinBluetooth::SDPInquiryDelegate) *delegate; ObjCScopedPointer<IOBluetoothDevice> device; bool isActive; + + // Needed to workaround a broken SDP on Monterey: + std::unique_ptr<QTimer> connectionWatchdog; } - (id)initWithDelegate:(DarwinBluetooth::SDPInquiryDelegate *)aDelegate @@ -241,6 +259,20 @@ using namespace DarwinBluetooth; return [self performSDPQueryWithDevice:address filters:emptyFilter]; } +- (void)interruptSDPQuery +{ + // To be only executed on timer. + Q_ASSERT(connectionWatchdog.get()); + // If device was reset, so the timer should be, we can never be here then. + Q_ASSERT(device.get()); + + Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)"); + + delegate->SDPInquiryError(device, kIOReturnTimeout); + device.reset(); + isActive = false; +} + - (IOReturn)performSDPQueryWithDevice:(const QBluetoothAddress &)address filters:(const QList<QBluetoothUuid> &)qtFilters { @@ -251,7 +283,8 @@ using namespace DarwinBluetooth; // We first try to allocate "filters": ObjCScopedPointer<NSMutableArray> array; - if (qtFilters.size()) { + if (QOperatingSystemVersion::current() <= QOperatingSystemVersion::MacOSBigSur + && qtFilters.size()) { // See the comment about filters on Monterey below. array.reset([[NSMutableArray alloc] init], RetainPolicy::noInitialRetain); if (!array) { qCCritical(QT_BT_DARWIN) << "failed to allocate an uuid filter"; @@ -278,6 +311,39 @@ using namespace DarwinBluetooth; } IOReturn result = kIOReturnSuccess; + + if (QOperatingSystemVersion::current() > QOperatingSystemVersion::MacOSBigSur) { + // SDP query on Monterey does not follow its own documented/expected behavior: + // - a simple performSDPQuery was previously ensuring baseband connection + // to be opened, now it does not do so, instead logs a warning and returns + // immediately. + // - a version with UUID filters simply does nothing except it immediately + // returns kIOReturnSuccess. + + if (![device isConnected]) { + result = [device openConnection:self]; + connectionWatchdog.reset(new QTimer); + connectionWatchdog->setSingleShot(true); + QObject::connect(connectionWatchdog.get(), &QTimer::timeout, + connectionWatchdog.get(), + [self]{[self interruptSDPQuery];}); + connectionWatchdog->start(basebandConnectTimeoutMS); + } + + if ([device isConnected]) + result = [device performSDPQuery:self]; + + if (result != kIOReturnSuccess) { + qCCritical(QT_BT_DARWIN, "failed to start an SDP query"); + device.reset(); + connectionWatchdog.reset(); + } else { + isActive = true; + } + + return result; + } // Monterey's code path. + if (qtFilters.size()) result = [device performSDPQuery:self uuids:array]; else @@ -293,12 +359,34 @@ using namespace DarwinBluetooth; return result; } +- (void)connectionComplete:(IOBluetoothDevice *)aDevice status:(IOReturn)status +{ + if (aDevice != device) { + // Connection was previously cancelled, probably, due to the timeout. + return; + } + + connectionWatchdog.reset(); + + if (status == kIOReturnSuccess) + status = [aDevice performSDPQuery:self]; + + if (status != kIOReturnSuccess) { + isActive = false; + qCWarning(QT_BT_DARWIN, "failed to open connection or start an SDP query"); + Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)"); + delegate->SDPInquiryError(aDevice, status); + } +} + - (void)stopSDPQuery { - // There is no API to stop it, - // but there is a 'stop' member-function in Qt and - // after it's called sdpQueryComplete must be somehow ignored. + // There is no API to stop it SDP on deice, but there is a 'stop' + // member-function in Qt and after it's called sdpQueryComplete + // must be somehow ignored (device != aDevice in a callback). device.reset(); + isActive = false; + connectionWatchdog.reset(); } - (void)sdpQueryComplete:(IOBluetoothDevice *)aDevice status:(IOReturn)status diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_macos.mm b/src/bluetooth/qbluetoothservicediscoveryagent_macos.mm index 657bb474..f5eff9f4 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent_macos.mm +++ b/src/bluetooth/qbluetoothservicediscoveryagent_macos.mm @@ -46,6 +46,7 @@ #include "darwin/btutility_p.h" #include "darwin/uistrings_p.h" +#include <QtCore/qoperatingsystemversion.h> #include <QtCore/qloggingcategory.h> #include <QtCore/qscopedpointer.h> #include <QtCore/qstring.h> @@ -159,6 +160,24 @@ void QBluetoothServiceDiscoveryAgentPrivate::SDPInquiryFinished(void *generic) if (!serviceInfo.isValid()) continue; + if (QOperatingSystemVersion::current() > QOperatingSystemVersion::MacOSBigSur + && uuidFilter.size()) { + const auto &serviceId = serviceInfo.serviceUuid(); + bool match = !serviceId.isNull() && uuidFilter.contains(serviceId); + if (!match) { + const auto &classUuids = serviceInfo.serviceClassUuids(); + for (const auto &uuid : classUuids) { + if (uuidFilter.contains(uuid)) { + match = true; + break; + } + } + if (!match) + continue; + } + } + + if (!isDuplicatedService(serviceInfo)) { discoveredServices.append(serviceInfo); emit q_ptr->serviceDiscovered(serviceInfo); |