Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qbluetoothservicediscoveryagent_bluez.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 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// Qt-Security score:critical reason:execute-external-code
4
7
8#include "bluez/bluez5_helper_p.h"
9#include "bluez/objectmanager_p.h"
10#include "bluez/adapter1_bluez5_p.h"
11
12#include <QtCore/QFile>
13#include <QtCore/QLibraryInfo>
14#include <QtCore/QLoggingCategory>
15#include <QtCore/QProcess>
16#include <QtCore/QScopeGuard>
17
18#include <QtDBus/QDBusPendingCallWatcher>
19
20QT_BEGIN_NAMESPACE
21
22Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
23
24using namespace QtBluetoothPrivate; // for D-Bus wrappers
25
26QBluetoothServiceDiscoveryAgentPrivate::QBluetoothServiceDiscoveryAgentPrivate(
27 QBluetoothServiceDiscoveryAgent *qp, const QBluetoothAddress &deviceAdapter)
28: error(QBluetoothServiceDiscoveryAgent::NoError), m_deviceAdapterAddress(deviceAdapter), state(Inactive),
29 mode(QBluetoothServiceDiscoveryAgent::MinimalDiscovery), singleDevice(false),
30 q_ptr(qp)
31{
32 initializeBluez5();
33 manager = new OrgFreedesktopDBusObjectManagerInterface(
34 QStringLiteral("org.bluez"), QStringLiteral("/"), QDBusConnection::systemBus());
35 qRegisterMetaType<QBluetoothServiceDiscoveryAgent::Error>();
36}
37
38QBluetoothServiceDiscoveryAgentPrivate::~QBluetoothServiceDiscoveryAgentPrivate()
39{
40 delete manager;
41}
42
43void QBluetoothServiceDiscoveryAgentPrivate::start(const QBluetoothAddress &address)
44{
45 Q_Q(QBluetoothServiceDiscoveryAgent);
46
47 qCDebug(QT_BT_BLUEZ) << "Discovery on: " << address.toString() << "Mode:" << DiscoveryMode();
48
49 if (foundHostAdapterPath.isEmpty()) {
50 // check that we match adapter addresses or use first if it wasn't specified
51
52 bool ok = false;
53 foundHostAdapterPath = findAdapterForAddress(m_deviceAdapterAddress, &ok);
54 if (!ok) {
55 discoveredDevices.clear();
56 error = QBluetoothServiceDiscoveryAgent::InputOutputError;
57 errorString = QBluetoothDeviceDiscoveryAgent::tr("Cannot access adapter during service discovery");
58 emit q->errorOccurred(error);
59 _q_serviceDiscoveryFinished();
60 return;
61 }
62
63 if (foundHostAdapterPath.isEmpty()) {
64 // Cannot find a local adapter
65 // Abort any outstanding discoveries
66 discoveredDevices.clear();
67
68 error = QBluetoothServiceDiscoveryAgent::InvalidBluetoothAdapterError;
69 errorString = QBluetoothServiceDiscoveryAgent::tr("Cannot find local Bluetooth adapter");
70 emit q->errorOccurred(error);
71 _q_serviceDiscoveryFinished();
72
73 return;
74 }
75 }
76
77 // ensure we didn't go offline yet
78 OrgBluezAdapter1Interface adapter(QStringLiteral("org.bluez"),
79 foundHostAdapterPath, QDBusConnection::systemBus());
80 if (!adapter.powered()) {
81 discoveredDevices.clear();
82
83 error = QBluetoothServiceDiscoveryAgent::PoweredOffError;
84 errorString = QBluetoothServiceDiscoveryAgent::tr("Local device is powered off");
85 emit q->errorOccurred(error);
86
87 _q_serviceDiscoveryFinished();
88 return;
89 }
90
91 if (DiscoveryMode() == QBluetoothServiceDiscoveryAgent::MinimalDiscovery) {
92 performMinimalServiceDiscovery(address);
93 } else {
94 runExternalSdpScan(address, QBluetoothAddress(adapter.address()));
95 }
96}
97
98/* Bluez 5
99 * src/tools/sdpscanner performs an SDP scan. This is
100 * done out-of-process to avoid license issues. At this stage Bluez uses GPLv2.
101 */
102void QBluetoothServiceDiscoveryAgentPrivate::runExternalSdpScan(
103 const QBluetoothAddress &remoteAddress, const QBluetoothAddress &localAddress)
104{
105 Q_Q(QBluetoothServiceDiscoveryAgent);
106
107 if (!sdpScannerProcess) {
108 const QString binPath = QLibraryInfo::path(QLibraryInfo::LibraryExecutablesPath);
109 QFileInfo fileInfo(binPath, QStringLiteral("sdpscanner"));
110 if (!fileInfo.exists() || !fileInfo.isExecutable()) {
111 _q_finishSdpScan(QBluetoothServiceDiscoveryAgent::InputOutputError,
112 QBluetoothServiceDiscoveryAgent::tr("Unable to find sdpscanner"),
113 QStringList());
114 qCWarning(QT_BT_BLUEZ) << "Cannot find sdpscanner:"
115 << fileInfo.canonicalFilePath();
116 return;
117 }
118
119 sdpScannerProcess = new QProcess(q);
120 sdpScannerProcess->setReadChannel(QProcess::StandardOutput);
121 if (QT_BT_BLUEZ().isDebugEnabled())
122 sdpScannerProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel);
123 // AXIVION Next Line Qt-Security-QProcessStart: expected behavior
124 sdpScannerProcess->setProgram(fileInfo.canonicalFilePath());
125 q->connect(sdpScannerProcess,
126 QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
127 q, [this](int exitCode, QProcess::ExitStatus status){
128 this->_q_sdpScannerDone(exitCode, status);
129 });
130 }
131
132 QStringList arguments;
133 arguments << remoteAddress.toString() << localAddress.toString();
134
135 // No filter implies PUBLIC_BROWSE_GROUP based SDP scan
136 if (!uuidFilter.isEmpty()) {
137 arguments << QLatin1String("-u"); // cmd line option for list of uuids
138 for (const QBluetoothUuid& uuid : std::as_const(uuidFilter))
139 arguments << uuid.toString();
140 }
141
142 sdpScannerProcess->setArguments(arguments);
143 sdpScannerProcess->start();
144}
145
146void QBluetoothServiceDiscoveryAgentPrivate::_q_sdpScannerDone(int exitCode, QProcess::ExitStatus status)
147{
148 if (status != QProcess::NormalExit || exitCode != 0) {
149 qCWarning(QT_BT_BLUEZ) << "SDP scan failure" << status << exitCode;
150 if (singleDevice) {
151 _q_finishSdpScan(QBluetoothServiceDiscoveryAgent::InputOutputError,
152 QBluetoothServiceDiscoveryAgent::tr("Unable to perform SDP scan"),
153 QStringList());
154 } else {
155 // go to next device
156 _q_finishSdpScan(QBluetoothServiceDiscoveryAgent::NoError, QString(), QStringList());
157 }
158 return;
159 }
160
161 QStringList xmlRecords;
162 const QByteArray utf8Data = QByteArray::fromBase64(sdpScannerProcess->readAllStandardOutput());
163 const QByteArrayView utf8View = utf8Data;
164
165 // split the various xml docs up
166 constexpr auto matcher = qMakeStaticByteArrayMatcher("<?xml");
167 qsizetype next;
168 qsizetype start = matcher.indexIn(utf8View, 0);
169 if (start != -1) {
170 do {
171 next = matcher.indexIn(utf8View, start + 1);
172 if (next != -1)
173 xmlRecords.append(QString::fromUtf8(utf8View.sliced(start, next - start)));
174 else
175 xmlRecords.append(QString::fromUtf8(utf8View.sliced(start)));
176 start = next;
177 } while ( start != -1);
178 }
179
180 _q_finishSdpScan(QBluetoothServiceDiscoveryAgent::NoError, QString(), xmlRecords);
181}
182
183void QBluetoothServiceDiscoveryAgentPrivate::_q_finishSdpScan(QBluetoothServiceDiscoveryAgent::Error errorCode,
184 const QString &errorDescription,
185 const QStringList &xmlRecords)
186{
187 Q_Q(QBluetoothServiceDiscoveryAgent);
188
189 if (errorCode != QBluetoothServiceDiscoveryAgent::NoError) {
190 qCWarning(QT_BT_BLUEZ) << "SDP search failed for"
191 << (!discoveredDevices.isEmpty()
192 ? discoveredDevices.at(0).address().toString()
193 : QStringLiteral("<Unknown>"));
194 // We have an error which we need to indicate and stop further processing
195 discoveredDevices.clear();
196 error = errorCode;
197 errorString = errorDescription;
198 emit q->errorOccurred(error);
199 } else if (!xmlRecords.isEmpty() && discoveryState() != Inactive) {
200 for (const QString &record : xmlRecords) {
201 QBluetoothServiceInfo serviceInfo = parseServiceXml(record);
202
203 //apply uuidFilter
204 if (!uuidFilter.isEmpty()) {
205 bool serviceNameMatched = uuidFilter.contains(serviceInfo.serviceUuid());
206 bool serviceClassMatched = false;
207 const QList<QBluetoothUuid> serviceClassUuids
208 = serviceInfo.serviceClassUuids();
209 for (const QBluetoothUuid &id : serviceClassUuids) {
210 if (uuidFilter.contains(id)) {
211 serviceClassMatched = true;
212 break;
213 }
214 }
215
216 if (!serviceNameMatched && !serviceClassMatched)
217 continue;
218 }
219
220 if (!serviceInfo.isValid())
221 continue;
222
223 // Bluez sdpscanner declares custom uuids into the service class uuid list.
224 // Let's move a potential custom uuid from QBluetoothServiceInfo::serviceClassUuids()
225 // to QBluetoothServiceInfo::serviceUuid(). If there is more than one, just move the first uuid
226 const QList<QBluetoothUuid> serviceClassUuids = serviceInfo.serviceClassUuids();
227 for (const QBluetoothUuid &id : serviceClassUuids) {
228 if (id.minimumSize() == 16) {
229 serviceInfo.setServiceUuid(id);
230 if (serviceInfo.serviceName().isEmpty()) {
231 serviceInfo.setServiceName(
232 QBluetoothServiceDiscoveryAgent::tr("Custom Service"));
233 }
234 QBluetoothServiceInfo::Sequence modSeq =
235 serviceInfo.attribute(QBluetoothServiceInfo::ServiceClassIds).value<QBluetoothServiceInfo::Sequence>();
236 modSeq.removeOne(QVariant::fromValue(id));
237 serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, modSeq);
238 break;
239 }
240 }
241
242 if (!isDuplicatedService(serviceInfo)) {
243 discoveredServices.append(serviceInfo);
244 qCDebug(QT_BT_BLUEZ) << "Discovered services" << discoveredDevices.at(0).address().toString()
245 << serviceInfo.serviceName() << serviceInfo.serviceUuid()
246 << ">>>" << serviceInfo.serviceClassUuids();
247 // Use queued connection to allow us finish the service looping; the application
248 // might call stop() when it has detected the service-of-interest.
249 QMetaObject::invokeMethod(q, "serviceDiscovered", Qt::QueuedConnection,
250 Q_ARG(QBluetoothServiceInfo, serviceInfo));
251 }
252 }
253 }
254
255 _q_serviceDiscoveryFinished();
256}
257
258void QBluetoothServiceDiscoveryAgentPrivate::stop()
259{
260 qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "Stop called";
261
262 discoveredDevices.clear();
263 setDiscoveryState(Inactive);
264
265 // must happen after discoveredDevices.clear() above to avoid retrigger of next scan
266 // while waitForFinished() is waiting
267 if (sdpScannerProcess) { // Bluez 5
268 if (sdpScannerProcess->state() != QProcess::NotRunning) {
269 sdpScannerProcess->kill();
270 sdpScannerProcess->waitForFinished();
271 }
272 }
273
274 Q_Q(QBluetoothServiceDiscoveryAgent);
275 emit q->canceled();
276}
277
278QBluetoothServiceInfo QBluetoothServiceDiscoveryAgentPrivate::parseServiceXml(
279 const QString& xmlRecord)
280{
281 QXmlStreamReader xml(xmlRecord);
282
283 QBluetoothServiceInfo serviceInfo;
284 serviceInfo.setDevice(discoveredDevices.at(0));
285
286 while (!xml.atEnd()) {
287 xml.readNext();
288
289 if (xml.tokenType() == QXmlStreamReader::StartElement &&
290 xml.name() == QLatin1String("attribute")) {
291 quint16 attributeId =
292 xml.attributes().value(QLatin1String("id")).toUShort(nullptr, 0);
293
294 if (xml.readNextStartElement()) {
295 const QVariant value = readAttributeValue(xml);
296 serviceInfo.setAttribute(attributeId, value);
297 }
298 }
299 }
300
301 return serviceInfo;
302}
303
304// Bluez 5
305void QBluetoothServiceDiscoveryAgentPrivate::performMinimalServiceDiscovery(const QBluetoothAddress &deviceAddress)
306{
307 if (foundHostAdapterPath.isEmpty()) {
308 _q_serviceDiscoveryFinished();
309 return;
310 }
311
312 Q_Q(QBluetoothServiceDiscoveryAgent);
313
314 QDBusPendingReply<ManagedObjectList> reply = manager->GetManagedObjects();
315 reply.waitForFinished();
316 if (reply.isError()) {
317 if (singleDevice) {
318 error = QBluetoothServiceDiscoveryAgent::InputOutputError;
319 errorString = reply.error().message();
320 emit q->errorOccurred(error);
321 }
322 _q_serviceDiscoveryFinished();
323 return;
324 }
325
326 QStringList uuidStrings;
327
328 ManagedObjectList managedObjectList = reply.value();
329 for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
330 const InterfaceList &ifaceList = it.value();
331
332 for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
333 const QString &iface = jt.key();
334 const QVariantMap &ifaceValues = jt.value();
335
336 if (iface == QStringLiteral("org.bluez.Device1")) {
337 if (deviceAddress.toString() == ifaceValues.value(QStringLiteral("Address")).toString()) {
338 uuidStrings = ifaceValues.value(QStringLiteral("UUIDs")).toStringList();
339 break;
340 }
341 }
342 }
343 if (!uuidStrings.isEmpty())
344 break;
345 }
346
347 if (uuidStrings.isEmpty() || discoveredDevices.isEmpty()) {
348 qCWarning(QT_BT_BLUEZ) << "No uuids found for" << deviceAddress.toString();
349 // nothing found -> go to next uuid
350 _q_serviceDiscoveryFinished();
351 return;
352 }
353
354 qCDebug(QT_BT_BLUEZ) << "Minimal uuid list for" << deviceAddress.toString() << uuidStrings;
355
356 QBluetoothUuid uuid;
357 for (qsizetype i = 0; i < uuidStrings.size(); ++i) {
358 uuid = QBluetoothUuid(uuidStrings.at(i));
359 if (uuid.isNull())
360 continue;
361
362 //apply uuidFilter
363 if (!uuidFilter.isEmpty() && !uuidFilter.contains(uuid))
364 continue;
365
366 QBluetoothServiceInfo serviceInfo;
367 serviceInfo.setDevice(discoveredDevices.at(0));
368
369 if (uuid.minimumSize() == 16) { // not derived from Bluetooth Base UUID
370 serviceInfo.setServiceUuid(uuid);
371 serviceInfo.setServiceName(QBluetoothServiceDiscoveryAgent::tr("Custom Service"));
372 } else {
373 // set uuid as service class id
374 QBluetoothServiceInfo::Sequence classId;
375 classId << QVariant::fromValue(uuid);
376 serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId);
377 QBluetoothUuid::ServiceClassUuid clsId
378 = static_cast<QBluetoothUuid::ServiceClassUuid>(uuid.data1 & 0xffff);
379 serviceInfo.setServiceName(QBluetoothUuid::serviceClassToString(clsId));
380 }
381
382 QBluetoothServiceInfo::Sequence protocolDescriptorList;
383 {
384 QBluetoothServiceInfo::Sequence protocol;
385 protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::ProtocolUuid::L2cap));
386 protocolDescriptorList.append(QVariant::fromValue(protocol));
387 }
388 {
389 QBluetoothServiceInfo::Sequence protocol;
390 protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::ProtocolUuid::Att));
391 protocolDescriptorList.append(QVariant::fromValue(protocol));
392 }
393 serviceInfo.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList);
394
395 //don't include the service if we already discovered it before
396 if (!isDuplicatedService(serviceInfo)) {
397 discoveredServices << serviceInfo;
398 qCDebug(QT_BT_BLUEZ) << "Discovered services" << discoveredDevices.at(0).address().toString()
399 << serviceInfo.serviceName();
400 emit q->serviceDiscovered(serviceInfo);
401 }
402 }
403
404 _q_serviceDiscoveryFinished();
405}
406
407QVariant QBluetoothServiceDiscoveryAgentPrivate::readAttributeValue(QXmlStreamReader &xml,
408 int depth)
409{
410 auto skippingCurrentElementByDefault = qScopeGuard([&] { xml.skipCurrentElement(); });
411
412 if (depth > kMaxSdpRecursionDepth) {
413 qCWarning(QT_BT_BLUEZ) << "SDP XML attribute recursion depth exceeded";
414 return QVariant();
415 }
416
417 if (xml.name() == QLatin1String("boolean")) {
418 return xml.attributes().value(QLatin1String("value")) == QLatin1String("true");
419 } else if (xml.name() == QLatin1String("uint8")) {
420 quint8 value = xml.attributes().value(QLatin1String("value")).toUShort(nullptr, 0);
421 return value;
422 } else if (xml.name() == QLatin1String("uint16")) {
423 quint16 value = xml.attributes().value(QLatin1String("value")).toUShort(nullptr, 0);
424 return value;
425 } else if (xml.name() == QLatin1String("uint32")) {
426 quint32 value = xml.attributes().value(QLatin1String("value")).toUInt(nullptr, 0);
427 return value;
428 } else if (xml.name() == QLatin1String("uint64")) {
429 quint64 value = xml.attributes().value(QLatin1String("value")).toULongLong(nullptr, 0);
430 return value;
431 } else if (xml.name() == QLatin1String("uuid")) {
432 QBluetoothUuid uuid;
433 const QStringView value = xml.attributes().value(QLatin1String("value"));
434 if (value.startsWith(QLatin1String("0x"))) {
435 if (value.size() == 6) {
436 quint16 v = value.toUShort(nullptr, 0);
437 uuid = QBluetoothUuid(v);
438 } else if (value.size() == 10) {
439 quint32 v = value.toUInt(nullptr, 0);
440 uuid = QBluetoothUuid(v);
441 }
442 } else {
443 uuid = QBluetoothUuid(value.toString());
444 }
445 return QVariant::fromValue(uuid);
446 } else if (xml.name() == QLatin1String("text") || xml.name() == QLatin1String("url")) {
447 const QStringView value = xml.attributes().value(QLatin1String("value"));
448 if (xml.attributes().value(QLatin1String("encoding")) == QLatin1String("hex"))
449 return QString::fromUtf8(QByteArray::fromHex(value.toLatin1()));
450 return value.toString();
451 } else if (xml.name() == QLatin1String("sequence")) {
452 QBluetoothServiceInfo::Sequence sequence;
453
454 skippingCurrentElementByDefault.dismiss(); // we skip several elements here
455
456 while (xml.readNextStartElement()) {
457 QVariant value = readAttributeValue(xml, depth + 1);
458 sequence.append(value);
459 }
460
461 return QVariant::fromValue<QBluetoothServiceInfo::Sequence>(sequence);
462 } else {
463 qCWarning(QT_BT_BLUEZ) << "unknown attribute type"
464 << xml.name()
465 << xml.attributes().value(QLatin1String("value"));
466 return QVariant();
467 }
468}
469
470QT_END_NAMESPACE