Warning: That file was not part of the compilation database. It may have many parsing errors.

1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtBluetooth module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qbluetoothdevicediscoveryagent_p.h"
41#include "qbluetoothdevicediscoveryagent.h"
42
43#include "osx/osxbtledeviceinquiry_p.h"
44#ifdef Q_OS_MACOS
45#include "osx/osxbtdeviceinquiry_p.h"
46#include "osx/osxbtsdpinquiry_p.h"
47#endif // Q_OS_MACOS
48#include "qbluetoothdeviceinfo.h"
49#include "osx/osxbtnotifier_p.h"
50#include "osx/osxbtutility_p.h"
51#include "osx/osxbluetooth_p.h"
52#include "osx/uistrings_p.h"
53#include "qbluetoothhostinfo.h"
54#include "qbluetoothaddress.h"
55#include "osx/uistrings_p.h"
56#include "qbluetoothuuid.h"
57#include "osx/btraii_p.h"
58
59#include <QtCore/qloggingcategory.h>
60#include <QtCore/qscopedpointer.h>
61#include <QtCore/qvector.h>
62#include <QtCore/qglobal.h>
63#include <QtCore/qstring.h>
64#include <QtCore/qdebug.h>
65
66#include <Foundation/Foundation.h>
67
68QT_BEGIN_NAMESPACE
69
70namespace
71{
72
73void registerQDeviceDiscoveryMetaType()
74{
75 static bool initDone = false;
76 if (!initDone) {
77 qRegisterMetaType<QBluetoothDeviceInfo>();
78 qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::Error>();
79 initDone = true;
80 }
81}
82#ifdef Q_OS_MACOS
83using InquiryObjC = QT_MANGLE_NAMESPACE(OSXBTDeviceInquiry);
84#endif // Q_OS_MACOS
85
86using LEInquiryObjC = QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry);
87
88} //namespace
89
90QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress &adapter,
91 QBluetoothDeviceDiscoveryAgent *q) :
92 inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry),
93 lastError(QBluetoothDeviceDiscoveryAgent::NoError),
94 agentState(NonActive),
95 adapterAddress(adapter),
96 startPending(false),
97 stopPending(false),
98 lowEnergySearchTimeout(OSXBluetooth::defaultLEScanTimeoutMS),
99#ifdef Q_OS_MACOS
100 requestedMethods(QBluetoothDeviceDiscoveryAgent::ClassicMethod | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod),
101#else
102 requestedMethods(QBluetoothDeviceDiscoveryAgent::ClassicMethod),
103#endif // Q_OS_MACOS
104 q_ptr(q)
105{
106 registerQDeviceDiscoveryMetaType();
107
108 Q_ASSERT_X(q != nullptr, Q_FUNC_INFO, "invalid q_ptr (null)");
109
110#ifdef Q_OS_MACOS
111 IOBluetoothHostController *hostController = [IOBluetoothHostController defaultController];
112 if (!hostController || [hostController powerState] != kBluetoothHCIPowerStateON) {
113 qCCritical(QT_BT_OSX) << "no default host controller or adapter is off";
114 return;
115 }
116 controller.reset(hostController, DarwinBluetooth::RetainPolicy::doInitialRetain);
117#endif
118}
119
120QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate()
121{
122 if (inquiryLE && agentState != NonActive) {
123 // We want the LE scan to stop as soon as possible.
124 if (dispatch_queue_t leQueue = OSXBluetooth::qt_LE_queue()) {
125 // Local variable to be retained ...
126 LEInquiryObjC *inq = inquiryLE.getAs<LEInquiryObjC>();
127 dispatch_sync(leQueue, ^{
128 [inq stop];
129 });
130 }
131 }
132}
133
134bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
135{
136 if (startPending)
137 return true;
138
139 if (stopPending)
140 return false;
141
142 return agentState != NonActive;
143}
144
145void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
146{
147 Q_ASSERT(!isActive());
148 Q_ASSERT(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError);
149 Q_ASSERT(methods & (QBluetoothDeviceDiscoveryAgent::ClassicMethod
150 | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod));
151
152#ifdef Q_OS_MACOS
153 if (!controller) {
154 setError(QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError);
155 emit q_ptr->error(lastError);
156 return;
157 }
158#endif // Q_OS_MACOS
159
160 requestedMethods = methods;
161
162 if (stopPending) {
163 startPending = true;
164 return;
165 }
166
167 // This function (re)starts the scan(s) from the scratch;
168 // starting from Classic if it's in 'methods' (or LE scan if not).
169
170 agentState = NonActive;
171 discoveredDevices.clear();
172 setError(QBluetoothDeviceDiscoveryAgent::NoError);
173#ifdef Q_OS_MACOS
174 if (requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod)
175 return startClassic();
176#endif // Q_OS_MACOS
177
178 startLE();
179}
180
181#ifdef Q_OS_MACOS
182
183void QBluetoothDeviceDiscoveryAgentPrivate::startClassic()
184{
185 Q_ASSERT(!isActive());
186 Q_ASSERT(lastError == QBluetoothDeviceDiscoveryAgent::NoError);
187 Q_ASSERT(requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod);
188 Q_ASSERT(agentState == NonActive);
189
190 OSXBluetooth::qt_test_iobluetooth_runloop();
191
192 if (!inquiry) {
193 // The first Classic scan for this DDA.
194 inquiry.reset([[InquiryObjC alloc] initWithDelegate:this],
195 DarwinBluetooth::RetainPolicy::noInitialRetain);
196
197 if (!inquiry) {
198 qCCritical(QT_BT_OSX) << "failed to initialize an Classic device inquiry";
199 setError(QBluetoothDeviceDiscoveryAgent::UnknownError,
200 QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED));
201 emit q_ptr->error(lastError);
202 return;
203 }
204 }
205
206 agentState = ClassicScan;
207
208 const IOReturn res = [inquiry.getAs<InquiryObjC>() start];
209 if (res != kIOReturnSuccess) {
210 setError(res, QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED));
211 agentState = NonActive;
212 emit q_ptr->error(lastError);
213 }
214}
215
216#endif // Q_OS_MACOS
217
218void QBluetoothDeviceDiscoveryAgentPrivate::startLE()
219{
220 Q_ASSERT(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError);
221 Q_ASSERT(requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
222
223 using namespace OSXBluetooth;
224
225 QScopedPointer<LECBManagerNotifier> notifier(new LECBManagerNotifier);
226 // Connections:
227 using ErrMemFunPtr = void (LECBManagerNotifier::*)(QBluetoothDeviceDiscoveryAgent::Error);
228 notifier->connect(notifier.data(), ErrMemFunPtr(&LECBManagerNotifier::CBManagerError),
229 this, &QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError);
230 notifier->connect(notifier.data(), &LECBManagerNotifier::LEnotSupported,
231 this, &QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported);
232 notifier->connect(notifier.data(), &LECBManagerNotifier::discoveryFinished,
233 this, &QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished);
234 using DeviceMemFunPtr = void (QBluetoothDeviceDiscoveryAgentPrivate::*)(const QBluetoothDeviceInfo &);
235 notifier->connect(notifier.data(), &LECBManagerNotifier::deviceDiscovered,
236 this, DeviceMemFunPtr(&QBluetoothDeviceDiscoveryAgentPrivate::deviceFound));
237
238 // Check queue and create scanner:
239 inquiryLE.reset([[LEInquiryObjC alloc] initWithNotifier:notifier.data()],
240 DarwinBluetooth::RetainPolicy::noInitialRetain);
241 if (inquiryLE)
242 notifier.take(); // Whatever happens next, inquiryLE is already the owner ...
243
244 dispatch_queue_t leQueue(qt_LE_queue());
245 if (!leQueue || !inquiryLE) {
246 setError(QBluetoothDeviceDiscoveryAgent::UnknownError,
247 QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED_LE));
248 agentState = NonActive;
249 emit q_ptr->error(lastError);
250 return;
251 }
252
253 // Now start in on LE queue:
254 agentState = LEScan;
255 // We need the local variable so that it's retained ...
256 LEInquiryObjC *inq = inquiryLE.getAs<LEInquiryObjC>();
257 dispatch_async(leQueue, ^{
258 [inq startWithTimeout:lowEnergySearchTimeout];
259 });
260}
261
262void QBluetoothDeviceDiscoveryAgentPrivate::stop()
263{
264 Q_ASSERT_X(isActive(), Q_FUNC_INFO, "called whithout active inquiry");
265 Q_ASSERT_X(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError,
266 Q_FUNC_INFO, "called with invalid bluetooth adapter");
267
268 using namespace OSXBluetooth;
269
270 const bool prevStart = startPending;
271 startPending = false;
272 stopPending = true;
273
274 setError(QBluetoothDeviceDiscoveryAgent::NoError);
275
276#ifdef Q_OS_MACOS
277 if (agentState == ClassicScan) {
278 const IOReturn res = [inquiry.getAs<InquiryObjC>() stop];
279 if (res != kIOReturnSuccess) {
280 qCWarning(QT_BT_OSX) << "failed to stop";
281 startPending = prevStart;
282 stopPending = false;
283 setError(res, QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STOPPED));
284 emit q_ptr->error(lastError);
285 }
286 } else {
287#else
288 {
289 Q_UNUSED(prevStart)
290#endif // Q_OS_MACOS
291 dispatch_queue_t leQueue(qt_LE_queue());
292 Q_ASSERT(leQueue);
293 // We need the local variable so that it's retained ...
294 LEInquiryObjC *inq = inquiryLE.getAs<LEInquiryObjC>();
295 dispatch_sync(leQueue, ^{
296 [inq stop];
297 });
298 // We consider LE scan to be stopped immediately and
299 // do not care about this LEDeviceInquiry object anymore.
300 LEinquiryFinished();
301 }
302}
303
304#ifdef Q_OS_MACOS
305
306void QBluetoothDeviceDiscoveryAgentPrivate::inquiryFinished()
307{
308 // The subsequent start(LE) function (if any)
309 // will (re)set the correct state.
310 agentState = NonActive;
311
312 if (stopPending && !startPending) {
313 stopPending = false;
314 emit q_ptr->canceled();
315 } else if (startPending) {
316 startPending = false;
317 stopPending = false;
318 start(requestedMethods);
319 } else {
320 // We can be here _only_ if a classic scan
321 // finished in a normal way (not cancelled)
322 // and requestedMethods includes LowEnergyMethod.
323 // startLE() will take care of old devices
324 // not supporting Bluetooth 4.0.
325 if (requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)
326 startLE();
327 else
328 emit q_ptr->finished();
329 }
330}
331
332void QBluetoothDeviceDiscoveryAgentPrivate::error(IOReturn error)
333{
334 startPending = false;
335 stopPending = false;
336
337 setError(error);
338
339 emit q_ptr->error(lastError);
340}
341
342void QBluetoothDeviceDiscoveryAgentPrivate::classicDeviceFound(void *obj)
343{
344 auto device = static_cast<IOBluetoothDevice *>(obj);
345 Q_ASSERT_X(device, Q_FUNC_INFO, "invalid IOBluetoothDevice (nil)");
346
347 Q_ASSERT_X(agentState == ClassicScan, Q_FUNC_INFO,
348 "invalid agent state (expected classic scan)");
349
350 QT_BT_MAC_AUTORELEASEPOOL;
351
352 // Let's collect some info about this device:
353 const QBluetoothAddress deviceAddress(OSXBluetooth::qt_address([device getAddress]));
354 if (deviceAddress.isNull()) {
355 qCWarning(QT_BT_OSX) << "invalid Bluetooth address";
356 return;
357 }
358
359 QString deviceName;
360 if (device.name)
361 deviceName = QString::fromNSString(device.name);
362
363 const auto classOfDevice = qint32(device.classOfDevice);
364
365 QBluetoothDeviceInfo deviceInfo(deviceAddress, deviceName, classOfDevice);
366 deviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateCoreConfiguration);
367 deviceInfo.setRssi(device.RSSI);
368
369 const QVector<QBluetoothUuid> uuids(OSXBluetooth::extract_services_uuids(device));
370 deviceInfo.setServiceUuids(uuids);
371
372 deviceFound(deviceInfo);
373}
374
375void QBluetoothDeviceDiscoveryAgentPrivate::setError(IOReturn error, const QString &text)
376{
377 if (error == kIOReturnSuccess)
378 setError(QBluetoothDeviceDiscoveryAgent::NoError, text);
379 else if (error == kIOReturnNoPower)
380 setError(QBluetoothDeviceDiscoveryAgent::PoweredOffError, text);
381 else
382 setError(QBluetoothDeviceDiscoveryAgent::UnknownError, text);
383}
384
385#endif // Q_OS_MACOS
386
387void QBluetoothDeviceDiscoveryAgentPrivate::setError(QBluetoothDeviceDiscoveryAgent::Error error, const QString &text)
388{
389 lastError = error;
390
391 if (text.length() > 0) {
392 errorString = text;
393 } else {
394 switch (lastError) {
395 case QBluetoothDeviceDiscoveryAgent::NoError:
396 errorString = QString();
397 break;
398 case QBluetoothDeviceDiscoveryAgent::PoweredOffError:
399 errorString = QCoreApplication::translate(DEV_DISCOVERY, DD_POWERED_OFF);
400 break;
401 case QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError:
402 errorString = QCoreApplication::translate(DEV_DISCOVERY, DD_INVALID_ADAPTER);
403 break;
404 case QBluetoothDeviceDiscoveryAgent::InputOutputError:
405 errorString = QCoreApplication::translate(DEV_DISCOVERY, DD_IO);
406 break;
407 case QBluetoothDeviceDiscoveryAgent::UnsupportedPlatformError:
408 errorString = QCoreApplication::translate(DEV_DISCOVERY, DD_NOTSUPPORTED);
409 break;
410 case QBluetoothDeviceDiscoveryAgent::UnknownError:
411 default:
412 errorString = QCoreApplication::translate(DEV_DISCOVERY, DD_UNKNOWN_ERROR);
413 }
414 }
415}
416
417void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError(QBluetoothDeviceDiscoveryAgent::Error error)
418{
419 Q_ASSERT(error == QBluetoothDeviceDiscoveryAgent::PoweredOffError
420 || error == QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod);
421
422 inquiryLE.reset();
423
424 startPending = false;
425 stopPending = false;
426 agentState = NonActive;
427 setError(error);
428 emit q_ptr->error(lastError);
429}
430
431void QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported()
432{
433 qCDebug(QT_BT_OSX) << "no Bluetooth LE support";
434
435#ifdef Q_OS_MACOS
436 if (requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod) {
437 // Having both Classic | LE means this is not an error.
438 LEinquiryFinished();
439 } else {
440 // In the past this was never an error, that's why we have
441 // LEnotSupported as a special method. But now, since
442 // we can have separate Classic/LE scans, we have to report it
443 // as UnsupportedDiscoveryMethod.
444 LEinquiryError(QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod);
445 }
446#else
447 inquiryLE.reset();
448 startPending = false;
449 stopPending = false;
450 setError(QBluetoothDeviceDiscoveryAgent::UnsupportedPlatformError);
451 emit q_ptr->error(lastError);
452#endif
453}
454
455void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished()
456{
457 // The same logic as in inquiryFinished, but does not start LE scan.
458 agentState = NonActive;
459 inquiryLE.reset();
460
461 if (stopPending && !startPending) {
462 stopPending = false;
463 emit q_ptr->canceled();
464 } else if (startPending) {
465 startPending = false;
466 stopPending = false;
467 start(requestedMethods); //Start again.
468 } else {
469 emit q_ptr->finished();
470 }
471}
472
473void QBluetoothDeviceDiscoveryAgentPrivate::deviceFound(const QBluetoothDeviceInfo &newDeviceInfo)
474{
475 // Core Bluetooth does not allow us to access addresses, we have to use uuid instead.
476 // This uuid has nothing to do with uuids in Bluetooth in general (it's generated by
477 // Apple's framework using some algorithm), but it's a 128-bit uuid after all.
478 const bool isLE =
479#ifdef Q_OS_MACOS
480 newDeviceInfo.coreConfigurations() == QBluetoothDeviceInfo::LowEnergyCoreConfiguration;
481#else
482 true;
483#endif // Q_OS_MACOS
484 for (int i = 0, e = discoveredDevices.size(); i < e; ++i) {
485 if (isLE) {
486 if (discoveredDevices[i].deviceUuid() == newDeviceInfo.deviceUuid()) {
487 QBluetoothDeviceInfo::Fields updatedFields = QBluetoothDeviceInfo::Field::None;
488 if (discoveredDevices[i].rssi() != newDeviceInfo.rssi()) {
489 qCDebug(QT_BT_OSX) << "Updating RSSI for" << newDeviceInfo.address()
490 << newDeviceInfo.rssi();
491 discoveredDevices[i].setRssi(newDeviceInfo.rssi());
492 updatedFields.setFlag(QBluetoothDeviceInfo::Field::RSSI);
493 }
494
495 if (discoveredDevices[i].manufacturerData() != newDeviceInfo.manufacturerData()) {
496 qCDebug(QT_BT_OSX) << "Updating manufacturer data for" << newDeviceInfo.address();
497 const QVector<quint16> keys = newDeviceInfo.manufacturerIds();
498 for (auto key: keys)
499 discoveredDevices[i].setManufacturerData(key, newDeviceInfo.manufacturerData(key));
500 updatedFields.setFlag(QBluetoothDeviceInfo::Field::ManufacturerData);
501 }
502
503 if (lowEnergySearchTimeout > 0) {
504 if (discoveredDevices[i] != newDeviceInfo) {
505 discoveredDevices.replace(i, newDeviceInfo);
506 emit q_ptr->deviceDiscovered(newDeviceInfo);
507 } else {
508 if (!updatedFields.testFlag(QBluetoothDeviceInfo::Field::None))
509 emit q_ptr->deviceUpdated(discoveredDevices[i], updatedFields);
510 }
511
512 return;
513 }
514
515 discoveredDevices.replace(i, newDeviceInfo);
516 emit q_ptr->deviceDiscovered(newDeviceInfo);
517
518 if (!updatedFields.testFlag(QBluetoothDeviceInfo::Field::None))
519 emit q_ptr->deviceUpdated(discoveredDevices[i], updatedFields);
520
521 return;
522 }
523 } else {
524#ifdef Q_OS_MACOS
525 if (discoveredDevices[i].address() == newDeviceInfo.address()) {
526 if (discoveredDevices[i] == newDeviceInfo)
527 return;
528
529 discoveredDevices.replace(i, newDeviceInfo);
530 emit q_ptr->deviceDiscovered(newDeviceInfo);
531 return;
532 }
533#else
534 Q_UNREACHABLE();
535#endif // Q_OS_MACOS
536 }
537 }
538
539 discoveredDevices.append(newDeviceInfo);
540 emit q_ptr->deviceDiscovered(newDeviceInfo);
541}
542
543QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
544{
545#ifdef Q_OS_MACOS
546 return ClassicMethod | LowEnergyMethod;
547#else
548 return LowEnergyMethod;
549#endif // Q_OS_MACOS
550}
551
552QT_END_NAMESPACE
553

Warning: That file was not part of the compilation database. It may have many parsing errors.