10#include <QtCore/qoperatingsystemversion.h>
11#include <QtCore/qvariant.h>
12#include <QtCore/qstring.h>
13#include <QtCore/qtimer.h>
23const int basebandConnectTimeoutMS = 20000;
25QBluetoothUuid sdp_element_to_uuid(IOBluetoothSDPDataElement *element)
29 if (!element || [element getTypeDescriptor] != kBluetoothSDPDataElementTypeUUID)
32 return qt_uuid([[element getUUIDValue] getUUIDWithLength:16]);
35QBluetoothUuid extract_service_ID(IOBluetoothSDPServiceRecord *record)
41 return sdp_element_to_uuid([record getAttributeDataElement:kBluetoothSDPAttributeIdentifierServiceID]);
44QList<QBluetoothUuid> extract_service_class_ID_list(IOBluetoothSDPServiceRecord *record)
50 IOBluetoothSDPDataElement *
const idList = [record getAttributeDataElement:kBluetoothSDPAttributeIdentifierServiceClassIDList];
52 QList<QBluetoothUuid> uuids;
57 if ([idList getTypeDescriptor] == kBluetoothSDPDataElementTypeDataElementSequence)
58 arr = [idList getArrayValue];
59 else if ([idList getTypeDescriptor] == kBluetoothSDPDataElementTypeUUID)
65 for (IOBluetoothSDPDataElement *dataElement in arr) {
66 const auto qtUuid = sdp_element_to_uuid(dataElement);
68 uuids.push_back(qtUuid);
74QBluetoothServiceInfo::Sequence service_class_ID_list_to_sequence(
const QList<QBluetoothUuid> &uuids)
79 QBluetoothServiceInfo::Sequence sequence;
80 for (
const auto &uuid : uuids) {
81 Q_ASSERT(!uuid.isNull());
82 sequence.append(QVariant::fromValue(uuid));
92 Q_ASSERT_X(dataElement, Q_FUNC_INFO,
"invalid data element (nil)");
99 const BluetoothSDPDataElementTypeDescriptor typeDescriptor = [dataElement getTypeDescriptor];
101 switch (typeDescriptor) {
102 case kBluetoothSDPDataElementTypeNil:
104 case kBluetoothSDPDataElementTypeUnsignedInt:
105 return [[dataElement getNumberValue] unsignedIntValue];
106 case kBluetoothSDPDataElementTypeSignedInt:
107 return [[dataElement getNumberValue] intValue];
108 case kBluetoothSDPDataElementTypeUUID:
109 return QVariant::fromValue(sdp_element_to_uuid(dataElement));
110 case kBluetoothSDPDataElementTypeString:
111 case kBluetoothSDPDataElementTypeURL:
112 return QString::fromNSString([dataElement getStringValue]);
113 case kBluetoothSDPDataElementTypeBoolean:
114 return [[dataElement getNumberValue] boolValue];
115 case kBluetoothSDPDataElementTypeDataElementSequence:
116 case kBluetoothSDPDataElementTypeDataElementAlternative:
118 QBluetoothServiceInfo::Sequence sequence;
119 NSArray *
const arr = [dataElement getArrayValue];
120 for (IOBluetoothSDPDataElement *element in arr)
121 sequence.append(extract_attribute_value(element));
123 return QVariant::fromValue(sequence);
139 NSDictionary *
const attributes = record.attributes;
140 NSEnumerator *
const keys = attributes.keyEnumerator;
141 for (NSNumber *key in keys) {
142 const quint16 attributeID = [key unsignedShortValue];
143 IOBluetoothSDPDataElement *
const element = [attributes objectForKey:key];
144 const QVariant attributeValue = DarwinBluetooth::extract_attribute_value(element);
145 serviceInfo.setAttribute(attributeID, attributeValue);
148 const QBluetoothUuid serviceUuid = extract_service_ID(record);
149 if (!serviceUuid.isNull())
150 serviceInfo.setServiceUuid(serviceUuid);
152 const QList<QBluetoothUuid> uuids(extract_service_class_ID_list(record));
153 const auto sequence = service_class_ID_list_to_sequence(uuids);
154 if (!sequence.isEmpty())
155 serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, sequence);
160 QList<QBluetoothUuid> uuids;
165 if (!device || !device.services)
168 NSArray *
const records = device.services;
169 for (IOBluetoothSDPServiceRecord *record in records) {
170 const QBluetoothUuid serviceID = extract_service_ID(record);
171 if (!serviceID.isNull())
172 uuids.push_back(serviceID);
174 const QList<QBluetoothUuid> idList(extract_service_class_ID_list(record));
176 uuids.append(idList);
188using namespace DarwinBluetooth;
190@implementation DarwinBTSDPInquiry
192 QT_PREPEND_NAMESPACE(DarwinBluetooth::SDPInquiryDelegate) *delegate;
193 ObjCScopedPointer<IOBluetoothDevice> device;
197 std::unique_ptr<QTimer> connectionWatchdog;
200- (
id)initWithDelegate:(DarwinBluetooth::SDPInquiryDelegate *)aDelegate
202 Q_ASSERT_X(aDelegate, Q_FUNC_INFO,
"invalid delegate (null)");
204 if (self = [super init]) {
205 delegate = aDelegate;
218- (IOReturn)performSDPQueryWithDevice:(
const QBluetoothAddress &)address
220 Q_ASSERT_X(!isActive, Q_FUNC_INFO,
"SDP query in progress");
222 QList<QBluetoothUuid> emptyFilter;
223 return [self performSDPQueryWithDevice:address filters:emptyFilter];
226- (
void)interruptSDPQuery
229 Q_ASSERT(connectionWatchdog.get());
231 Q_ASSERT(device.get());
233 Q_ASSERT_X(delegate, Q_FUNC_INFO,
"invalid delegate (null)");
234 qCDebug(QT_BT_DARWIN) <<
"couldn't connect to device" << [device nameOrAddress]
235 <<
", ending SDP inquiry.";
239 connectionWatchdog->stop();
240 [device closeConnection];
242 delegate->SDPInquiryError(device, kIOReturnTimeout);
247- (IOReturn)performSDPQueryWithDevice:(
const QBluetoothAddress &)address
248 filters:(
const QList<QBluetoothUuid> &)qtFilters
250 Q_ASSERT_X(!isActive, Q_FUNC_INFO,
"SDP query in progress");
251 Q_ASSERT_X(!address.isNull(), Q_FUNC_INFO,
"invalid target device address");
252 qCDebug(QT_BT_DARWIN) <<
"Starting and SDP inquiry for address:" << address;
257 ObjCScopedPointer<NSMutableArray> array;
258 if (QOperatingSystemVersion::current() <= QOperatingSystemVersion::MacOSBigSur
259 && qtFilters.size()) {
260 array.reset([[NSMutableArray alloc] init], RetainPolicy::noInitialRetain);
262 qCCritical(QT_BT_DARWIN) <<
"failed to allocate an uuid filter";
263 return kIOReturnError;
266 for (
const QBluetoothUuid &qUuid : qtFilters) {
267 ObjCStrongReference<IOBluetoothSDPUUID> uuid(iobluetooth_uuid(qUuid));
269 [array addObject:uuid];
272 if (qsizetype([array count]) != qtFilters.size()) {
273 qCCritical(QT_BT_DARWIN) <<
"failed to create an uuid filter";
274 return kIOReturnError;
278 const BluetoothDeviceAddress iobtAddress(iobluetooth_address(address));
279 device.reset([IOBluetoothDevice deviceWithAddress:&iobtAddress], RetainPolicy::doInitialRetain);
281 qCCritical(QT_BT_DARWIN) <<
"failed to create an IOBluetoothDevice object";
282 return kIOReturnError;
284 qCDebug(QT_BT_DARWIN) <<
"Device" << [device nameOrAddress] <<
"connected:"
285 <<
bool([device isConnected]) <<
"paired:" <<
bool([device isPaired]);
287 IOReturn result = kIOReturnSuccess;
289 if (QOperatingSystemVersion::current() > QOperatingSystemVersion::MacOSBigSur) {
298 if (![device isConnected]) {
299 qCDebug(QT_BT_DARWIN) <<
"Device" << [device nameOrAddress]
300 <<
"is not connected, connecting it first";
301 result = [device openConnection:self];
307 if (![device isConnected]) {
308 qCDebug(QT_BT_DARWIN) <<
"Starting connection monitor for device"
309 << [device nameOrAddress] <<
"with timeout limit of"
310 << basebandConnectTimeoutMS/1000 <<
"seconds.";
311 connectionWatchdog.reset(
new QTimer);
312 connectionWatchdog->setSingleShot(
false);
313 QObject::connect(connectionWatchdog.get(), &QTimer::timeout,
314 connectionWatchdog.get(),
316 qCDebug(QT_BT_DARWIN) <<
"Connection monitor timeout for device:"
317 << [device nameOrAddress]
318 <<
", connected:" <<
bool([device isConnected]);
321 if ([device isConnected])
322 [self connectionComplete:device status:kIOReturnSuccess];
324 [self interruptSDPQuery];
326 connectionWatchdog->start(basebandConnectTimeoutMS);
330 if ([device isConnected])
331 result = [device performSDPQuery:self];
333 if (result != kIOReturnSuccess) {
334 qCCritical(QT_BT_DARWIN,
"failed to start an SDP query");
343 if (qtFilters.size())
344 result = [device performSDPQuery:self uuids:array];
346 result = [device performSDPQuery:self];
348 if (result != kIOReturnSuccess) {
349 qCCritical(QT_BT_DARWIN) <<
"failed to start an SDP query";
358- (
void)connectionComplete:(IOBluetoothDevice *)aDevice status:(IOReturn)status
360 qCDebug(QT_BT_DARWIN) <<
"connectionComplete for device" << [aDevice nameOrAddress]
361 <<
"with status:" << status;
362 if (aDevice != device) {
369 if (connectionWatchdog)
370 connectionWatchdog->stop();
372 if (status == kIOReturnSuccess)
373 status = [aDevice performSDPQuery:self];
375 if (status != kIOReturnSuccess) {
377 qCWarning(QT_BT_DARWIN,
"failed to open connection or start an SDP query");
378 Q_ASSERT_X(delegate, Q_FUNC_INFO,
"invalid delegate (null)");
379 delegate->SDPInquiryError(aDevice, status);
390 connectionWatchdog.reset();
393- (
void)sdpQueryComplete:(IOBluetoothDevice *)aDevice status:(IOReturn)status
395 qCDebug(QT_BT_DARWIN) <<
"sdpQueryComplete for device:" << [aDevice nameOrAddress]
396 <<
"with status:" << status;
400 if (device != aDevice)
403 Q_ASSERT_X(delegate, Q_FUNC_INFO,
"invalid delegate (null)");
410 if (connectionWatchdog) {
411 qCDebug(QT_BT_DARWIN) <<
"Closing the connection established for SDP inquiry.";
412 connectionWatchdog.reset();
413 [device closeConnection];
416 if (status != kIOReturnSuccess)
417 delegate->SDPInquiryError(aDevice, status);
419 delegate->SDPInquiryFinished(aDevice);
#define QT_BT_MAC_AUTORELEASEPOOL
QList< QBluetoothUuid > extract_services_uuids(IOBluetoothDevice *device)
void extract_service_record(IOBluetoothSDPServiceRecord *record, QBluetoothServiceInfo &serviceInfo)
QVariant extract_attribute_value(IOBluetoothSDPDataElement *dataElement)