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
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
6
10
11#include <QtCore/QFile>
12#include <QtCore/QLibraryInfo>
13#include <QtCore/QLoggingCategory>
14#include <QtCore/QProcess>
15#include <QtCore/QScopeGuard>
16
17#include <QtDBus/QDBusPendingCallWatcher>
18
20
22
24 QBluetoothServiceDiscoveryAgent *qp, const QBluetoothAddress &deviceAdapter)
25: error(QBluetoothServiceDiscoveryAgent::NoError), m_deviceAdapterAddress(deviceAdapter), state(Inactive),
26 mode(QBluetoothServiceDiscoveryAgent::MinimalDiscovery), singleDevice(false),
27 q_ptr(qp)
28{
32 qRegisterMetaType<QBluetoothServiceDiscoveryAgent::Error>();
33}
34
36{
37 delete manager;
38}
39
40void QBluetoothServiceDiscoveryAgentPrivate::start(const QBluetoothAddress &address)
41{
43
44 qCDebug(QT_BT_BLUEZ) << "Discovery on: " << address.toString() << "Mode:" << DiscoveryMode();
45
46 if (foundHostAdapterPath.isEmpty()) {
47 // check that we match adapter addresses or use first if it wasn't specified
48
49 bool ok = false;
50 foundHostAdapterPath = findAdapterForAddress(m_deviceAdapterAddress, &ok);
51 if (!ok) {
54 errorString = QBluetoothDeviceDiscoveryAgent::tr("Cannot access adapter during service discovery");
55 emit q->errorOccurred(error);
57 return;
58 }
59
60 if (foundHostAdapterPath.isEmpty()) {
61 // Cannot find a local adapter
62 // Abort any outstanding discoveries
64
66 errorString = QBluetoothServiceDiscoveryAgent::tr("Cannot find local Bluetooth adapter");
67 emit q->errorOccurred(error);
69
70 return;
71 }
72 }
73
74 // ensure we didn't go offline yet
75 OrgBluezAdapter1Interface adapter(QStringLiteral("org.bluez"),
76 foundHostAdapterPath, QDBusConnection::systemBus());
77 if (!adapter.powered()) {
79
81 errorString = QBluetoothServiceDiscoveryAgent::tr("Local device is powered off");
82 emit q->errorOccurred(error);
83
85 return;
86 }
87
89 performMinimalServiceDiscovery(address);
90 } else {
91 runExternalSdpScan(address, QBluetoothAddress(adapter.address()));
92 }
93}
94
95/* Bluez 5
96 * src/tools/sdpscanner performs an SDP scan. This is
97 * done out-of-process to avoid license issues. At this stage Bluez uses GPLv2.
98 */
99void QBluetoothServiceDiscoveryAgentPrivate::runExternalSdpScan(
100 const QBluetoothAddress &remoteAddress, const QBluetoothAddress &localAddress)
101{
103
104 if (!sdpScannerProcess) {
106 QFileInfo fileInfo(binPath, QStringLiteral("sdpscanner"));
107 if (!fileInfo.exists() || !fileInfo.isExecutable()) {
109 QBluetoothServiceDiscoveryAgent::tr("Unable to find sdpscanner"),
110 QStringList());
111 qCWarning(QT_BT_BLUEZ) << "Cannot find sdpscanner:"
112 << fileInfo.canonicalFilePath();
113 return;
114 }
115
116 sdpScannerProcess = new QProcess(q);
117 sdpScannerProcess->setReadChannel(QProcess::StandardOutput);
118 if (QT_BT_BLUEZ().isDebugEnabled())
119 sdpScannerProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel);
120 sdpScannerProcess->setProgram(fileInfo.canonicalFilePath());
121 q->connect(sdpScannerProcess,
122 QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
123 q, [this](int exitCode, QProcess::ExitStatus status){
124 this->_q_sdpScannerDone(exitCode, status);
125 });
126 }
127
129 arguments << remoteAddress.toString() << localAddress.toString();
130
131 // No filter implies PUBLIC_BROWSE_GROUP based SDP scan
132 if (!uuidFilter.isEmpty()) {
133 arguments << QLatin1String("-u"); // cmd line option for list of uuids
134 for (const QBluetoothUuid& uuid : std::as_const(uuidFilter))
135 arguments << uuid.toString();
136 }
137
138 sdpScannerProcess->setArguments(arguments);
139 sdpScannerProcess->start();
140}
141
142void QBluetoothServiceDiscoveryAgentPrivate::_q_sdpScannerDone(int exitCode, QProcess::ExitStatus status)
143{
144 if (status != QProcess::NormalExit || exitCode != 0) {
145 qCWarning(QT_BT_BLUEZ) << "SDP scan failure" << status << exitCode;
146 if (singleDevice) {
148 QBluetoothServiceDiscoveryAgent::tr("Unable to perform SDP scan"),
149 QStringList());
150 } else {
151 // go to next device
153 }
154 return;
155 }
156
157 QStringList xmlRecords;
158 const QByteArray utf8Data = QByteArray::fromBase64(sdpScannerProcess->readAllStandardOutput());
159 const QByteArrayView utf8View = utf8Data;
160
161 // split the various xml docs up
162 constexpr auto matcher = qMakeStaticByteArrayMatcher("<?xml");
164 qsizetype start = matcher.indexIn(utf8View, 0);
165 if (start != -1) {
166 do {
167 next = matcher.indexIn(utf8View, start + 1);
168 if (next != -1)
169 xmlRecords.append(QString::fromUtf8(utf8View.sliced(start, next - start)));
170 else
171 xmlRecords.append(QString::fromUtf8(utf8View.sliced(start)));
172 start = next;
173 } while ( start != -1);
174 }
175
176 _q_finishSdpScan(QBluetoothServiceDiscoveryAgent::NoError, QString(), xmlRecords);
177}
178
179void QBluetoothServiceDiscoveryAgentPrivate::_q_finishSdpScan(QBluetoothServiceDiscoveryAgent::Error errorCode,
180 const QString &errorDescription,
181 const QStringList &xmlRecords)
182{
184
186 qCWarning(QT_BT_BLUEZ) << "SDP search failed for"
188 ? discoveredDevices.at(0).address().toString()
189 : QStringLiteral("<Unknown>"));
190 // We have an error which we need to indicate and stop further processing
192 error = errorCode;
193 errorString = errorDescription;
194 emit q->errorOccurred(error);
195 } else if (!xmlRecords.isEmpty() && discoveryState() != Inactive) {
196 for (const QString &record : xmlRecords) {
197 QBluetoothServiceInfo serviceInfo = parseServiceXml(record);
198
199 //apply uuidFilter
200 if (!uuidFilter.isEmpty()) {
201 bool serviceNameMatched = uuidFilter.contains(serviceInfo.serviceUuid());
202 bool serviceClassMatched = false;
203 const QList<QBluetoothUuid> serviceClassUuids
204 = serviceInfo.serviceClassUuids();
205 for (const QBluetoothUuid &id : serviceClassUuids) {
206 if (uuidFilter.contains(id)) {
207 serviceClassMatched = true;
208 break;
209 }
210 }
211
212 if (!serviceNameMatched && !serviceClassMatched)
213 continue;
214 }
215
216 if (!serviceInfo.isValid())
217 continue;
218
219 // Bluez sdpscanner declares custom uuids into the service class uuid list.
220 // Let's move a potential custom uuid from QBluetoothServiceInfo::serviceClassUuids()
221 // to QBluetoothServiceInfo::serviceUuid(). If there is more than one, just move the first uuid
222 const QList<QBluetoothUuid> serviceClassUuids = serviceInfo.serviceClassUuids();
223 for (const QBluetoothUuid &id : serviceClassUuids) {
224 if (id.minimumSize() == 16) {
225 serviceInfo.setServiceUuid(id);
226 if (serviceInfo.serviceName().isEmpty()) {
227 serviceInfo.setServiceName(
228 QBluetoothServiceDiscoveryAgent::tr("Custom Service"));
229 }
232 modSeq.removeOne(QVariant::fromValue(id));
233 serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, modSeq);
234 break;
235 }
236 }
237
238 if (!isDuplicatedService(serviceInfo)) {
239 discoveredServices.append(serviceInfo);
240 qCDebug(QT_BT_BLUEZ) << "Discovered services" << discoveredDevices.at(0).address().toString()
241 << serviceInfo.serviceName() << serviceInfo.serviceUuid()
242 << ">>>" << serviceInfo.serviceClassUuids();
243 // Use queued connection to allow us finish the service looping; the application
244 // might call stop() when it has detected the service-of-interest.
246 Q_ARG(QBluetoothServiceInfo, serviceInfo));
247 }
248 }
249 }
250
252}
253
254void QBluetoothServiceDiscoveryAgentPrivate::stop()
255{
256 qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "Stop called";
257
260
261 // must happen after discoveredDevices.clear() above to avoid retrigger of next scan
262 // while waitForFinished() is waiting
263 if (sdpScannerProcess) { // Bluez 5
264 if (sdpScannerProcess->state() != QProcess::NotRunning) {
265 sdpScannerProcess->kill();
266 sdpScannerProcess->waitForFinished();
267 }
268 }
269
271 emit q->canceled();
272}
273
274QBluetoothServiceInfo QBluetoothServiceDiscoveryAgentPrivate::parseServiceXml(
275 const QString& xmlRecord)
276{
277 QXmlStreamReader xml(xmlRecord);
278
279 QBluetoothServiceInfo serviceInfo;
280 serviceInfo.setDevice(discoveredDevices.at(0));
281
282 while (!xml.atEnd()) {
283 xml.readNext();
284
285 if (xml.tokenType() == QXmlStreamReader::StartElement &&
286 xml.name() == QLatin1String("attribute")) {
287 quint16 attributeId =
288 xml.attributes().value(QLatin1String("id")).toUShort(nullptr, 0);
289
290 if (xml.readNextStartElement()) {
291 const QVariant value = readAttributeValue(xml);
292 serviceInfo.setAttribute(attributeId, value);
293 }
294 }
295 }
296
297 return serviceInfo;
298}
299
300// Bluez 5
301void QBluetoothServiceDiscoveryAgentPrivate::performMinimalServiceDiscovery(const QBluetoothAddress &deviceAddress)
302{
303 if (foundHostAdapterPath.isEmpty()) {
305 return;
306 }
307
309
310 QDBusPendingReply<ManagedObjectList> reply = manager->GetManagedObjects();
311 reply.waitForFinished();
312 if (reply.isError()) {
313 if (singleDevice) {
315 errorString = reply.error().message();
316 emit q->errorOccurred(error);
317 }
319 return;
320 }
321
322 QStringList uuidStrings;
323
324 ManagedObjectList managedObjectList = reply.value();
325 for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
326 const InterfaceList &ifaceList = it.value();
327
328 for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
329 const QString &iface = jt.key();
330 const QVariantMap &ifaceValues = jt.value();
331
332 if (iface == QStringLiteral("org.bluez.Device1")) {
333 if (deviceAddress.toString() == ifaceValues.value(QStringLiteral("Address")).toString()) {
334 uuidStrings = ifaceValues.value(QStringLiteral("UUIDs")).toStringList();
335 break;
336 }
337 }
338 }
339 if (!uuidStrings.isEmpty())
340 break;
341 }
342
343 if (uuidStrings.isEmpty() || discoveredDevices.isEmpty()) {
344 qCWarning(QT_BT_BLUEZ) << "No uuids found for" << deviceAddress.toString();
345 // nothing found -> go to next uuid
347 return;
348 }
349
350 qCDebug(QT_BT_BLUEZ) << "Minimal uuid list for" << deviceAddress.toString() << uuidStrings;
351
352 QBluetoothUuid uuid;
353 for (qsizetype i = 0; i < uuidStrings.size(); ++i) {
354 uuid = QBluetoothUuid(uuidStrings.at(i));
355 if (uuid.isNull())
356 continue;
357
358 //apply uuidFilter
359 if (!uuidFilter.isEmpty() && !uuidFilter.contains(uuid))
360 continue;
361
362 QBluetoothServiceInfo serviceInfo;
363 serviceInfo.setDevice(discoveredDevices.at(0));
364
365 if (uuid.minimumSize() == 16) { // not derived from Bluetooth Base UUID
366 serviceInfo.setServiceUuid(uuid);
367 serviceInfo.setServiceName(QBluetoothServiceDiscoveryAgent::tr("Custom Service"));
368 } else {
369 // set uuid as service class id
371 classId << QVariant::fromValue(uuid);
372 serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId);
374 = static_cast<QBluetoothUuid::ServiceClassUuid>(uuid.data1 & 0xffff);
375 serviceInfo.setServiceName(QBluetoothUuid::serviceClassToString(clsId));
376 }
377
378 QBluetoothServiceInfo::Sequence protocolDescriptorList;
379 {
382 protocolDescriptorList.append(QVariant::fromValue(protocol));
383 }
384 {
387 protocolDescriptorList.append(QVariant::fromValue(protocol));
388 }
389 serviceInfo.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList);
390
391 //don't include the service if we already discovered it before
392 if (!isDuplicatedService(serviceInfo)) {
393 discoveredServices << serviceInfo;
394 qCDebug(QT_BT_BLUEZ) << "Discovered services" << discoveredDevices.at(0).address().toString()
395 << serviceInfo.serviceName();
396 emit q->serviceDiscovered(serviceInfo);
397 }
398 }
399
401}
402
403QVariant QBluetoothServiceDiscoveryAgentPrivate::readAttributeValue(QXmlStreamReader &xml)
404{
405 auto skippingCurrentElementByDefault = qScopeGuard([&] { xml.skipCurrentElement(); });
406
407 if (xml.name() == QLatin1String("boolean")) {
408 return xml.attributes().value(QLatin1String("value")) == QLatin1String("true");
409 } else if (xml.name() == QLatin1String("uint8")) {
410 quint8 value = xml.attributes().value(QLatin1String("value")).toUShort(nullptr, 0);
411 return value;
412 } else if (xml.name() == QLatin1String("uint16")) {
413 quint16 value = xml.attributes().value(QLatin1String("value")).toUShort(nullptr, 0);
414 return value;
415 } else if (xml.name() == QLatin1String("uint32")) {
416 quint32 value = xml.attributes().value(QLatin1String("value")).toUInt(nullptr, 0);
417 return value;
418 } else if (xml.name() == QLatin1String("uint64")) {
419 quint64 value = xml.attributes().value(QLatin1String("value")).toULongLong(nullptr, 0);
420 return value;
421 } else if (xml.name() == QLatin1String("uuid")) {
422 QBluetoothUuid uuid;
423 const QStringView value = xml.attributes().value(QLatin1String("value"));
424 if (value.startsWith(QLatin1String("0x"))) {
425 if (value.size() == 6) {
426 quint16 v = value.toUShort(nullptr, 0);
427 uuid = QBluetoothUuid(v);
428 } else if (value.size() == 10) {
429 quint32 v = value.toUInt(nullptr, 0);
430 uuid = QBluetoothUuid(v);
431 }
432 } else {
433 uuid = QBluetoothUuid(value.toString());
434 }
435 return QVariant::fromValue(uuid);
436 } else if (xml.name() == QLatin1String("text") || xml.name() == QLatin1String("url")) {
437 const QStringView value = xml.attributes().value(QLatin1String("value"));
438 if (xml.attributes().value(QLatin1String("encoding")) == QLatin1String("hex"))
439 return QString::fromUtf8(QByteArray::fromHex(value.toLatin1()));
440 return value.toString();
441 } else if (xml.name() == QLatin1String("sequence")) {
443
444 skippingCurrentElementByDefault.dismiss(); // we skip several elements here
445
446 while (xml.readNextStartElement()) {
447 QVariant value = readAttributeValue(xml);
448 sequence.append(value);
449 }
450
451 return QVariant::fromValue<QBluetoothServiceInfo::Sequence>(sequence);
452 } else {
453 qCWarning(QT_BT_BLUEZ) << "unknown attribute type"
454 << xml.name()
455 << xml.attributes().value(QLatin1String("value"));
456 return QVariant();
457 }
458}
459
QString findAdapterForAddress(const QBluetoothAddress &wantedAddress, bool *ok=nullptr)
QMap< QDBusObjectPath, InterfaceList > ManagedObjectList
QT_BEGIN_NAMESPACE void initializeBluez5()
\inmodule QtBluetooth
QBluetoothAddress address() const
Returns the address of the device.
QBluetoothServiceDiscoveryAgentPrivate(QBluetoothServiceDiscoveryAgent *qp, const QBluetoothAddress &deviceAdapter)
QBluetoothServiceDiscoveryAgent::DiscoveryMode DiscoveryMode()
Error
This enum describes errors that can occur during service discovery.
\inmodule QtBluetooth
void setDevice(const QBluetoothDeviceInfo &info)
Sets the Bluetooth device that provides this service to device.
\inmodule QtBluetooth
int minimumSize() const
Returns the minimum size in bytes that this UUID can be represented in.
ServiceClassUuid
This enum is a convienience type for Bluetooth service class and profile UUIDs.
static QString serviceClassToString(ServiceClassUuid uuid)
Returns a human-readable and translated name for the given service class represented by uuid.
\inmodule QtCore
Definition qbytearray.h:57
static QByteArray fromHex(const QByteArray &hexEncoded)
Returns a decoded copy of the hex encoded array hexEncoded.
static QByteArray fromBase64(const QByteArray &base64, Base64Options options=Base64Encoding)
static QDBusConnection systemBus()
Returns a QDBusConnection object opened with the system bus.
static QString path(LibraryPath p)
bool isEmpty() const noexcept
Definition qlist.h:401
bool removeOne(const AT &t)
Definition qlist.h:598
const_reference at(qsizetype i) const noexcept
Definition qlist.h:446
T value(qsizetype i) const
Definition qlist.h:664
void append(parameter_type t)
Definition qlist.h:458
void clear()
Definition qlist.h:434
Definition qmap.h:187
T value(const Key &key, const T &defaultValue=T()) const
Definition qmap.h:357
NetworkError error() const
Returns the error that was found during the processing of this request.
const_iterator constBegin() const noexcept
Definition qset.h:139
const_iterator constEnd() const noexcept
Definition qset.h:143
\inmodule QtCore
\inmodule QtCore
Definition qstringview.h:78
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
uint data1
Definition quuid.h:203
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
T value() const &
Definition qvariant.h:516
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
QSet< QString >::iterator it
QList< QVariant > arguments
else opt state
[0]
short next
Definition keywords.cpp:445
Combined button and popup list for selecting options.
constexpr QBindableInterface iface
Definition qproperty.h:666
@ QueuedConnection
constexpr QStaticByteArrayMatcher< N > qMakeStaticByteArrayMatcher(const char(&pattern)[N]) noexcept
#define Q_FUNC_INFO
QList< QString > QStringList
Constructs a string list that contains the given string, str.
DBusConnection const char DBusError * error
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qCWarning(category,...)
#define qCDebug(category,...)
#define Q_DECLARE_LOGGING_CATEGORY(name)
#define Q_ARG(Type, data)
Definition qobjectdefs.h:63
GLsizei const GLfloat * v
[13]
GLenum mode
GLuint start
GLuint GLuint64EXT address
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
QScopeGuard< typename std::decay< F >::type > qScopeGuard(F &&f)
[qScopeGuard]
Definition qscopeguard.h:60
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define QStringLiteral(str)
@ NoError
Definition main.cpp:34
#define emit
static QStringList toStringList(const QJsonArray &jsonArray)
unsigned int quint32
Definition qtypes.h:50
unsigned short quint16
Definition qtypes.h:48
unsigned long long quint64
Definition qtypes.h:61
ptrdiff_t qsizetype
Definition qtypes.h:165
unsigned char quint8
Definition qtypes.h:46
static const auto matcher
[0]
MyRecord record(int row) const
[0]
QXmlStreamReader xml
[0]
QNetworkAccessManager manager
QNetworkReply * reply
char * toString(const MyType &t)
[31]
bool contains(const AT &t) const noexcept
Definition qlist.h:45
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...