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;
27 QBluetoothServiceDiscoveryAgent *qp,
const QBluetoothAddress &deviceAdapter)
33 manager =
new OrgFreedesktopDBusObjectManagerInterface(
34 QStringLiteral(
"org.bluez"), QStringLiteral(
"/"), QDBusConnection::systemBus());
35 qRegisterMetaType<QBluetoothServiceDiscoveryAgent::Error>();
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);
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);
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);
91 if (DiscoveryMode() == QBluetoothServiceDiscoveryAgent::MinimalDiscovery) {
92 performMinimalServiceDiscovery(address);
94 runExternalSdpScan(address, QBluetoothAddress(adapter.address()));
99
100
101
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);
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);
131 QStringList arguments;
132 arguments << remoteAddress.toString() << localAddress.toString();
135 if (!uuidFilter.isEmpty()) {
136 arguments << QLatin1String(
"-u");
137 for (
const QBluetoothUuid& uuid : std::as_const(uuidFilter))
138 arguments << uuid.toString();
141 sdpScannerProcess->setArguments(arguments);
142 sdpScannerProcess->start();
147 if (status != QProcess::NormalExit || exitCode != 0) {
148 qCWarning(QT_BT_BLUEZ) <<
"SDP scan failure" << status << exitCode;
150 _q_finishSdpScan(QBluetoothServiceDiscoveryAgent::InputOutputError,
151 QBluetoothServiceDiscoveryAgent::tr(
"Unable to perform SDP scan"),
155 _q_finishSdpScan(QBluetoothServiceDiscoveryAgent::NoError, QString(), QStringList());
160 QStringList xmlRecords;
161 const QByteArray utf8Data = QByteArray::fromBase64(sdpScannerProcess->readAllStandardOutput());
162 const QByteArrayView utf8View = utf8Data;
165 constexpr auto matcher = qMakeStaticByteArrayMatcher(
"<?xml");
167 qsizetype start = matcher.indexIn(utf8View, 0);
170 next = matcher.indexIn(utf8View, start + 1);
172 xmlRecords.append(QString::fromUtf8(utf8View.sliced(start, next - start)));
174 xmlRecords.append(QString::fromUtf8(utf8View.sliced(start)));
176 }
while ( start != -1);
179 _q_finishSdpScan(QBluetoothServiceDiscoveryAgent::NoError, QString(), xmlRecords);
183 const QString &errorDescription,
184 const QStringList &xmlRecords)
186 Q_Q(QBluetoothServiceDiscoveryAgent);
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>"));
194 discoveredDevices.clear();
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);
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;
215 if (!serviceNameMatched && !serviceClassMatched)
219 if (!serviceInfo.isValid())
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"));
233 QBluetoothServiceInfo::Sequence modSeq =
234 serviceInfo.attribute(QBluetoothServiceInfo::ServiceClassIds).value<QBluetoothServiceInfo::Sequence>();
235 modSeq.removeOne(QVariant::fromValue(id));
236 serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, modSeq);
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();
248 QMetaObject::invokeMethod(q,
"serviceDiscovered", Qt::QueuedConnection,
249 Q_ARG(QBluetoothServiceInfo, serviceInfo));
259 qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO <<
"Stop called";
261 discoveredDevices.clear();
262 setDiscoveryState(Inactive);
266 if (sdpScannerProcess) {
267 if (sdpScannerProcess->state() != QProcess::NotRunning) {
268 sdpScannerProcess->kill();
269 sdpScannerProcess->waitForFinished();
273 Q_Q(QBluetoothServiceDiscoveryAgent);
278 const QString& xmlRecord)
280 QXmlStreamReader xml(xmlRecord);
283 serviceInfo.setDevice(discoveredDevices.at(0));
285 while (!xml.atEnd()) {
288 if (xml.tokenType() == QXmlStreamReader::StartElement &&
289 xml.name() == QLatin1String(
"attribute")) {
290 quint16 attributeId =
291 xml.attributes().value(QLatin1String(
"id")).toUShort(
nullptr, 0);
293 if (xml.readNextStartElement()) {
294 const QVariant value = readAttributeValue(xml);
295 serviceInfo.setAttribute(attributeId, value);
306 if (foundHostAdapterPath.isEmpty()) {
311 Q_Q(QBluetoothServiceDiscoveryAgent);
313 QDBusPendingReply<ManagedObjectList> reply = manager->GetManagedObjects();
314 reply.waitForFinished();
315 if (reply.isError()) {
317 error = QBluetoothServiceDiscoveryAgent::InputOutputError;
318 errorString = reply.error().message();
319 emit q->errorOccurred(error);
325 QStringList uuidStrings;
328 for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
329 const InterfaceList &ifaceList = it.value();
331 for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
332 const QString &iface = jt.key();
333 const QVariantMap &ifaceValues = jt.value();
335 if (iface == QStringLiteral(
"org.bluez.Device1")) {
336 if (deviceAddress.toString() == ifaceValues.value(QStringLiteral(
"Address")).toString()) {
337 uuidStrings = ifaceValues.value(QStringLiteral(
"UUIDs")).toStringList();
342 if (!uuidStrings.isEmpty())
346 if (uuidStrings.isEmpty() || discoveredDevices.isEmpty()) {
347 qCWarning(QT_BT_BLUEZ) <<
"No uuids found for" << deviceAddress.toString();
353 qCDebug(QT_BT_BLUEZ) <<
"Minimal uuid list for" << deviceAddress.toString() << uuidStrings;
356 for (qsizetype i = 0; i < uuidStrings.size(); ++i) {
357 uuid = QBluetoothUuid(uuidStrings.at(i));
362 if (!uuidFilter.isEmpty() && !uuidFilter.contains(uuid))
366 serviceInfo.setDevice(discoveredDevices.at(0));
368 if (uuid.minimumSize() == 16) {
369 serviceInfo.setServiceUuid(uuid);
370 serviceInfo.setServiceName(QBluetoothServiceDiscoveryAgent::tr(
"Custom Service"));
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));
381 QBluetoothServiceInfo::Sequence protocolDescriptorList;
383 QBluetoothServiceInfo::Sequence protocol;
384 protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::ProtocolUuid::L2cap));
385 protocolDescriptorList.append(QVariant::fromValue(protocol));
388 QBluetoothServiceInfo::Sequence protocol;
389 protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::ProtocolUuid::Att));
390 protocolDescriptorList.append(QVariant::fromValue(protocol));
392 serviceInfo.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList);
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);
408 auto skippingCurrentElementByDefault = qScopeGuard([&] { xml.skipCurrentElement(); });
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);
415 }
else if (xml.name() == QLatin1String(
"uint16")) {
416 quint16 value = xml.attributes().value(QLatin1String(
"value")).toUShort(
nullptr, 0);
418 }
else if (xml.name() == QLatin1String(
"uint32")) {
419 quint32 value = xml.attributes().value(QLatin1String(
"value")).toUInt(
nullptr, 0);
421 }
else if (xml.name() == QLatin1String(
"uint64")) {
422 quint64 value = xml.attributes().value(QLatin1String(
"value")).toULongLong(
nullptr, 0);
424 }
else if (xml.name() == QLatin1String(
"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);
436 uuid = QBluetoothUuid(value.toString());
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;
447 skippingCurrentElementByDefault.dismiss();
449 while (xml.readNextStartElement()) {
450 QVariant value = readAttributeValue(xml);
451 sequence.append(value);
454 return QVariant::fromValue<QBluetoothServiceInfo::Sequence>(sequence);
456 qCWarning(QT_BT_BLUEZ) <<
"unknown attribute type"
458 << xml.attributes().value(QLatin1String(
"value"));
QMap< QDBusObjectPath, InterfaceList > ManagedObjectList
~QBluetoothServiceDiscoveryAgentPrivate()
void _q_serviceDiscoveryFinished()
QBluetoothServiceDiscoveryAgentPrivate(QBluetoothServiceDiscoveryAgent *qp, const QBluetoothAddress &deviceAdapter)