summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTimur Pocheptsov <timur.pocheptsov@qt.io>2022-01-20 15:12:42 +0100
committerTimur Pocheptsov <timur.pocheptsov@qt.io>2022-02-10 11:56:51 +0100
commit4fb1b482d88ddc6c5101bf7922e32199ae6bd52a (patch)
tree1497fe2280151641f57c0bf4f1a8e0439feeacc9
parent6c59b203d372acfc340a0e8abf378e9b9a9efc8d (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)
-rw-r--r--src/bluetooth/osx/osxbtsdpinquiry.mm112
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_osx.mm18
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);