8#include "bluez/bluez5_helper_p.h"
9#include "bluez/objectmanager_p.h"
10#include "bluez/adapter1_bluez5_p.h"
12#include <QtCore/QFile>
13#include <QtCore/QLibraryInfo>
14#include <QtCore/QLoggingCategory>
15#include <QtCore/QProcess>
16#include <QtCore/QScopeGuard>
18#include <QtDBus/QDBusPendingCallWatcher>
22Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
24using namespace QtBluetoothPrivate;
26QBluetoothServiceDiscoveryAgentPrivate::QBluetoothServiceDiscoveryAgentPrivate(
27 QBluetoothServiceDiscoveryAgent *qp,
const QBluetoothAddress &deviceAdapter)
28: error(QBluetoothServiceDiscoveryAgent::NoError), m_deviceAdapterAddress(deviceAdapter), state(Inactive),
29 mode(QBluetoothServiceDiscoveryAgent::MinimalDiscovery), singleDevice(
false),
33 manager =
new OrgFreedesktopDBusObjectManagerInterface(
34 QStringLiteral(
"org.bluez"), QStringLiteral(
"/"), QDBusConnection::systemBus());
35 qRegisterMetaType<QBluetoothServiceDiscoveryAgent::Error>();
38QBluetoothServiceDiscoveryAgentPrivate::~QBluetoothServiceDiscoveryAgentPrivate()
43void QBluetoothServiceDiscoveryAgentPrivate::start(
const QBluetoothAddress &address)
45 Q_Q(QBluetoothServiceDiscoveryAgent);
47 qCDebug(QT_BT_BLUEZ) <<
"Discovery on: " << address.toString() <<
"Mode:" << DiscoveryMode();
49 if (foundHostAdapterPath.isEmpty()) {
53 foundHostAdapterPath = findAdapterForAddress(m_deviceAdapterAddress, &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();
63 if (foundHostAdapterPath.isEmpty()) {
66 discoveredDevices.clear();
68 error = QBluetoothServiceDiscoveryAgent::InvalidBluetoothAdapterError;
69 errorString = QBluetoothServiceDiscoveryAgent::tr(
"Cannot find local Bluetooth adapter");
70 emit q->errorOccurred(error);
71 _q_serviceDiscoveryFinished();
78 OrgBluezAdapter1Interface adapter(QStringLiteral(
"org.bluez"),
79 foundHostAdapterPath, QDBusConnection::systemBus());
80 if (!adapter.powered()) {
81 discoveredDevices.clear();
83 error = QBluetoothServiceDiscoveryAgent::PoweredOffError;
84 errorString = QBluetoothServiceDiscoveryAgent::tr(
"Local device is powered off");
85 emit q->errorOccurred(error);
87 _q_serviceDiscoveryFinished();
91 if (DiscoveryMode() == QBluetoothServiceDiscoveryAgent::MinimalDiscovery) {
92 performMinimalServiceDiscovery(address);
94 runExternalSdpScan(address, QBluetoothAddress(adapter.address()));
99
100
101
102void QBluetoothServiceDiscoveryAgentPrivate::runExternalSdpScan(
103 const QBluetoothAddress &remoteAddress,
const QBluetoothAddress &localAddress)
105 Q_Q(QBluetoothServiceDiscoveryAgent);
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"),
114 qCWarning(QT_BT_BLUEZ) <<
"Cannot find sdpscanner:"
115 << fileInfo.canonicalFilePath();
119 sdpScannerProcess =
new QProcess(q);
120 sdpScannerProcess->setReadChannel(QProcess::StandardOutput);
121 if (QT_BT_BLUEZ().isDebugEnabled())
122 sdpScannerProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel);
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);
132 QStringList arguments;
133 arguments << remoteAddress.toString() << localAddress.toString();
136 if (!uuidFilter.isEmpty()) {
137 arguments << QLatin1String(
"-u");
138 for (
const QBluetoothUuid& uuid : std::as_const(uuidFilter))
139 arguments << uuid.toString();
142 sdpScannerProcess->setArguments(arguments);
143 sdpScannerProcess->start();
146void QBluetoothServiceDiscoveryAgentPrivate::_q_sdpScannerDone(
int exitCode, QProcess::ExitStatus status)
148 if (status != QProcess::NormalExit || exitCode != 0) {
149 qCWarning(QT_BT_BLUEZ) <<
"SDP scan failure" << status << exitCode;
151 _q_finishSdpScan(QBluetoothServiceDiscoveryAgent::InputOutputError,
152 QBluetoothServiceDiscoveryAgent::tr(
"Unable to perform SDP scan"),
156 _q_finishSdpScan(QBluetoothServiceDiscoveryAgent::NoError, QString(), QStringList());
161 QStringList xmlRecords;
162 const QByteArray utf8Data = QByteArray::fromBase64(sdpScannerProcess->readAllStandardOutput());
163 const QByteArrayView utf8View = utf8Data;
166 constexpr auto matcher = qMakeStaticByteArrayMatcher(
"<?xml");
168 qsizetype start = matcher.indexIn(utf8View, 0);
171 next = matcher.indexIn(utf8View, start + 1);
173 xmlRecords.append(QString::fromUtf8(utf8View.sliced(start, next - start)));
175 xmlRecords.append(QString::fromUtf8(utf8View.sliced(start)));
177 }
while ( start != -1);
180 _q_finishSdpScan(QBluetoothServiceDiscoveryAgent::NoError, QString(), xmlRecords);
183void QBluetoothServiceDiscoveryAgentPrivate::_q_finishSdpScan(QBluetoothServiceDiscoveryAgent::Error errorCode,
184 const QString &errorDescription,
185 const QStringList &xmlRecords)
187 Q_Q(QBluetoothServiceDiscoveryAgent);
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>"));
195 discoveredDevices.clear();
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);
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;
216 if (!serviceNameMatched && !serviceClassMatched)
220 if (!serviceInfo.isValid())
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"));
234 QBluetoothServiceInfo::Sequence modSeq =
235 serviceInfo.attribute(QBluetoothServiceInfo::ServiceClassIds).value<QBluetoothServiceInfo::Sequence>();
236 modSeq.removeOne(QVariant::fromValue(id));
237 serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, modSeq);
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();
249 QMetaObject::invokeMethod(q,
"serviceDiscovered", Qt::QueuedConnection,
250 Q_ARG(QBluetoothServiceInfo, serviceInfo));
255 _q_serviceDiscoveryFinished();
258void QBluetoothServiceDiscoveryAgentPrivate::stop()
260 qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO <<
"Stop called";
262 discoveredDevices.clear();
263 setDiscoveryState(Inactive);
267 if (sdpScannerProcess) {
268 if (sdpScannerProcess->state() != QProcess::NotRunning) {
269 sdpScannerProcess->kill();
270 sdpScannerProcess->waitForFinished();
274 Q_Q(QBluetoothServiceDiscoveryAgent);
278QBluetoothServiceInfo QBluetoothServiceDiscoveryAgentPrivate::parseServiceXml(
279 const QString& xmlRecord)
281 QXmlStreamReader xml(xmlRecord);
283 QBluetoothServiceInfo serviceInfo;
284 serviceInfo.setDevice(discoveredDevices.at(0));
286 while (!xml.atEnd()) {
289 if (xml.tokenType() == QXmlStreamReader::StartElement &&
290 xml.name() == QLatin1String(
"attribute")) {
291 quint16 attributeId =
292 xml.attributes().value(QLatin1String(
"id")).toUShort(
nullptr, 0);
294 if (xml.readNextStartElement()) {
295 const QVariant value = readAttributeValue(xml);
296 serviceInfo.setAttribute(attributeId, value);
305void QBluetoothServiceDiscoveryAgentPrivate::performMinimalServiceDiscovery(
const QBluetoothAddress &deviceAddress)
307 if (foundHostAdapterPath.isEmpty()) {
308 _q_serviceDiscoveryFinished();
312 Q_Q(QBluetoothServiceDiscoveryAgent);
314 QDBusPendingReply<ManagedObjectList> reply = manager->GetManagedObjects();
315 reply.waitForFinished();
316 if (reply.isError()) {
318 error = QBluetoothServiceDiscoveryAgent::InputOutputError;
319 errorString = reply.error().message();
320 emit q->errorOccurred(error);
322 _q_serviceDiscoveryFinished();
326 QStringList uuidStrings;
328 ManagedObjectList managedObjectList = reply.value();
329 for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
330 const InterfaceList &ifaceList = it.value();
332 for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
333 const QString &iface = jt.key();
334 const QVariantMap &ifaceValues = jt.value();
336 if (iface == QStringLiteral(
"org.bluez.Device1")) {
337 if (deviceAddress.toString() == ifaceValues.value(QStringLiteral(
"Address")).toString()) {
338 uuidStrings = ifaceValues.value(QStringLiteral(
"UUIDs")).toStringList();
343 if (!uuidStrings.isEmpty())
347 if (uuidStrings.isEmpty() || discoveredDevices.isEmpty()) {
348 qCWarning(QT_BT_BLUEZ) <<
"No uuids found for" << deviceAddress.toString();
350 _q_serviceDiscoveryFinished();
354 qCDebug(QT_BT_BLUEZ) <<
"Minimal uuid list for" << deviceAddress.toString() << uuidStrings;
357 for (qsizetype i = 0; i < uuidStrings.size(); ++i) {
358 uuid = QBluetoothUuid(uuidStrings.at(i));
363 if (!uuidFilter.isEmpty() && !uuidFilter.contains(uuid))
366 QBluetoothServiceInfo serviceInfo;
367 serviceInfo.setDevice(discoveredDevices.at(0));
369 if (uuid.minimumSize() == 16) {
370 serviceInfo.setServiceUuid(uuid);
371 serviceInfo.setServiceName(QBluetoothServiceDiscoveryAgent::tr(
"Custom Service"));
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));
382 QBluetoothServiceInfo::Sequence protocolDescriptorList;
384 QBluetoothServiceInfo::Sequence protocol;
385 protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::ProtocolUuid::L2cap));
386 protocolDescriptorList.append(QVariant::fromValue(protocol));
389 QBluetoothServiceInfo::Sequence protocol;
390 protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::ProtocolUuid::Att));
391 protocolDescriptorList.append(QVariant::fromValue(protocol));
393 serviceInfo.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList);
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);
404 _q_serviceDiscoveryFinished();
407QVariant QBluetoothServiceDiscoveryAgentPrivate::readAttributeValue(QXmlStreamReader &xml,
410 auto skippingCurrentElementByDefault = qScopeGuard([&] { xml.skipCurrentElement(); });
412 if (depth > kMaxSdpRecursionDepth) {
413 qCWarning(QT_BT_BLUEZ) <<
"SDP XML attribute recursion depth exceeded";
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);
422 }
else if (xml.name() == QLatin1String(
"uint16")) {
423 quint16 value = xml.attributes().value(QLatin1String(
"value")).toUShort(
nullptr, 0);
425 }
else if (xml.name() == QLatin1String(
"uint32")) {
426 quint32 value = xml.attributes().value(QLatin1String(
"value")).toUInt(
nullptr, 0);
428 }
else if (xml.name() == QLatin1String(
"uint64")) {
429 quint64 value = xml.attributes().value(QLatin1String(
"value")).toULongLong(
nullptr, 0);
431 }
else if (xml.name() == QLatin1String(
"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);
443 uuid = QBluetoothUuid(value.toString());
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;
454 skippingCurrentElementByDefault.dismiss();
456 while (xml.readNextStartElement()) {
457 QVariant value = readAttributeValue(xml, depth + 1);
458 sequence.append(value);
461 return QVariant::fromValue<QBluetoothServiceInfo::Sequence>(sequence);
463 qCWarning(QT_BT_BLUEZ) <<
"unknown attribute type"
465 << xml.attributes().value(QLatin1String(
"value"));