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
27 QBluetoothServiceDiscoveryAgent *qp, const QBluetoothAddress &deviceAdapter)
30 q_ptr(qp)
31{
32 initializeBluez5();
33 manager = new OrgFreedesktopDBusObjectManagerInterface(
34 QStringLiteral("org.bluez"), QStringLiteral("/"), QDBusConnection::systemBus());
35 qRegisterMetaType<QBluetoothServiceDiscoveryAgent::Error>();
36}
37
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);
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);
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
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 sdpScannerProcess->setProgram(fileInfo.canonicalFilePath());
124 q->connect(sdpScannerProcess,
125 QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
126 q, [this](int exitCode, QProcess::ExitStatus status){
127 this->_q_sdpScannerDone(exitCode, status);
128 });
129 }
130
131 QStringList arguments;
132 arguments << remoteAddress.toString() << localAddress.toString();
133
134 // No filter implies PUBLIC_BROWSE_GROUP based SDP scan
135 if (!uuidFilter.isEmpty()) {
136 arguments << QLatin1String("-u"); // cmd line option for list of uuids
137 for (const QBluetoothUuid& uuid : std::as_const(uuidFilter))
138 arguments << uuid.toString();
139 }
140
141 sdpScannerProcess->setArguments(arguments);
142 sdpScannerProcess->start();
143}
144
145void QBluetoothServiceDiscoveryAgentPrivate::_q_sdpScannerDone(int exitCode, QProcess::ExitStatus status)
146{
147 if (status != QProcess::NormalExit || exitCode != 0) {
148 qCWarning(QT_BT_BLUEZ) << "SDP scan failure" << status << exitCode;
149 if (singleDevice) {
150 _q_finishSdpScan(QBluetoothServiceDiscoveryAgent::InputOutputError,
151 QBluetoothServiceDiscoveryAgent::tr("Unable to perform SDP scan"),
152 QStringList());
153 } else {
154 // go to next device
155 _q_finishSdpScan(QBluetoothServiceDiscoveryAgent::NoError, QString(), QStringList());
156 }
157 return;
158 }
159
160 QStringList xmlRecords;
161 const QByteArray utf8Data = QByteArray::fromBase64(sdpScannerProcess->readAllStandardOutput());
162 const QByteArrayView utf8View = utf8Data;
163
164 // split the various xml docs up
165 constexpr auto matcher = qMakeStaticByteArrayMatcher("<?xml");
166 qsizetype next;
167 qsizetype start = matcher.indexIn(utf8View, 0);
168 if (start != -1) {
169 do {
170 next = matcher.indexIn(utf8View, start + 1);
171 if (next != -1)
172 xmlRecords.append(QString::fromUtf8(utf8View.sliced(start, next - start)));
173 else
174 xmlRecords.append(QString::fromUtf8(utf8View.sliced(start)));
175 start = next;
176 } while ( start != -1);
177 }
178
179 _q_finishSdpScan(QBluetoothServiceDiscoveryAgent::NoError, QString(), xmlRecords);
180}
181
182void QBluetoothServiceDiscoveryAgentPrivate::_q_finishSdpScan(QBluetoothServiceDiscoveryAgent::Error errorCode,
183 const QString &errorDescription,
184 const QStringList &xmlRecords)
185{
186 Q_Q(QBluetoothServiceDiscoveryAgent);
187
188 if (errorCode != QBluetoothServiceDiscoveryAgent::NoError) {
189 qCWarning(QT_BT_BLUEZ) << "SDP search failed for"
190 << (!discoveredDevices.isEmpty()
191 ? discoveredDevices.at(0).address().toString()
192 : QStringLiteral("<Unknown>"));
193 // We have an error which we need to indicate and stop further processing
194 discoveredDevices.clear();
195 error = errorCode;
196 errorString = errorDescription;
197 emit q->errorOccurred(error);
198 } else if (!xmlRecords.isEmpty() && discoveryState() != Inactive) {
199 for (const QString &record : xmlRecords) {
200 QBluetoothServiceInfo serviceInfo = parseServiceXml(record);
201
202 //apply uuidFilter
203 if (!uuidFilter.isEmpty()) {
204 bool serviceNameMatched = uuidFilter.contains(serviceInfo.serviceUuid());
205 bool serviceClassMatched = false;
206 const QList<QBluetoothUuid> serviceClassUuids
207 = serviceInfo.serviceClassUuids();
208 for (const QBluetoothUuid &id : serviceClassUuids) {
209 if (uuidFilter.contains(id)) {
210 serviceClassMatched = true;
211 break;
212 }
213 }
214
215 if (!serviceNameMatched && !serviceClassMatched)
216 continue;
217 }
218
219 if (!serviceInfo.isValid())
220 continue;
221
222 // Bluez sdpscanner declares custom uuids into the service class uuid list.
223 // Let's move a potential custom uuid from QBluetoothServiceInfo::serviceClassUuids()
224 // to QBluetoothServiceInfo::serviceUuid(). If there is more than one, just move the first uuid
225 const QList<QBluetoothUuid> serviceClassUuids = serviceInfo.serviceClassUuids();
226 for (const QBluetoothUuid &id : serviceClassUuids) {
227 if (id.minimumSize() == 16) {
228 serviceInfo.setServiceUuid(id);
229 if (serviceInfo.serviceName().isEmpty()) {
230 serviceInfo.setServiceName(
231 QBluetoothServiceDiscoveryAgent::tr("Custom Service"));
232 }
233 QBluetoothServiceInfo::Sequence modSeq =
234 serviceInfo.attribute(QBluetoothServiceInfo::ServiceClassIds).value<QBluetoothServiceInfo::Sequence>();
235 modSeq.removeOne(QVariant::fromValue(id));
236 serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, modSeq);
237 break;
238 }
239 }
240
241 if (!isDuplicatedService(serviceInfo)) {
242 discoveredServices.append(serviceInfo);
243 qCDebug(QT_BT_BLUEZ) << "Discovered services" << discoveredDevices.at(0).address().toString()
244 << serviceInfo.serviceName() << serviceInfo.serviceUuid()
245 << ">>>" << serviceInfo.serviceClassUuids();
246 // Use queued connection to allow us finish the service looping; the application
247 // might call stop() when it has detected the service-of-interest.
248 QMetaObject::invokeMethod(q, "serviceDiscovered", Qt::QueuedConnection,
249 Q_ARG(QBluetoothServiceInfo, serviceInfo));
250 }
251 }
252 }
253
255}
256
258{
259 qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "Stop called";
260
261 discoveredDevices.clear();
262 setDiscoveryState(Inactive);
263
264 // must happen after discoveredDevices.clear() above to avoid retrigger of next scan
265 // while waitForFinished() is waiting
266 if (sdpScannerProcess) { // Bluez 5
267 if (sdpScannerProcess->state() != QProcess::NotRunning) {
268 sdpScannerProcess->kill();
269 sdpScannerProcess->waitForFinished();
270 }
271 }
272
273 Q_Q(QBluetoothServiceDiscoveryAgent);
274 emit q->canceled();
275}
276
277QBluetoothServiceInfo QBluetoothServiceDiscoveryAgentPrivate::parseServiceXml(
278 const QString& xmlRecord)
279{
280 QXmlStreamReader xml(xmlRecord);
281
282 QBluetoothServiceInfo serviceInfo;
283 serviceInfo.setDevice(discoveredDevices.at(0));
284
285 while (!xml.atEnd()) {
286 xml.readNext();
287
288 if (xml.tokenType() == QXmlStreamReader::StartElement &&
289 xml.name() == QLatin1String("attribute")) {
290 quint16 attributeId =
291 xml.attributes().value(QLatin1String("id")).toUShort(nullptr, 0);
292
293 if (xml.readNextStartElement()) {
294 const QVariant value = readAttributeValue(xml);
295 serviceInfo.setAttribute(attributeId, value);
296 }
297 }
298 }
299
300 return serviceInfo;
301}
302
303// Bluez 5
304void QBluetoothServiceDiscoveryAgentPrivate::performMinimalServiceDiscovery(const QBluetoothAddress &deviceAddress)
305{
306 if (foundHostAdapterPath.isEmpty()) {
308 return;
309 }
310
311 Q_Q(QBluetoothServiceDiscoveryAgent);
312
313 QDBusPendingReply<ManagedObjectList> reply = manager->GetManagedObjects();
314 reply.waitForFinished();
315 if (reply.isError()) {
316 if (singleDevice) {
317 error = QBluetoothServiceDiscoveryAgent::InputOutputError;
318 errorString = reply.error().message();
319 emit q->errorOccurred(error);
320 }
322 return;
323 }
324
325 QStringList uuidStrings;
326
327 ManagedObjectList managedObjectList = reply.value();
328 for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
329 const InterfaceList &ifaceList = it.value();
330
331 for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
332 const QString &iface = jt.key();
333 const QVariantMap &ifaceValues = jt.value();
334
335 if (iface == QStringLiteral("org.bluez.Device1")) {
336 if (deviceAddress.toString() == ifaceValues.value(QStringLiteral("Address")).toString()) {
337 uuidStrings = ifaceValues.value(QStringLiteral("UUIDs")).toStringList();
338 break;
339 }
340 }
341 }
342 if (!uuidStrings.isEmpty())
343 break;
344 }
345
346 if (uuidStrings.isEmpty() || discoveredDevices.isEmpty()) {
347 qCWarning(QT_BT_BLUEZ) << "No uuids found for" << deviceAddress.toString();
348 // nothing found -> go to next uuid
350 return;
351 }
352
353 qCDebug(QT_BT_BLUEZ) << "Minimal uuid list for" << deviceAddress.toString() << uuidStrings;
354
355 QBluetoothUuid uuid;
356 for (qsizetype i = 0; i < uuidStrings.size(); ++i) {
357 uuid = QBluetoothUuid(uuidStrings.at(i));
358 if (uuid.isNull())
359 continue;
360
361 //apply uuidFilter
362 if (!uuidFilter.isEmpty() && !uuidFilter.contains(uuid))
363 continue;
364
365 QBluetoothServiceInfo serviceInfo;
366 serviceInfo.setDevice(discoveredDevices.at(0));
367
368 if (uuid.minimumSize() == 16) { // not derived from Bluetooth Base UUID
369 serviceInfo.setServiceUuid(uuid);
370 serviceInfo.setServiceName(QBluetoothServiceDiscoveryAgent::tr("Custom Service"));
371 } else {
372 // set uuid as service class id
373 QBluetoothServiceInfo::Sequence classId;
374 classId << QVariant::fromValue(uuid);
375 serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId);
376 QBluetoothUuid::ServiceClassUuid clsId
377 = static_cast<QBluetoothUuid::ServiceClassUuid>(uuid.data1 & 0xffff);
378 serviceInfo.setServiceName(QBluetoothUuid::serviceClassToString(clsId));
379 }
380
381 QBluetoothServiceInfo::Sequence protocolDescriptorList;
382 {
383 QBluetoothServiceInfo::Sequence protocol;
384 protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::ProtocolUuid::L2cap));
385 protocolDescriptorList.append(QVariant::fromValue(protocol));
386 }
387 {
388 QBluetoothServiceInfo::Sequence protocol;
389 protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::ProtocolUuid::Att));
390 protocolDescriptorList.append(QVariant::fromValue(protocol));
391 }
392 serviceInfo.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList);
393
394 //don't include the service if we already discovered it before
395 if (!isDuplicatedService(serviceInfo)) {
396 discoveredServices << serviceInfo;
397 qCDebug(QT_BT_BLUEZ) << "Discovered services" << discoveredDevices.at(0).address().toString()
398 << serviceInfo.serviceName();
399 emit q->serviceDiscovered(serviceInfo);
400 }
401 }
402
404}
405
406QVariant QBluetoothServiceDiscoveryAgentPrivate::readAttributeValue(QXmlStreamReader &xml)
407{
408 auto skippingCurrentElementByDefault = qScopeGuard([&] { xml.skipCurrentElement(); });
409
410 if (xml.name() == QLatin1String("boolean")) {
411 return xml.attributes().value(QLatin1String("value")) == QLatin1String("true");
412 } else if (xml.name() == QLatin1String("uint8")) {
413 quint8 value = xml.attributes().value(QLatin1String("value")).toUShort(nullptr, 0);
414 return value;
415 } else if (xml.name() == QLatin1String("uint16")) {
416 quint16 value = xml.attributes().value(QLatin1String("value")).toUShort(nullptr, 0);
417 return value;
418 } else if (xml.name() == QLatin1String("uint32")) {
419 quint32 value = xml.attributes().value(QLatin1String("value")).toUInt(nullptr, 0);
420 return value;
421 } else if (xml.name() == QLatin1String("uint64")) {
422 quint64 value = xml.attributes().value(QLatin1String("value")).toULongLong(nullptr, 0);
423 return value;
424 } else if (xml.name() == QLatin1String("uuid")) {
425 QBluetoothUuid uuid;
426 const QStringView value = xml.attributes().value(QLatin1String("value"));
427 if (value.startsWith(QLatin1String("0x"))) {
428 if (value.size() == 6) {
429 quint16 v = value.toUShort(nullptr, 0);
430 uuid = QBluetoothUuid(v);
431 } else if (value.size() == 10) {
432 quint32 v = value.toUInt(nullptr, 0);
433 uuid = QBluetoothUuid(v);
434 }
435 } else {
436 uuid = QBluetoothUuid(value.toString());
437 }
438 return QVariant::fromValue(uuid);
439 } else if (xml.name() == QLatin1String("text") || xml.name() == QLatin1String("url")) {
440 const QStringView value = xml.attributes().value(QLatin1String("value"));
441 if (xml.attributes().value(QLatin1String("encoding")) == QLatin1String("hex"))
442 return QString::fromUtf8(QByteArray::fromHex(value.toLatin1()));
443 return value.toString();
444 } else if (xml.name() == QLatin1String("sequence")) {
445 QBluetoothServiceInfo::Sequence sequence;
446
447 skippingCurrentElementByDefault.dismiss(); // we skip several elements here
448
449 while (xml.readNextStartElement()) {
450 QVariant value = readAttributeValue(xml);
451 sequence.append(value);
452 }
453
454 return QVariant::fromValue<QBluetoothServiceInfo::Sequence>(sequence);
455 } else {
456 qCWarning(QT_BT_BLUEZ) << "unknown attribute type"
457 << xml.name()
458 << xml.attributes().value(QLatin1String("value"));
459 return QVariant();
460 }
461}
462
463QT_END_NAMESPACE
QMap< QDBusObjectPath, InterfaceList > ManagedObjectList
QBluetoothServiceDiscoveryAgentPrivate(QBluetoothServiceDiscoveryAgent *qp, const QBluetoothAddress &deviceAdapter)
\inmodule QtBluetooth