summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTimur Pocheptsov <timur.pocheptsov@qt.io>2022-01-20 15:12:42 +0100
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2022-01-27 11:46:02 +0000
commit9108693b8ce40e91d91e17217b80c1c84fe3a0e1 (patch)
tree99c08e4f0a13e840327ab33b24fe212b9e5e52e3
parent45f95100ac99b8424221afec5215e033aa4bbcaf (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.mm102
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_macos.mm19
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);