summaryrefslogtreecommitdiff
path: root/src/bluetooth/osx
diff options
context:
space:
mode:
Diffstat (limited to 'src/bluetooth/osx')
-rw-r--r--src/bluetooth/osx/osxbtconnectionmonitor.mm9
-rw-r--r--src/bluetooth/osx/osxbtdeviceinquiry.mm19
-rw-r--r--src/bluetooth/osx/osxbtsdpinquiry.mm160
-rw-r--r--src/bluetooth/osx/osxbtservicerecord.mm50
4 files changed, 208 insertions, 30 deletions
diff --git a/src/bluetooth/osx/osxbtconnectionmonitor.mm b/src/bluetooth/osx/osxbtconnectionmonitor.mm
index f41bbed5..159bceb5 100644
--- a/src/bluetooth/osx/osxbtconnectionmonitor.mm
+++ b/src/bluetooth/osx/osxbtconnectionmonitor.mm
@@ -96,6 +96,15 @@ using namespace QT_NAMESPACE;
if (!device)
return;
+ if (!monitor) {
+ // Rather surprising: monitor == nullptr means we stopped monitoring.
+ // So apparently this thingie is still alive and keeps receiving
+ // notifications.
+ qCWarning(QT_BT_OSX,
+ "Connection notification received in a monitor that was cancelled");
+ return;
+ }
+
QT_BT_MAC_AUTORELEASEPOOL;
// All Obj-C objects are autoreleased.
diff --git a/src/bluetooth/osx/osxbtdeviceinquiry.mm b/src/bluetooth/osx/osxbtdeviceinquiry.mm
index 2fd0d2db..4757331c 100644
--- a/src/bluetooth/osx/osxbtdeviceinquiry.mm
+++ b/src/bluetooth/osx/osxbtdeviceinquiry.mm
@@ -112,11 +112,14 @@ const uint8_t IOBlueoothInquiryLengthS = 15;
m_active = true;
[m_inquiry clearFoundDevices];
+
+ qCDebug(QT_BT_OSX) << "Starting device inquiry with"
+ << IOBlueoothInquiryLengthS << "second timeout limit.";
const IOReturn result = [m_inquiry start];
if (result != kIOReturnSuccess) {
// QtBluetooth will probably convert an error into UnknownError,
// losing the actual information.
- qCWarning(QT_BT_OSX) << "failed with IOKit error code:" << result;
+ qCWarning(QT_BT_OSX) << "device inquiry start failed with IOKit error code:" << result;
m_active = false;
} else {
// Docs say it's 10 s. by default, we set it to 15 s. (see -initWithDelegate:),
@@ -124,11 +127,14 @@ const uint8_t IOBlueoothInquiryLengthS = 15;
watchDog.reset(new QTimer);
watchDog->connect(watchDog.get(), &QTimer::timeout, watchDog.get(), [self]{
qCWarning(QT_BT_OSX, "Manually interrupting IOBluetoothDeviceInquiry");
+ qCDebug(QT_BT_OSX) << "Found devices:" << [m_inquiry foundDevices];
[self stop];
});
watchDog->setSingleShot(true);
- watchDog->setInterval(IOBlueoothInquiryLengthS * 2 * 1000); // Let's make it twice as long.
+ // Let's use 17 s. so that IOBluetooth, if it respects 'inquiryLength'
+ // has a chance to stop before we do:
+ watchDog->setInterval((IOBlueoothInquiryLengthS + 2) * 1000);
watchDog->start();
}
@@ -148,6 +154,8 @@ const uint8_t IOBlueoothInquiryLengthS = 15;
- (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry *)sender
error:(IOReturn)error aborted:(BOOL)aborted
{
+ qCDebug(QT_BT_OSX) << "deviceInquiryComplete, error:" << error
+ << "user-stopped:" << aborted;
if (!m_active)
return;
@@ -158,11 +166,11 @@ const uint8_t IOBlueoothInquiryLengthS = 15;
if (error != kIOReturnSuccess && !aborted) {
// QtBluetooth has not too many error codes, 'UnknownError' is not really
- // useful, report the actual error code here:
+ // useful, log the actual error code here:
qCWarning(QT_BT_OSX) << "IOKit error code: " << error;
- m_delegate->error(error);
// Let watchDog to stop it, calling -stop at timeout, otherwise,
- // it looks like inquiry continues and keeps reporting new devices found.
+ // it looks like inquiry continues even after this error and
+ // keeps reporting new devices found.
} else {
// Either a normal completion or from a timer slot.
watchDog.reset();
@@ -174,6 +182,7 @@ const uint8_t IOBlueoothInquiryLengthS = 15;
- (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry *)sender
device:(IOBluetoothDevice *)device
{
+ qCDebug(QT_BT_OSX) << "deviceInquiryDeviceFound:" << [device nameOrAddress];
if (sender != m_inquiry) // Can never happen in the current version.
return;
diff --git a/src/bluetooth/osx/osxbtsdpinquiry.mm b/src/bluetooth/osx/osxbtsdpinquiry.mm
index a2b02b1a..e60b9787 100644
--- a/src/bluetooth/osx/osxbtsdpinquiry.mm
+++ b/src/bluetooth/osx/osxbtsdpinquiry.mm
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 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,17 +260,41 @@ 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)");
+ qCDebug(QT_BT_OSX) << "couldn't connect to device" << [device nameOrAddress]
+ << ", ending SDP inquiry.";
+
+ // Stop the watchdog and close the connection as otherwise there could be
+ // later "connectionComplete" callbacks
+ connectionWatchdog->stop();
+ [device closeConnection];
+
+ delegate->SDPInquiryError(device, kIOReturnTimeout);
+ [device release];
+ device = nil;
+ isActive = false;
+}
+
- (IOReturn)performSDPQueryWithDevice:(const QBluetoothAddress &)address
filters:(const QList<QBluetoothUuid> &)qtFilters
{
Q_ASSERT_X(!isActive, Q_FUNC_INFO, "SDP query in progress");
Q_ASSERT_X(!address.isNull(), Q_FUNC_INFO, "invalid target device address");
+ qCDebug(QT_BT_OSX) << "Starting and SDP inquiry for address:" << address;
QT_BT_MAC_AUTORELEASEPOOL;
// 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";
@@ -281,7 +323,65 @@ using namespace OSXBluetooth;
ObjCScopedPointer<IOBluetoothDevice> oldDevice(device);
device = newDevice.data();
+ qCDebug(QT_BT_OSX) << "Device" << [device nameOrAddress] << "connected:"
+ << bool([device isConnected]) << "paired:" << bool([device isPaired]);
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 the device was not yet connected, connect it first
+ if (![device isConnected]) {
+ qCDebug(QT_BT_OSX) << "Device" << [device nameOrAddress]
+ << "is not connected, connecting it first";
+ result = [device openConnection:self];
+ // The connection may succeed immediately. But if it didn't, start a connection timer
+ // which has two guardian roles:
+ // 1. Guard against connect attempt taking too long time
+ // 2. Sometimes on Monterey the callback indicating "connection completion" is
+ // not received even though the connection has in fact succeeded
+ if (![device isConnected]) {
+ qCDebug(QT_BT_OSX) << "Starting connection monitor for device"
+ << [device nameOrAddress] << "with timeout limit of"
+ << basebandConnectTimeoutMS/1000 << "seconds.";
+ connectionWatchdog.reset(new QTimer);
+ connectionWatchdog->setSingleShot(false);
+ QObject::connect(connectionWatchdog.get(), &QTimer::timeout,
+ connectionWatchdog.get(),
+ [self] () {
+ qCDebug(QT_BT_OSX) << "Connection monitor timeout for device:"
+ << [device nameOrAddress]
+ << ", connected:" << bool([device isConnected]);
+ // Device can sometimes get properly connected without IOBluetooth
+ // calling the connectionComplete callback, so we check the status here
+ if ([device isConnected])
+ [self connectionComplete:device status:kIOReturnSuccess];
+ else
+ [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");
+ 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,25 +391,53 @@ 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.
+ qCDebug(QT_BT_OSX) << "connectionComplete for device" << [aDevice nameOrAddress]
+ << "with status:" << status;
+ if (aDevice != device) {
+ // Connection was previously cancelled, probably, due to the timeout.
+ return;
+ }
+
+ // The connectionComplete may be invoked by either the IOBluetooth callback or our
+ // connection watchdog. In either case stop the watchdog if it exists
+ if (connectionWatchdog)
+ connectionWatchdog->stop();
+ 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 device, 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
{
+ qCDebug(QT_BT_OSX) << "sdpQueryComplete for device:" << [aDevice nameOrAddress]
+ << "with status:" << status;
// Can happen - there is no legal way to cancel an SDP query,
// after the 'reset' device can never be
// the same as the cancelled one.
@@ -320,6 +448,15 @@ using namespace OSXBluetooth;
isActive = false;
+ // If we used the manual connection establishment, close the
+ // connection here. Otherwise the IOBluetooth may call stray
+ // connectionComplete or sdpQueryCompletes
+ if (connectionWatchdog) {
+ qCDebug(QT_BT_OSX) << "Closing the connection established for SDP inquiry.";
+ connectionWatchdog.reset();
+ [device closeConnection];
+ }
+
if (status != kIOReturnSuccess)
delegate->SDPInquiryError(aDevice, status);
else
@@ -327,3 +464,4 @@ using namespace OSXBluetooth;
}
@end
+
diff --git a/src/bluetooth/osx/osxbtservicerecord.mm b/src/bluetooth/osx/osxbtservicerecord.mm
index 23d55d13..2d868f63 100644
--- a/src/bluetooth/osx/osxbtservicerecord.mm
+++ b/src/bluetooth/osx/osxbtservicerecord.mm
@@ -146,7 +146,7 @@ void add_attribute(const QVariant &var, AttributeId key, Dictionary dict)
return;
const Number num(variant_to_nsnumber<ValueType>(var));
- [dict setObject:num forKey:[NSString stringWithFormat:@"%d", int(key)]];
+ [dict setObject:num forKey:[NSString stringWithFormat:@"%x", int(key)]];
}
template<>
@@ -160,7 +160,7 @@ void add_attribute<QString>(const QVariant &var, AttributeId key, Dictionary dic
const QString string(var.value<QString>());
if (string.length()) {
if (NSString *const nsString = string.toNSString())
- [dict setObject:nsString forKey:[NSString stringWithFormat:@"%d", int(key)]];
+ [dict setObject:nsString forKey:[NSString stringWithFormat:@"%x", int(key)]];
}
}
@@ -173,7 +173,7 @@ void add_attribute<QBluetoothUuid>(const QVariant &var, AttributeId key, Diction
return;
SDPUUid ioUUID(iobluetooth_uuid(var.value<QBluetoothUuid>()));
- [dict setObject:ioUUID forKey:[NSString stringWithFormat:@"%d", int(key)]];
+ [dict setObject:ioUUID forKey:[NSString stringWithFormat:@"%x", int(key)]];
}
template<>
@@ -207,6 +207,25 @@ void add_attribute(const QVariant &var, NSMutableArray *list)
}
template<>
+void add_attribute<unsigned short>(const QVariant &var, NSMutableArray *list)
+{
+ Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)");
+
+ if (!var.canConvert<unsigned short>())
+ return;
+
+ const Number num(variant_to_nsnumber<unsigned short>(var));
+
+ NSDictionary* dict = @{
+ @"DataElementType" : [NSNumber numberWithInt:1],
+ @"DataElementSize" : [NSNumber numberWithInt:2],
+ @"DataElementValue" : num
+ };
+
+ [list addObject: dict];
+}
+
+template<>
void add_attribute<QString>(const QVariant &var, NSMutableArray *list)
{
Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)");
@@ -273,7 +292,7 @@ void add_rfcomm_protocol_descriptor_list(uint16 channelID, Dictionary dict)
[rfcommList addObject:rfcommDict];
[descriptorList addObject:rfcommList];
- [dict setObject:descriptorList forKey:[NSString stringWithFormat:@"%d",
+ [dict setObject:descriptorList forKey:[NSString stringWithFormat:@"%x",
kBluetoothSDPAttributeIdentifierProtocolDescriptorList]];
}
@@ -299,7 +318,7 @@ void add_l2cap_protocol_descriptor_list(uint16 psm, Dictionary dict)
[l2capList addObject:l2capDict];
[descriptorList addObject:l2capList];
- [dict setObject:descriptorList forKey:[NSString stringWithFormat:@"%d",
+ [dict setObject:descriptorList forKey:[NSString stringWithFormat:@"%x",
kBluetoothSDPAttributeIdentifierProtocolDescriptorList]];
}
@@ -310,10 +329,10 @@ bool add_attribute(const QVariant &var, AttributeId key, NSMutableArray *list)
if (var.canConvert<Sequence>())
return false;
- if (var.canConvert<QString>()) {
+ if (var.type() == QVariant::String) {
//ServiceName, ServiceDescription, ServiceProvider.
add_attribute<QString>(var, list);
- } else if (var.canConvert<QBluetoothUuid>()) {
+ } else if (var.userType() == qMetaTypeId<QBluetoothUuid>()) {
add_attribute<QBluetoothUuid>(var, list);
} else {
// Here we need 'key' to understand the type.
@@ -325,6 +344,9 @@ bool add_attribute(const QVariant &var, AttributeId key, NSMutableArray *list)
case QSInfo::ServiceInfoTimeToLive:
add_attribute<unsigned>(var, list);
break;
+ case QSInfo::BluetoothProfileDescriptorList:
+ add_attribute<unsigned short>(var, list);
+ break;
case QSInfo::ServiceAvailability:
add_attribute<unsigned char>(var, list);
break;
@@ -348,10 +370,10 @@ bool add_attribute(const QBluetoothServiceInfo &serviceInfo, AttributeId key, Di
if (var.canConvert<Sequence>())
return false;
- if (var.canConvert<QString>()) {
+ if (var.type() == QVariant::String) {
//ServiceName, ServiceDescription, ServiceProvider.
add_attribute<QString>(var, key, dict);
- } else if (var.canConvert<QBluetoothUuid>()) {
+ } else if (var.userType() == qMetaTypeId<QBluetoothUuid>()) {
add_attribute<QBluetoothUuid>(serviceInfo.attribute(key), key, dict);
} else {
// We can have different integer types actually, so I have to check
@@ -385,14 +407,15 @@ bool add_sequence_attribute(const QVariant &var, AttributeId key, NSMutableArray
if (var.isNull() || !var.canConvert<Sequence>())
return false;
+ NSMutableArray *const nested = [NSMutableArray array];
+ [list addObject:nested];
+
const Sequence sequence(var.value<Sequence>());
for (const QVariant &var : sequence) {
if (var.canConvert<Sequence>()) {
- NSMutableArray *const nested = [NSMutableArray array];
add_sequence_attribute(var, key, nested);
- [list addObject:nested];
} else {
- add_attribute(var, key, list);
+ add_attribute(var, key, nested);
}
}
@@ -415,8 +438,7 @@ bool add_sequence_attribute(const QBluetoothServiceInfo &serviceInfo, AttributeI
if (!add_sequence_attribute(element, key, list))
add_attribute(element, key, list);
}
- [dict setObject:list forKey:[NSString stringWithFormat:@"%d", int(key)]];
-
+ [dict setObject:list forKey:[NSString stringWithFormat:@"%x", int(key)]];
return true;
}