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 "qbluetoothservicediscoveryagent.h"
41#include "qbluetoothservicediscoveryagent_p.h"
42
43#include "bluez/manager_p.h"
44#include "bluez/adapter_p.h"
45#include "bluez/device_p.h"
46#include "bluez/bluez5_helper_p.h"
47#include "bluez/objectmanager_p.h"
48#include "bluez/adapter1_bluez5_p.h"
49
50#include <QtCore/QFile>
51#include <QtCore/QLibraryInfo>
52#include <QtCore/QLoggingCategory>
53#include <QtCore/QProcess>
54#include <QtDBus/QDBusPendingCallWatcher>
55#include <QtConcurrent/QtConcurrentRun>
56
57QT_BEGIN_NAMESPACE
58
59Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
60
61QBluetoothServiceDiscoveryAgentPrivate::QBluetoothServiceDiscoveryAgentPrivate(
62 QBluetoothServiceDiscoveryAgent *qp, const QBluetoothAddress &deviceAdapter)
63: error(QBluetoothServiceDiscoveryAgent::NoError), m_deviceAdapterAddress(deviceAdapter), state(Inactive),
64 mode(QBluetoothServiceDiscoveryAgent::MinimalDiscovery), singleDevice(false),
65 q_ptr(qp)
66{
67 if (isBluez5()) {
68 managerBluez5 = new OrgFreedesktopDBusObjectManagerInterface(
69 QStringLiteral("org.bluez"), QStringLiteral("/"),
70 QDBusConnection::systemBus());
71 qRegisterMetaType<QBluetoothServiceDiscoveryAgent::Error>();
72 } else {
73 qRegisterMetaType<ServiceMap>();
74 qDBusRegisterMetaType<ServiceMap>();
75
76 manager = new OrgBluezManagerInterface(QStringLiteral("org.bluez"), QStringLiteral("/"),
77 QDBusConnection::systemBus());
78 }
79}
80
81QBluetoothServiceDiscoveryAgentPrivate::~QBluetoothServiceDiscoveryAgentPrivate()
82{
83 delete device;
84 delete manager;
85 delete managerBluez5;
86 delete adapter;
87}
88
89void QBluetoothServiceDiscoveryAgentPrivate::start(const QBluetoothAddress &address)
90{
91 Q_Q(QBluetoothServiceDiscoveryAgent);
92
93 qCDebug(QT_BT_BLUEZ) << "Discovery on: " << address.toString() << "Mode:" << DiscoveryMode();
94
95 if (managerBluez5) {
96 startBluez5(address);
97 return;
98 }
99
100 QDBusPendingReply<QDBusObjectPath> reply;
101 if (m_deviceAdapterAddress.isNull())
102 reply = manager->DefaultAdapter();
103 else
104 reply = manager->FindAdapter(m_deviceAdapterAddress.toString());
105
106 reply.waitForFinished();
107 if (reply.isError()) {
108 error = QBluetoothServiceDiscoveryAgent::InputOutputError;
109 errorString = QBluetoothServiceDiscoveryAgent::tr("Unable to find appointed local adapter");
110 emit q->error(error);
111 _q_serviceDiscoveryFinished();
112 return;
113 }
114
115 adapter = new OrgBluezAdapterInterface(QStringLiteral("org.bluez"), reply.value().path(),
116 QDBusConnection::systemBus());
117
118 if (m_deviceAdapterAddress.isNull()) {
119 QDBusPendingReply<QVariantMap> reply = adapter->GetProperties();
120 reply.waitForFinished();
121 if (!reply.isError()) {
122 const QBluetoothAddress path_address(reply.value().value(QStringLiteral("Address")).toString());
123 m_deviceAdapterAddress = path_address;
124 }
125 }
126
127 QDBusPendingReply<QDBusObjectPath> deviceObjectPath = adapter->FindDevice(address.toString());
128
129 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(deviceObjectPath, q);
130 watcher->setProperty("_q_BTaddress", QVariant::fromValue(address));
131 QObject::connect(watcher, &QDBusPendingCallWatcher::finished,
132 q, [this](QDBusPendingCallWatcher *watcher){
133 this->_q_foundDevice(watcher);
134 });
135}
136
137// Bluez 5
138void QBluetoothServiceDiscoveryAgentPrivate::startBluez5(const QBluetoothAddress &address)
139{
140 Q_Q(QBluetoothServiceDiscoveryAgent);
141
142 if (foundHostAdapterPath.isEmpty()) {
143 // check that we match adapter addresses or use first if it wasn't specified
144
145 bool ok = false;
146 foundHostAdapterPath = findAdapterForAddress(m_deviceAdapterAddress, &ok);
147 if (!ok) {
148 discoveredDevices.clear();
149 error = QBluetoothServiceDiscoveryAgent::InputOutputError;
150 errorString = QBluetoothDeviceDiscoveryAgent::tr("Cannot access adapter during service discovery");
151 emit q->error(error);
152 _q_serviceDiscoveryFinished();
153 return;
154 }
155
156 if (foundHostAdapterPath.isEmpty()) {
157 // Cannot find a local adapter
158 // Abort any outstanding discoveries
159 discoveredDevices.clear();
160
161 error = QBluetoothServiceDiscoveryAgent::InvalidBluetoothAdapterError;
162 errorString = QBluetoothServiceDiscoveryAgent::tr("Cannot find local Bluetooth adapter");
163 emit q->error(error);
164 _q_serviceDiscoveryFinished();
165
166 return;
167 }
168 }
169
170 // ensure we didn't go offline yet
171 OrgBluezAdapter1Interface adapter(QStringLiteral("org.bluez"),
172 foundHostAdapterPath, QDBusConnection::systemBus());
173 if (!adapter.powered()) {
174 discoveredDevices.clear();
175
176 error = QBluetoothServiceDiscoveryAgent::PoweredOffError;
177 errorString = QBluetoothServiceDiscoveryAgent::tr("Local device is powered off");
178 emit q->error(error);
179
180 _q_serviceDiscoveryFinished();
181 return;
182 }
183
184 if (DiscoveryMode() == QBluetoothServiceDiscoveryAgent::MinimalDiscovery) {
185 performMinimalServiceDiscovery(address);
186 } else {
187 runExternalSdpScan(address, QBluetoothAddress(adapter.address()));
188 }
189}
190
191/* Bluez 5
192 * src/tools/sdpscanner performs an SDP scan. This is
193 * done out-of-process to avoid license issues. At this stage Bluez uses GPLv2.
194 */
195void QBluetoothServiceDiscoveryAgentPrivate::runExternalSdpScan(
196 const QBluetoothAddress &remoteAddress, const QBluetoothAddress &localAddress)
197{
198 Q_Q(QBluetoothServiceDiscoveryAgent);
199
200 if (!sdpScannerProcess) {
201 const QString binPath = QLibraryInfo::location(QLibraryInfo::BinariesPath);
202 QFileInfo fileInfo(binPath, QStringLiteral("sdpscanner"));
203 if (!fileInfo.exists() || !fileInfo.isExecutable()) {
204 _q_finishSdpScan(QBluetoothServiceDiscoveryAgent::InputOutputError,
205 QBluetoothServiceDiscoveryAgent::tr("Unable to find sdpscanner"),
206 QStringList());
207 qCWarning(QT_BT_BLUEZ) << "Cannot find sdpscanner:"
208 << fileInfo.canonicalFilePath();
209 return;
210 }
211
212 sdpScannerProcess = new QProcess(q);
213 sdpScannerProcess->setReadChannel(QProcess::StandardOutput);
214 if (QT_BT_BLUEZ().isDebugEnabled())
215 sdpScannerProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel);
216 sdpScannerProcess->setProgram(fileInfo.canonicalFilePath());
217 q->connect(sdpScannerProcess,
218 QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
219 q, [this](int exitCode, QProcess::ExitStatus status){
220 this->_q_sdpScannerDone(exitCode, status);
221 });
222 }
223
224 QStringList arguments;
225 arguments << remoteAddress.toString() << localAddress.toString();
226
227 // No filter implies PUBLIC_BROWSE_GROUP based SDP scan
228 if (!uuidFilter.isEmpty()) {
229 arguments << QLatin1String("-u"); // cmd line option for list of uuids
230 for (const QBluetoothUuid& uuid : qAsConst(uuidFilter))
231 arguments << uuid.toString();
232 }
233
234 sdpScannerProcess->setArguments(arguments);
235 sdpScannerProcess->start();
236}
237
238// Bluez 5
239void QBluetoothServiceDiscoveryAgentPrivate::_q_sdpScannerDone(int exitCode, QProcess::ExitStatus status)
240{
241 if (status != QProcess::NormalExit || exitCode != 0) {
242 qCWarning(QT_BT_BLUEZ) << "SDP scan failure" << status << exitCode;
243 if (singleDevice) {
244 _q_finishSdpScan(QBluetoothServiceDiscoveryAgent::InputOutputError,
245 QBluetoothServiceDiscoveryAgent::tr("Unable to perform SDP scan"),
246 QStringList());
247 } else {
248 // go to next device
249 _q_finishSdpScan(QBluetoothServiceDiscoveryAgent::NoError, QString(), QStringList());
250 }
251 return;
252 }
253
254 QStringList xmlRecords;
255 const QByteArray output = sdpScannerProcess->readAllStandardOutput();
256 const QString decodedData = QString::fromUtf8(QByteArray::fromBase64(output));
257
258 // split the various xml docs up
259 int next;
260 int start = decodedData.indexOf(QStringLiteral("<?xml"), 0);
261 if (start != -1) {
262 do {
263 next = decodedData.indexOf(QStringLiteral("<?xml"), start + 1);
264 if (next != -1)
265 xmlRecords.append(decodedData.mid(start, next-start));
266 else
267 xmlRecords.append(decodedData.mid(start, decodedData.size() - start));
268 start = next;
269 } while ( start != -1);
270 }
271
272 _q_finishSdpScan(QBluetoothServiceDiscoveryAgent::NoError, QString(), xmlRecords);
273}
274
275// Bluez 5
276void QBluetoothServiceDiscoveryAgentPrivate::_q_finishSdpScan(QBluetoothServiceDiscoveryAgent::Error errorCode,
277 const QString &errorDescription,
278 const QStringList &xmlRecords)
279{
280 Q_Q(QBluetoothServiceDiscoveryAgent);
281
282 if (errorCode != QBluetoothServiceDiscoveryAgent::NoError) {
283 qCWarning(QT_BT_BLUEZ) << "SDP search failed for"
284 << (!discoveredDevices.isEmpty()
285 ? discoveredDevices.at(0).address().toString()
286 : QStringLiteral("<Unknown>"));
287 // We have an error which we need to indicate and stop further processing
288 discoveredDevices.clear();
289 error = errorCode;
290 errorString = errorDescription;
291 emit q->error(error);
292 } else if (!xmlRecords.isEmpty() && discoveryState() != Inactive) {
293 for (const QString &record : xmlRecords) {
294 QBluetoothServiceInfo serviceInfo = parseServiceXml(record);
295
296 //apply uuidFilter
297 if (!uuidFilter.isEmpty()) {
298 bool serviceNameMatched = uuidFilter.contains(serviceInfo.serviceUuid());
299 bool serviceClassMatched = false;
300 const QList<QBluetoothUuid> serviceClassUuids
301 = serviceInfo.serviceClassUuids();
302 for (const QBluetoothUuid &id : serviceClassUuids) {
303 if (uuidFilter.contains(id)) {
304 serviceClassMatched = true;
305 break;
306 }
307 }
308
309 if (!serviceNameMatched && !serviceClassMatched)
310 continue;
311 }
312
313 if (!serviceInfo.isValid())
314 continue;
315
316 // Bluez sdpscanner declares custom uuids into the service class uuid list.
317 // Let's move a potential custom uuid from QBluetoothServiceInfo::serviceClassUuids()
318 // to QBluetoothServiceInfo::serviceUuid(). If there is more than one, just move the first uuid
319 const QList<QBluetoothUuid> serviceClassUuids = serviceInfo.serviceClassUuids();
320 for (const QBluetoothUuid &id : serviceClassUuids) {
321 if (id.minimumSize() == 16) {
322 serviceInfo.setServiceUuid(id);
323 serviceInfo.setServiceName(QBluetoothServiceDiscoveryAgent::tr("Custom Service"));
324 QBluetoothServiceInfo::Sequence modSeq =
325 serviceInfo.attribute(QBluetoothServiceInfo::ServiceClassIds).value<QBluetoothServiceInfo::Sequence>();
326 modSeq.removeOne(QVariant::fromValue(id));
327 serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, modSeq);
328 break;
329 }
330 }
331
332 if (!isDuplicatedService(serviceInfo)) {
333 discoveredServices.append(serviceInfo);
334 qCDebug(QT_BT_BLUEZ) << "Discovered services" << discoveredDevices.at(0).address().toString()
335 << serviceInfo.serviceName() << serviceInfo.serviceUuid()
336 << ">>>" << serviceInfo.serviceClassUuids();
337
338 emit q->serviceDiscovered(serviceInfo);
339 }
340 }
341 }
342
343 _q_serviceDiscoveryFinished();
344}
345
346void QBluetoothServiceDiscoveryAgentPrivate::stop()
347{
348 qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "Stop called";
349 if (device) {
350 //we are waiting for _q_discoveredServices() to be called
351 // adapter is already 0
352 QDBusPendingReply<> reply = device->CancelDiscovery();
353 reply.waitForFinished();
354
355 device->deleteLater();
356 device = nullptr;
357 Q_ASSERT(!adapter);
358 } else if (adapter) {
359 //we are waiting for _q_createdDevice() to be called
360 adapter->deleteLater();
361 adapter = nullptr;
362 Q_ASSERT(!device);
363 }
364
365
366 discoveredDevices.clear();
367 setDiscoveryState(Inactive);
368
369 // must happen after discoveredDevices.clear() above to avoid retrigger of next scan
370 // while waitForFinished() is waiting
371 if (sdpScannerProcess) { // Bluez 5
372 if (sdpScannerProcess->state() != QProcess::NotRunning) {
373 sdpScannerProcess->kill();
374 sdpScannerProcess->waitForFinished();
375 }
376 }
377
378 Q_Q(QBluetoothServiceDiscoveryAgent);
379 emit q->canceled();
380}
381
382void QBluetoothServiceDiscoveryAgentPrivate::_q_foundDevice(QDBusPendingCallWatcher *watcher)
383{
384 if (!adapter) {
385 watcher->deleteLater();
386 return;
387 }
388
389 Q_Q(QBluetoothServiceDiscoveryAgent);
390
391 const QBluetoothAddress &address = watcher->property("_q_BTaddress").value<QBluetoothAddress>();
392
393 qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "found" << address.toString();
394
395 QDBusPendingReply<QDBusObjectPath> deviceObjectPath = *watcher;
396 watcher->deleteLater();
397 if (deviceObjectPath.isError()) {
398 if (deviceObjectPath.error().name() != QStringLiteral("org.bluez.Error.DoesNotExist")) {
399 qCDebug(QT_BT_BLUEZ) << "Find device failed Error: " << error << deviceObjectPath.error().name();
400 delete adapter;
401 adapter = nullptr;
402 if (singleDevice) {
403 error = QBluetoothServiceDiscoveryAgent::InputOutputError;
404 errorString = QBluetoothServiceDiscoveryAgent::tr("Unable to access device");
405 emit q->error(error);
406 }
407 _q_serviceDiscoveryFinished();
408 return;
409 }
410
411 deviceObjectPath = adapter->CreateDevice(address.toString());
412 watcher = new QDBusPendingCallWatcher(deviceObjectPath, q);
413 watcher->setProperty("_q_BTaddress", QVariant::fromValue(address));
414 QObject::connect(watcher, &QDBusPendingCallWatcher::finished,
415 q, [this](QDBusPendingCallWatcher *watcher){
416 this->_q_createdDevice(watcher);
417 });
418
419 return;
420 }
421
422 qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "path" << deviceObjectPath.value().path();
423 discoverServices(deviceObjectPath.value().path());
424}
425
426void QBluetoothServiceDiscoveryAgentPrivate::_q_createdDevice(QDBusPendingCallWatcher *watcher)
427{
428 Q_Q(QBluetoothServiceDiscoveryAgent);
429
430 if (!adapter) {
431 watcher->deleteLater();
432 return;
433 }
434
435 const QBluetoothAddress &address = watcher->property("_q_BTaddress").value<QBluetoothAddress>();
436
437 qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "created" << address.toString();
438
439 QDBusPendingReply<QDBusObjectPath> deviceObjectPath = *watcher;
440 watcher->deleteLater();
441 if (deviceObjectPath.isError()) {
442 if (deviceObjectPath.error().name() != QLatin1String("org.bluez.Error.AlreadyExists")) {
443 qCDebug(QT_BT_BLUEZ) << "Create device failed Error: " << error << deviceObjectPath.error().name();
444 delete adapter;
445 adapter = nullptr;
446 if (singleDevice) {
447 error = QBluetoothServiceDiscoveryAgent::InputOutputError;
448 errorString = QBluetoothServiceDiscoveryAgent::tr("Unable to access device");
449 emit q->error(error);
450 }
451 _q_serviceDiscoveryFinished();
452 return;
453 }
454 }
455
456 qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "path" << deviceObjectPath.value().path();
457 discoverServices(deviceObjectPath.value().path());
458}
459
460void QBluetoothServiceDiscoveryAgentPrivate::discoverServices(const QString &deviceObjectPath)
461{
462 Q_Q(QBluetoothServiceDiscoveryAgent);
463
464 device = new OrgBluezDeviceInterface(QStringLiteral("org.bluez"),
465 deviceObjectPath,
466 QDBusConnection::systemBus());
467 delete adapter;
468 adapter = nullptr;
469
470 QVariantMap deviceProperties;
471 QString classType;
472 QDBusPendingReply<QVariantMap> deviceReply = device->GetProperties();
473 deviceReply.waitForFinished();
474 if (!deviceReply.isError()) {
475 deviceProperties = deviceReply.value();
476 classType = deviceProperties.value(QStringLiteral("Class")).toString();
477 }
478
479 /*
480 * Low Energy services in bluez are represented as the list of the paths that can be
481 * accessed with org.bluez.Characteristic
482 */
483 //QDBusArgument services = v.value(QLatin1String("Services")).value<QDBusArgument>();
484
485
486 /*
487 * Bluez v4.1 does not have extra bit which gives information if device is Bluetooth
488 * Low Energy device and the way to discover it is with Class property of the Bluetooth device.
489 * Low Energy devices do not have property Class.
490 * In case we have LE device finish service discovery; otherwise search for regular services.
491 */
492 if (classType.isEmpty()) { //is BLE device or device properties above not retrievable
493 qCDebug(QT_BT_BLUEZ) << "Discovered BLE-only device. Normal service discovery skipped.";
494 delete device;
495 device = nullptr;
496
497 const QStringList deviceUuids = deviceProperties.value(QStringLiteral("UUIDs")).toStringList();
498 for (int i = 0; i < deviceUuids.size(); i++) {
499 QString b = deviceUuids.at(i);
500 b = b.remove(QLatin1Char('{')).remove(QLatin1Char('}'));
501 const QBluetoothUuid uuid(b);
502
503 qCDebug(QT_BT_BLUEZ) << "Discovered service" << uuid << uuidFilter.size();
504 QBluetoothServiceInfo service;
505 service.setDevice(discoveredDevices.at(0));
506 bool ok = false;
507 quint16 serviceClass = uuid.toUInt16(&ok);
508 if (ok)
509 service.setServiceName(QBluetoothUuid::serviceClassToString(
510 static_cast<QBluetoothUuid::ServiceClassUuid>(serviceClass)));
511
512 QBluetoothServiceInfo::Sequence classId;
513 classId << QVariant::fromValue(uuid);
514 service.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId);
515
516 QBluetoothServiceInfo::Sequence protocolDescriptorList;
517 {
518 QBluetoothServiceInfo::Sequence protocol;
519 protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap));
520 protocolDescriptorList.append(QVariant::fromValue(protocol));
521 }
522 {
523 QBluetoothServiceInfo::Sequence protocol;
524 protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Att));
525 protocolDescriptorList.append(QVariant::fromValue(protocol));
526 }
527 service.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList);
528
529 if (uuidFilter.isEmpty())
530 emit q->serviceDiscovered(service);
531 else {
532 for (int j = 0; j < uuidFilter.size(); j++) {
533 if (uuidFilter.at(j) == uuid)
534 emit q->serviceDiscovered(service);
535 }
536 }
537 }
538
539 if (singleDevice && deviceReply.isError()) {
540 error = QBluetoothServiceDiscoveryAgent::InputOutputError;
541 errorString = QBluetoothServiceDiscoveryAgent::tr("Unable to access device");
542 emit q->error(error);
543 }
544 _q_serviceDiscoveryFinished();
545 } else {
546 QString pattern;
547 for (const QBluetoothUuid &uuid : qAsConst(uuidFilter))
548 pattern += uuid.toString().remove(QLatin1Char('{')).remove(QLatin1Char('}')) + QLatin1Char(' ');
549
550 pattern = pattern.trimmed();
551 qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "Discover restrictions:" << pattern;
552
553 QDBusPendingReply<ServiceMap> discoverReply = device->DiscoverServices(pattern);
554 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(discoverReply, q);
555 QObject::connect(watcher, &QDBusPendingCallWatcher::finished,
556 q, [this](QDBusPendingCallWatcher *watcher){
557 this->_q_discoveredServices(watcher);
558 });
559 }
560}
561
562// Bluez 4
563void QBluetoothServiceDiscoveryAgentPrivate::_q_discoveredServices(QDBusPendingCallWatcher *watcher)
564{
565 if (!device) {
566 watcher->deleteLater();
567 return;
568 }
569
570 qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO;
571 Q_Q(QBluetoothServiceDiscoveryAgent);
572
573 QDBusPendingReply<ServiceMap> reply = *watcher;
574 if (reply.isError()) {
575 qCDebug(QT_BT_BLUEZ) << "discoveredServices error: " << error << reply.error().message();
576 watcher->deleteLater();
577 if (singleDevice) {
578 error = QBluetoothServiceDiscoveryAgent::UnknownError;
579 errorString = reply.error().message();
580 emit q->error(error);
581 }
582 delete device;
583 device = nullptr;
584 _q_serviceDiscoveryFinished();
585 return;
586 }
587
588 const ServiceMap map = reply.value();
589
590 qCDebug(QT_BT_BLUEZ) << "Parsing xml" << discoveredDevices.at(0).address().toString() << discoveredDevices.count() << map.count();
591
592
593
594 for (const QString &record : map) {
595 QBluetoothServiceInfo serviceInfo = parseServiceXml(record);
596
597 if (!serviceInfo.isValid())
598 continue;
599
600 // Don't need to apply uuidFilter because Bluez 4 applies
601 // search pattern during DiscoverServices() call
602
603 Q_Q(QBluetoothServiceDiscoveryAgent);
604 // Some service uuids are unknown to Bluez. In such cases we fall back
605 // to our own naming resolution.
606 if (serviceInfo.serviceName().isEmpty()
607 && !serviceInfo.serviceClassUuids().isEmpty()) {
608 const QList<QBluetoothUuid> classUuids = serviceInfo.serviceClassUuids();
609 for (const QBluetoothUuid &classUuid : classUuids) {
610 bool ok = false;
611 QBluetoothUuid::ServiceClassUuid clsId
612 = static_cast<QBluetoothUuid::ServiceClassUuid>(classUuid.toUInt16(&ok));
613 if (ok) {
614 serviceInfo.setServiceName(QBluetoothUuid::serviceClassToString(clsId));
615 break;
616 }
617 }
618 }
619
620 if (!isDuplicatedService(serviceInfo)) {
621 discoveredServices.append(serviceInfo);
622 qCDebug(QT_BT_BLUEZ) << "Discovered services" << discoveredDevices.at(0).address().toString()
623 << serviceInfo.serviceName();
624 emit q->serviceDiscovered(serviceInfo);
625 }
626
627 // could stop discovery, check for state
628 if (discoveryState() == Inactive)
629 qCDebug(QT_BT_BLUEZ) << "Exit discovery after stop";
630 }
631
632 watcher->deleteLater();
633 delete device;
634 device = nullptr;
635
636 _q_serviceDiscoveryFinished();
637}
638
639QBluetoothServiceInfo QBluetoothServiceDiscoveryAgentPrivate::parseServiceXml(
640 const QString& xmlRecord)
641{
642 QXmlStreamReader xml(xmlRecord);
643
644 QBluetoothServiceInfo serviceInfo;
645 serviceInfo.setDevice(discoveredDevices.at(0));
646
647 while (!xml.atEnd()) {
648 xml.readNext();
649
650 if (xml.tokenType() == QXmlStreamReader::StartElement &&
651 xml.name() == QLatin1String("attribute")) {
652 quint16 attributeId =
653 xml.attributes().value(QLatin1String("id")).toString().toUShort(nullptr, 0);
654
655 if (xml.readNextStartElement()) {
656 const QVariant value = readAttributeValue(xml);
657 serviceInfo.setAttribute(attributeId, value);
658 }
659 }
660 }
661
662 return serviceInfo;
663}
664
665// Bluez 5
666void QBluetoothServiceDiscoveryAgentPrivate::performMinimalServiceDiscovery(const QBluetoothAddress &deviceAddress)
667{
668 if (foundHostAdapterPath.isEmpty()) {
669 _q_serviceDiscoveryFinished();
670 return;
671 }
672
673 Q_Q(QBluetoothServiceDiscoveryAgent);
674
675 QDBusPendingReply<ManagedObjectList> reply = managerBluez5->GetManagedObjects();
676 reply.waitForFinished();
677 if (reply.isError()) {
678 if (singleDevice) {
679 error = QBluetoothServiceDiscoveryAgent::InputOutputError;
680 errorString = reply.error().message();
681 emit q->error(error);
682
683 }
684 _q_serviceDiscoveryFinished();
685 return;
686 }
687
688 QStringList uuidStrings;
689
690 ManagedObjectList managedObjectList = reply.value();
691 for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
692 const InterfaceList &ifaceList = it.value();
693
694 for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
695 const QString &iface = jt.key();
696 const QVariantMap &ifaceValues = jt.value();
697
698 if (iface == QStringLiteral("org.bluez.Device1")) {
699 if (deviceAddress.toString() == ifaceValues.value(QStringLiteral("Address")).toString()) {
700 uuidStrings = ifaceValues.value(QStringLiteral("UUIDs")).toStringList();
701 break;
702 }
703 }
704 }
705 if (!uuidStrings.isEmpty())
706 break;
707 }
708
709 if (uuidStrings.isEmpty() || discoveredDevices.isEmpty()) {
710 qCWarning(QT_BT_BLUEZ) << "No uuids found for" << deviceAddress.toString();
711 // nothing found -> go to next uuid
712 _q_serviceDiscoveryFinished();
713 return;
714 }
715
716 qCDebug(QT_BT_BLUEZ) << "Minimal uuid list for" << deviceAddress.toString() << uuidStrings;
717
718 QBluetoothUuid uuid;
719 for (int i = 0; i < uuidStrings.count(); i++) {
720 uuid = QBluetoothUuid(uuidStrings.at(i));
721 if (uuid.isNull())
722 continue;
723
724 //apply uuidFilter
725 if (!uuidFilter.isEmpty() && !uuidFilter.contains(uuid))
726 continue;
727
728 QBluetoothServiceInfo serviceInfo;
729 serviceInfo.setDevice(discoveredDevices.at(0));
730
731 if (uuid.minimumSize() == 16) { // not derived from Bluetooth Base UUID
732 serviceInfo.setServiceUuid(uuid);
733 serviceInfo.setServiceName(QBluetoothServiceDiscoveryAgent::tr("Custom Service"));
734 } else {
735 // set uuid as service class id
736 QBluetoothServiceInfo::Sequence classId;
737 classId << QVariant::fromValue(uuid);
738 serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId);
739 QBluetoothUuid::ServiceClassUuid clsId
740 = static_cast<QBluetoothUuid::ServiceClassUuid>(uuid.data1 & 0xffff);
741 serviceInfo.setServiceName(QBluetoothUuid::serviceClassToString(clsId));
742 }
743
744 QBluetoothServiceInfo::Sequence protocolDescriptorList;
745 {
746 QBluetoothServiceInfo::Sequence protocol;
747 protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap));
748 protocolDescriptorList.append(QVariant::fromValue(protocol));
749 }
750 {
751 QBluetoothServiceInfo::Sequence protocol;
752 protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Att));
753 protocolDescriptorList.append(QVariant::fromValue(protocol));
754 }
755 serviceInfo.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList);
756
757 //don't include the service if we already discovered it before
758 if (!isDuplicatedService(serviceInfo)) {
759 discoveredServices << serviceInfo;
760 qCDebug(QT_BT_BLUEZ) << "Discovered services" << discoveredDevices.at(0).address().toString()
761 << serviceInfo.serviceName();
762 emit q->serviceDiscovered(serviceInfo);
763 }
764 }
765
766 _q_serviceDiscoveryFinished();
767}
768
769QVariant QBluetoothServiceDiscoveryAgentPrivate::readAttributeValue(QXmlStreamReader &xml)
770{
771 if (xml.name() == QLatin1String("boolean")) {
772 const QString value = xml.attributes().value(QStringLiteral("value")).toString();
773 xml.skipCurrentElement();
774 return value == QLatin1String("true");
775 } else if (xml.name() == QLatin1String("uint8")) {
776 quint8 value = xml.attributes().value(QStringLiteral("value")).toString().toUShort(nullptr, 0);
777 xml.skipCurrentElement();
778 return value;
779 } else if (xml.name() == QLatin1String("uint16")) {
780 quint16 value = xml.attributes().value(QStringLiteral("value")).toString().toUShort(nullptr, 0);
781 xml.skipCurrentElement();
782 return value;
783 } else if (xml.name() == QLatin1String("uint32")) {
784 quint32 value = xml.attributes().value(QStringLiteral("value")).toString().toUInt(nullptr, 0);
785 xml.skipCurrentElement();
786 return value;
787 } else if (xml.name() == QLatin1String("uint64")) {
788 quint64 value = xml.attributes().value(QStringLiteral("value")).toString().toULongLong(nullptr, 0);
789 xml.skipCurrentElement();
790 return value;
791 } else if (xml.name() == QLatin1String("uuid")) {
792 QBluetoothUuid uuid;
793 const QString value = xml.attributes().value(QStringLiteral("value")).toString();
794 if (value.startsWith(QStringLiteral("0x"))) {
795 if (value.length() == 6) {
796 quint16 v = value.toUShort(nullptr, 0);
797 uuid = QBluetoothUuid(v);
798 } else if (value.length() == 10) {
799 quint32 v = value.toUInt(nullptr, 0);
800 uuid = QBluetoothUuid(v);
801 }
802 } else {
803 uuid = QBluetoothUuid(value);
804 }
805 xml.skipCurrentElement();
806 return QVariant::fromValue(uuid);
807 } else if (xml.name() == QLatin1String("text") || xml.name() == QLatin1String("url")) {
808 QString value = xml.attributes().value(QStringLiteral("value")).toString();
809 if (xml.attributes().value(QStringLiteral("encoding")) == QLatin1String("hex"))
810 value = QString::fromUtf8(QByteArray::fromHex(value.toLatin1()));
811 xml.skipCurrentElement();
812 return value;
813 } else if (xml.name() == QLatin1String("sequence")) {
814 QBluetoothServiceInfo::Sequence sequence;
815
816 while (xml.readNextStartElement()) {
817 QVariant value = readAttributeValue(xml);
818 sequence.append(value);
819 }
820
821 return QVariant::fromValue<QBluetoothServiceInfo::Sequence>(sequence);
822 } else {
823 qCWarning(QT_BT_BLUEZ) << "unknown attribute type"
824 << xml.name().toString()
825 << xml.attributes().value(QStringLiteral("value")).toString();
826 xml.skipCurrentElement();
827 return QVariant();
828 }
829}
830
831QT_END_NAMESPACE
832