diff options
author | Timur Pocheptsov <timur.pocheptsov@qt.io> | 2022-01-20 15:12:42 +0100 |
---|---|---|
committer | Timur Pocheptsov <timur.pocheptsov@qt.io> | 2022-02-10 11:56:51 +0100 |
commit | 4fb1b482d88ddc6c5101bf7922e32199ae6bd52a (patch) | |
tree | 1497fe2280151641f57c0bf4f1a8e0439feeacc9 | |
parent | 6c59b203d372acfc340a0e8abf378e9b9a9efc8d (diff) | |
download | qtconnectivity-4fb1b482d88ddc6c5101bf7922e32199ae6bd52a.tar.gz |
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)
-rw-r--r-- | src/bluetooth/osx/osxbtsdpinquiry.mm | 112 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothservicediscoveryagent_osx.mm | 18 |
2 files changed, 119 insertions, 11 deletions
diff --git a/src/bluetooth/osx/osxbtsdpinquiry.mm b/src/bluetooth/osx/osxbtsdpinquiry.mm index 1645f959..481a077b 100644 --- a/src/bluetooth/osx/osxbtsdpinquiry.mm +++ b/src/bluetooth/osx/osxbtsdpinquiry.mm @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2021 The Qt Company Ltd. +** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtBluetooth module of the Qt Toolkit. @@ -43,8 +43,12 @@ #include "osxbtutility_p.h" #include "btdelegates_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 OSXBluetooth { namespace { +const int basebandConnectTimeoutMS = 20000; + QBluetoothUuid sdp_element_to_uuid(IOBluetoothSDPDataElement *element) { QT_BT_MAC_AUTORELEASEPOOL; @@ -78,11 +84,20 @@ QVector<QBluetoothUuid> extract_service_class_ID_list(IOBluetoothSDPServiceRecor QT_BT_MAC_AUTORELEASEPOOL; IOBluetoothSDPDataElement *const idList = [record getAttributeDataElement:kBluetoothSDPAttributeIdentifierServiceClassIDList]; - if (!idList || [idList getTypeDescriptor] != kBluetoothSDPDataElementTypeDataElementSequence) - return {}; QVector<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()) @@ -200,7 +215,7 @@ QVector<QBluetoothUuid> extract_services_uuids(IOBluetoothDevice *device) return uuids; } -} +} // namespace OSXBluetooth QT_END_NAMESPACE @@ -213,6 +228,9 @@ using namespace OSXBluetooth; QT_PREPEND_NAMESPACE(DarwinBluetooth::SDPInquiryDelegate) *delegate; IOBluetoothDevice *device; bool isActive; + + // Needed to workaround a broken SDP on Monterey: + std::unique_ptr<QTimer> connectionWatchdog; } - (id)initWithDelegate:(DarwinBluetooth::SDPInquiryDelegate *)aDelegate @@ -242,6 +260,21 @@ using namespace OSXBluetooth; 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); + + Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)"); + + delegate->SDPInquiryError(device, kIOReturnTimeout); + [device release]; + device = nil; + isActive = false; +} + - (IOReturn)performSDPQueryWithDevice:(const QBluetoothAddress &)address filters:(const QList<QBluetoothUuid> &)qtFilters { @@ -252,7 +285,8 @@ using namespace OSXBluetooth; // 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]); if (!array) { qCCritical(QT_BT_OSX) << "failed to allocate an uuid filter"; @@ -282,6 +316,40 @@ using namespace OSXBluetooth; device = newDevice.data(); 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_OSX, "failed to start an SDP query"); + connectionWatchdog.reset(); + device = oldDevice.take(); + } else { + newDevice.take(); + isActive = true; + } + + return result; + } // Monterey's code path. + if (qtFilters.size()) result = [device performSDPQuery:self uuids:array]; else @@ -291,21 +359,42 @@ using namespace OSXBluetooth; qCCritical(QT_BT_OSX) << "failed to start an SDP query"; device = oldDevice.take(); } else { - isActive = true; newDevice.take(); + isActive = true; } return result; } -- (void)stopSDPQuery +- (void)connectionComplete:(IOBluetoothDevice *)aDevice status:(IOReturn)status { - // 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. + 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_OSX, "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 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 release]; device = nil; + isActive = false; + connectionWatchdog.reset(); } - (void)sdpQueryComplete:(IOBluetoothDevice *)aDevice status:(IOReturn)status @@ -327,3 +416,4 @@ using namespace OSXBluetooth; } @end + diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm b/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm index d1194c49..8469dd59 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm +++ b/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm @@ -47,6 +47,7 @@ #include "osx/osxbluetooth_p.h" #include "osx/uistrings_p.h" +#include <QtCore/qoperatingsystemversion.h> #include <QtCore/qloggingcategory.h> #include <QtCore/qscopedpointer.h> #include <QtCore/qstring.h> @@ -158,6 +159,23 @@ 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); |