Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
btsdpinquiry.mm
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5#include "btsdpinquiry_p.h"
6#include "qbluetoothuuid.h"
7#include "btdelegates_p.h"
8#include "btutility_p.h"
9
10#include <QtCore/qoperatingsystemversion.h>
11#include <QtCore/qvariant.h>
12#include <QtCore/qstring.h>
13#include <QtCore/qtimer.h>
14
15#include <memory>
16
18
19namespace DarwinBluetooth {
20
21namespace {
22
23const int basebandConnectTimeoutMS = 20000;
24
25QBluetoothUuid sdp_element_to_uuid(IOBluetoothSDPDataElement *element)
26{
28
29 if (!element || [element getTypeDescriptor] != kBluetoothSDPDataElementTypeUUID)
30 return {};
31
32 return qt_uuid([[element getUUIDValue] getUUIDWithLength:16]);
33}
34
35QBluetoothUuid extract_service_ID(IOBluetoothSDPServiceRecord *record)
36{
38
40
41 return sdp_element_to_uuid([record getAttributeDataElement:kBluetoothSDPAttributeIdentifierServiceID]);
42}
43
44QList<QBluetoothUuid> extract_service_class_ID_list(IOBluetoothSDPServiceRecord *record)
45{
47
49
50 IOBluetoothSDPDataElement *const idList = [record getAttributeDataElement:kBluetoothSDPAttributeIdentifierServiceClassIDList];
51
52 QList<QBluetoothUuid> uuids;
53 if (!idList)
54 return uuids;
55
56 NSArray *arr = nil;
57 if ([idList getTypeDescriptor] == kBluetoothSDPDataElementTypeDataElementSequence)
58 arr = [idList getArrayValue];
59 else if ([idList getTypeDescriptor] == kBluetoothSDPDataElementTypeUUID)
60 arr = @[idList];
61
62 if (!arr)
63 return uuids;
64
65 for (IOBluetoothSDPDataElement *dataElement in arr) {
66 const auto qtUuid = sdp_element_to_uuid(dataElement);
67 if (!qtUuid.isNull())
68 uuids.push_back(qtUuid);
69 }
70
71 return uuids;
72}
73
74QBluetoothServiceInfo::Sequence service_class_ID_list_to_sequence(const QList<QBluetoothUuid> &uuids)
75{
76 if (uuids.isEmpty())
77 return {};
78
80 for (const auto &uuid : uuids) {
81 Q_ASSERT(!uuid.isNull());
82 sequence.append(QVariant::fromValue(uuid));
83 }
84
85 return sequence;
86}
87
88} // unnamed namespace
89
90QVariant extract_attribute_value(IOBluetoothSDPDataElement *dataElement)
91{
92 Q_ASSERT_X(dataElement, Q_FUNC_INFO, "invalid data element (nil)");
93
94 // TODO: error handling and diagnostic messages.
95
96 // All "temporary" obj-c objects are autoreleased.
98
99 const BluetoothSDPDataElementTypeDescriptor typeDescriptor = [dataElement getTypeDescriptor];
100
101 switch (typeDescriptor) {
102 case kBluetoothSDPDataElementTypeNil:
103 break;
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: // TODO: check this!
117 {
119 NSArray *const arr = [dataElement getArrayValue];
120 for (IOBluetoothSDPDataElement *element in arr)
121 sequence.append(extract_attribute_value(element));
122
123 return QVariant::fromValue(sequence);
124 }
125 break;// Coding style.
126 default:;
127 }
128
129 return QVariant();
130}
131
132void extract_service_record(IOBluetoothSDPServiceRecord *record, QBluetoothServiceInfo &serviceInfo)
133{
135
136 if (!record)
137 return;
138
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);
146 }
147
148 const QBluetoothUuid serviceUuid = extract_service_ID(record);
149 if (!serviceUuid.isNull())
150 serviceInfo.setServiceUuid(serviceUuid);
151
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);
156}
157
158QList<QBluetoothUuid> extract_services_uuids(IOBluetoothDevice *device)
159{
160 QList<QBluetoothUuid> uuids;
161
162 // All "temporary" obj-c objects are autoreleased.
164
165 if (!device || !device.services)
166 return uuids;
167
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);
173
174 const QList<QBluetoothUuid> idList(extract_service_class_ID_list(record));
175 if (idList.size())
176 uuids.append(idList);
177 }
178
179 return uuids;
180}
181
182} // namespace DarwinBluetooth
183
185
187
188using namespace DarwinBluetooth;
189
190@implementation DarwinBTSDPInquiry
191{
192 QT_PREPEND_NAMESPACE(DarwinBluetooth::SDPInquiryDelegate) *delegate;
193 ObjCScopedPointer<IOBluetoothDevice> device;
195
196 // Needed to workaround a broken SDP on Monterey:
197 std::unique_ptr<QTimer> connectionWatchdog;
198}
199
200- (id)initWithDelegate:(DarwinBluetooth::SDPInquiryDelegate *)aDelegate
201{
202 Q_ASSERT_X(aDelegate, Q_FUNC_INFO, "invalid delegate (null)");
203
204 if (self = [super init]) {
205 delegate = aDelegate;
206 isActive = false;
207 }
208
209 return self;
210}
211
212- (void)dealloc
213{
214 //[device closeConnection]; //??? - synchronous, "In the future this API will be changed to allow asynchronous operation."
215 [super dealloc];
216}
217
218- (IOReturn)performSDPQueryWithDevice:(const QBluetoothAddress &)address
219{
220 Q_ASSERT_X(!isActive, Q_FUNC_INFO, "SDP query in progress");
221
222 QList<QBluetoothUuid> emptyFilter;
223 return [self performSDPQueryWithDevice:address filters:emptyFilter];
224}
225
226- (void)interruptSDPQuery
227{
228 // To be only executed on timer.
230 // If device was reset, so the timer should be, we can never be here then.
231 Q_ASSERT(device.get());
232
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.";
236
237 // Stop the watchdog and close the connection as otherwise there could be
238 // later "connectionComplete" callbacks
239 connectionWatchdog->stop();
240 [device closeConnection];
241
242 delegate->SDPInquiryError(device, kIOReturnTimeout);
243 device.reset();
244 isActive = false;
245}
246
247- (IOReturn)performSDPQueryWithDevice:(const QBluetoothAddress &)address
248 filters:(const QList<QBluetoothUuid> &)qtFilters
249{
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;
253
255
256 // We first try to allocate "filters":
257 ObjCScopedPointer<NSMutableArray> array;
259 && qtFilters.size()) { // See the comment about filters on Monterey below.
260 array.reset([[NSMutableArray alloc] init], RetainPolicy::noInitialRetain);
261 if (!array) {
262 qCCritical(QT_BT_DARWIN) << "failed to allocate an uuid filter";
263 return kIOReturnError;
264 }
265
266 for (const QBluetoothUuid &qUuid : qtFilters) {
267 ObjCStrongReference<IOBluetoothSDPUUID> uuid(iobluetooth_uuid(qUuid));
268 if (uuid)
269 [array addObject:uuid];
270 }
271
272 if (qsizetype([array count]) != qtFilters.size()) {
273 qCCritical(QT_BT_DARWIN) << "failed to create an uuid filter";
274 return kIOReturnError;
275 }
276 }
277
278 const BluetoothDeviceAddress iobtAddress(iobluetooth_address(address));
279 device.reset([IOBluetoothDevice deviceWithAddress:&iobtAddress], RetainPolicy::doInitialRetain);
280 if (!device) {
281 qCCritical(QT_BT_DARWIN) << "failed to create an IOBluetoothDevice object";
282 return kIOReturnError;
283 }
284 qCDebug(QT_BT_DARWIN) << "Device" << [device nameOrAddress] << "connected:"
285 << bool([device isConnected]) << "paired:" << bool([device isPaired]);
286
287 IOReturn result = kIOReturnSuccess;
288
290 // SDP query on Monterey does not follow its own documented/expected behavior:
291 // - a simple performSDPQuery was previously ensuring baseband connection
292 // to be opened, now it does not do so, instead logs a warning and returns
293 // immediately.
294 // - a version with UUID filters simply does nothing except it immediately
295 // returns kIOReturnSuccess.
296
297 // If the device was not yet connected, connect it first
298 if (![device isConnected]) {
299 qCDebug(QT_BT_DARWIN) << "Device" << [device nameOrAddress]
300 << "is not connected, connecting it first";
301 result = [device openConnection:self];
302 // The connection may succeed immediately. But if it didn't, start a connection timer
303 // which has two guardian roles:
304 // 1. Guard against connect attempt taking too long time
305 // 2. Sometimes on Monterey the callback indicating "connection completion" is
306 // not received even though the connection has in fact succeeded
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);
314 connectionWatchdog.get(),
315 [self] () {
316 qCDebug(QT_BT_DARWIN) << "Connection monitor timeout for device:"
317 << [device nameOrAddress]
318 << ", connected:" << bool([device isConnected]);
319 // Device can sometimes get properly connected without IOBluetooth
320 // calling the connectionComplete callback, so we check the status here
321 if ([device isConnected])
322 [self connectionComplete:device status:kIOReturnSuccess];
323 else
324 [self interruptSDPQuery];
325 });
326 connectionWatchdog->start(basebandConnectTimeoutMS);
327 }
328 }
329
330 if ([device isConnected])
331 result = [device performSDPQuery:self];
332
333 if (result != kIOReturnSuccess) {
334 qCCritical(QT_BT_DARWIN, "failed to start an SDP query");
335 device.reset();
336 } else {
337 isActive = true;
338 }
339
340 return result;
341 } // Monterey's code path.
342
343 if (qtFilters.size())
344 result = [device performSDPQuery:self uuids:array];
345 else
346 result = [device performSDPQuery:self];
347
348 if (result != kIOReturnSuccess) {
349 qCCritical(QT_BT_DARWIN) << "failed to start an SDP query";
350 device.reset();
351 } else {
352 isActive = true;
353 }
354
355 return result;
356}
357
358- (void)connectionComplete:(IOBluetoothDevice *)aDevice status:(IOReturn)status
359{
360 qCDebug(QT_BT_DARWIN) << "connectionComplete for device" << [aDevice nameOrAddress]
361 << "with status:" << status;
362 if (aDevice != device) {
363 // Connection was previously cancelled, probably, due to the timeout.
364 return;
365 }
366
367 // The connectionComplete may be invoked by either the IOBluetooth callback or our
368 // connection watchdog. In either case stop the watchdog if it exists
370 connectionWatchdog->stop();
371
372 if (status == kIOReturnSuccess)
373 status = [aDevice performSDPQuery:self];
374
375 if (status != kIOReturnSuccess) {
376 isActive = false;
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);
380 }
381}
382
383- (void)stopSDPQuery
384{
385 // There is no API to stop it SDP on device, but there is a 'stop'
386 // member-function in Qt and after it's called sdpQueryComplete
387 // must be somehow ignored (device != aDevice in a callback).
388 device.reset();
389 isActive = false;
390 connectionWatchdog.reset();
391}
392
393- (void)sdpQueryComplete:(IOBluetoothDevice *)aDevice status:(IOReturn)status
394{
395 qCDebug(QT_BT_DARWIN) << "sdpQueryComplete for device:" << [aDevice nameOrAddress]
396 << "with status:" << status;
397 // Can happen - there is no legal way to cancel an SDP query,
398 // after the 'reset' device can never be
399 // the same as the cancelled one.
400 if (device != aDevice)
401 return;
402
403 Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)");
404
405 isActive = false;
406
407 // If we used the manual connection establishment, close the
408 // connection here. Otherwise the IOBluetooth may call stray
409 // connectionComplete or sdpQueryCompletes
410 if (connectionWatchdog) {
411 qCDebug(QT_BT_DARWIN) << "Closing the connection established for SDP inquiry.";
412 connectionWatchdog.reset();
413 [device closeConnection];
414 }
415
416 if (status != kIOReturnSuccess)
417 delegate->SDPInquiryError(aDevice, status);
418 else
419 delegate->SDPInquiryFinished(aDevice);
420}
421
422@end
IOBluetoothDevice * device
bool isActive
std::unique_ptr< QTimer > connectionWatchdog
ObjCScopedPointer< IOBluetoothDevice > device
#define QT_BT_MAC_AUTORELEASEPOOL
Definition btutility_p.h:78
\inmodule QtBluetooth
\inmodule QtBluetooth
\inmodule QtBluetooth
Definition qlist.h:75
bool isEmpty() const noexcept
Definition qlist.h:401
void append(parameter_type t)
Definition qlist.h:458
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
static Q_CORE_EXPORT QOperatingSystemVersionBase current()
static constexpr QOperatingSystemVersionBase MacOSBigSur
\variable QOperatingSystemVersion::MacOSBigSur
\inmodule QtCore
Definition qtimer.h:20
void timeout(QPrivateSignal)
This signal is emitted when the timer times out.
bool isNull() const noexcept
Returns true if this is the null UUID {00000000-0000-0000-0000-000000000000}; otherwise returns false...
Definition quuid.cpp:818
\inmodule QtCore
Definition qvariant.h:65
static auto fromValue(T &&value) noexcept(std::is_nothrow_copy_constructible_v< T > &&Private::CanUseInternalSpace< T >) -> std::enable_if_t< std::conjunction_v< std::is_copy_constructible< T >, std::is_destructible< T > >, QVariant >
Definition qvariant.h:536
QList< QBluetoothUuid > extract_services_uuids(IOBluetoothDevice *device)
void extract_service_record(IOBluetoothSDPServiceRecord *record, QBluetoothServiceInfo &serviceInfo)
BluetoothDeviceAddress iobluetooth_address(const QBluetoothAddress &qAddress)
Definition btutility.mm:65
QBluetoothUuid qt_uuid(NSUUID *nsUuid)
ObjCStrongReference< IOBluetoothSDPUUID > iobluetooth_uuid(const QBluetoothUuid &uuid)
Definition btutility.mm:81
QVariant extract_attribute_value(IOBluetoothSDPDataElement *dataElement)
Combined button and popup list for selecting options.
QString self
Definition language.cpp:58
#define Q_FUNC_INFO
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
#define qCCritical(category,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
GLuint64 key
GLenum GLuint id
[7]
GLenum GLenum GLsizei count
GLenum array
GLuint in
GLuint GLuint64EXT address
GLuint64EXT * result
[6]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define Q_ASSERT_X(cond, x, msg)
Definition qrandom.cpp:48
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
unsigned short quint16
Definition qtypes.h:48
ptrdiff_t qsizetype
Definition qtypes.h:165
QStringList keys
MyRecord record(int row) const
[0]