| 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 | |
| 5 | #include <QtCore/QGlobalStatic> |
| 6 | #include <QtCore/QLoggingCategory> |
| 7 | #include <QtCore/QMap> |
| 8 | #include <QtCore/QVersionNumber> |
| 9 | #include <QtNetwork/private/qnet_unix_p.h> |
| 10 | #include "bluez5_helper_p.h" |
| 11 | #include "bluez_data_p.h" |
| 12 | #include "objectmanager_p.h" |
| 13 | #include "properties_p.h" |
| 14 | #include "adapter1_bluez5_p.h" |
| 15 | |
| 16 | QT_BEGIN_NAMESPACE |
| 17 | |
| 18 | QT_IMPL_METATYPE_EXTERN(InterfaceList) |
| 19 | QT_IMPL_METATYPE_EXTERN(ManufacturerDataList) |
| 20 | QT_IMPL_METATYPE_EXTERN(ServiceDataList) |
| 21 | QT_IMPL_METATYPE_EXTERN(ManagedObjectList) |
| 22 | |
| 23 | |
| 24 | Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ) |
| 25 | |
| 26 | using namespace QtBluetoothPrivate; // for D-Bus wrappers |
| 27 | |
| 28 | typedef enum Bluez5TestResultType |
| 29 | { |
| 30 | BluezVersionUnknown, |
| 31 | BluezVersion5, |
| 32 | BluezNotAvailable |
| 33 | } Bluez5TestResult; |
| 34 | |
| 35 | Q_GLOBAL_STATIC_WITH_ARGS(Bluez5TestResult, bluezVersion, (BluezVersionUnknown)) |
| 36 | Q_GLOBAL_STATIC_WITH_ARGS(QVersionNumber, bluezDaemonVersion, (QVersionNumber())) |
| 37 | |
| 38 | /* |
| 39 | Ensures that the DBus types are registered |
| 40 | */ |
| 41 | |
| 42 | void initializeBluez5() |
| 43 | { |
| 44 | if (*bluezVersion() == BluezVersionUnknown) { |
| 45 | OrgFreedesktopDBusObjectManagerInterface manager(QStringLiteral("org.bluez" ), |
| 46 | QStringLiteral("/" ), |
| 47 | QDBusConnection::systemBus()); |
| 48 | qDBusRegisterMetaType<InterfaceList>(); |
| 49 | qDBusRegisterMetaType<ManagedObjectList>(); |
| 50 | qDBusRegisterMetaType<ManufacturerDataList>(); |
| 51 | qDBusRegisterMetaType<ServiceDataList>(); |
| 52 | |
| 53 | QDBusPendingReply<ManagedObjectList> reply = manager.GetManagedObjects(); |
| 54 | reply.waitForFinished(); |
| 55 | if (reply.isError()) { |
| 56 | *bluezVersion() = BluezNotAvailable; |
| 57 | qWarning() << "Cannot find a compatible running Bluez. " |
| 58 | "Please check the Bluez installation. " |
| 59 | "QtBluetooth requires at least BlueZ version 5." ; |
| 60 | } else { |
| 61 | *bluezVersion() = BluezVersion5; |
| 62 | qCDebug(QT_BT_BLUEZ) << "Bluez 5 detected." ; |
| 63 | } |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | /* |
| 68 | Checks that the mandatory Bluetooth HCI ioctls are offered |
| 69 | by Linux kernel. Returns \c true if the ictls are available; otherwise \c false. |
| 70 | |
| 71 | Mandatory ioctls: |
| 72 | - HCIGETCONNLIST |
| 73 | - HCIGETDEVINFO |
| 74 | - HCIGETDEVLIST |
| 75 | */ |
| 76 | bool mandatoryHciIoctlsAvailable() |
| 77 | { |
| 78 | // open hci socket |
| 79 | int hciSocket = ::qt_safe_socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); |
| 80 | if (hciSocket < 0) { |
| 81 | qCWarning(QT_BT_BLUEZ) << "Cannot open HCI socket:" << qt_error_string(errno); |
| 82 | return false; |
| 83 | } |
| 84 | |
| 85 | // check HCIGETDEVLIST & HCIGETDEVLIST |
| 86 | struct hci_dev_req *devRequest = nullptr; |
| 87 | struct hci_dev_list_req *devRequestList = nullptr; |
| 88 | struct hci_dev_info devInfo; |
| 89 | const int devListSize = sizeof(struct hci_dev_list_req) |
| 90 | + HCI_MAX_DEV * sizeof(struct hci_dev_req); |
| 91 | |
| 92 | devRequestList = (hci_dev_list_req *) malloc(size: devListSize); |
| 93 | if (!devRequestList) { |
| 94 | qt_safe_close(fd: hciSocket); |
| 95 | return false; // if we cannot malloc nothing will help anyway |
| 96 | } |
| 97 | |
| 98 | QScopedPointer<hci_dev_list_req, QScopedPointerPodDeleter> pDevList(devRequestList); |
| 99 | memset(s: pDevList.data(), c: 0, n: devListSize); |
| 100 | pDevList->dev_num = HCI_MAX_DEV; |
| 101 | devRequest = pDevList->dev_req; |
| 102 | |
| 103 | if (qt_safe_ioctl(sockfd: hciSocket, HCIGETDEVLIST, arg: devRequestList) < 0) { |
| 104 | qt_safe_close(fd: hciSocket); |
| 105 | qCWarning(QT_BT_BLUEZ) << "HCI icotl HCIGETDEVLIST:" << qt_error_string(errno); |
| 106 | return false; |
| 107 | } |
| 108 | |
| 109 | if (devRequestList->dev_num > 0) { |
| 110 | devInfo.dev_id = devRequest->dev_id; |
| 111 | if (qt_safe_ioctl(sockfd: hciSocket, HCIGETDEVINFO, arg: &devInfo) < 0) { |
| 112 | qt_safe_close(fd: hciSocket); |
| 113 | qCWarning(QT_BT_BLUEZ) << "HCI icotl HCIGETDEVINFO:" << qt_error_string(errno); |
| 114 | return false; |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | // check HCIGETCONNLIST |
| 119 | const int maxNoOfConnections = 20; |
| 120 | hci_conn_list_req *infoList = nullptr; |
| 121 | infoList = (hci_conn_list_req *) |
| 122 | malloc(size: sizeof(hci_conn_list_req) + maxNoOfConnections * sizeof(hci_conn_info)); |
| 123 | |
| 124 | if (!infoList) { |
| 125 | qt_safe_close(fd: hciSocket); |
| 126 | return false; |
| 127 | } |
| 128 | |
| 129 | QScopedPointer<hci_conn_list_req, QScopedPointerPodDeleter> pInfoList(infoList); |
| 130 | pInfoList->conn_num = maxNoOfConnections; |
| 131 | pInfoList->dev_id = devInfo.dev_id; |
| 132 | |
| 133 | if (qt_safe_ioctl(sockfd: hciSocket, HCIGETCONNLIST, arg: (void *) infoList) < 0) { |
| 134 | qCWarning(QT_BT_BLUEZ) << "HCI icotl HCIGETCONNLIST:" << qt_error_string(errno); |
| 135 | qt_safe_close(fd: hciSocket); |
| 136 | return false; |
| 137 | } |
| 138 | |
| 139 | qt_safe_close(fd: hciSocket); |
| 140 | return true; |
| 141 | } |
| 142 | |
| 143 | /* |
| 144 | * This function returns the version of bluetoothd in use on the system. |
| 145 | * This is required to determine which QLEControllerPrivate implementation |
| 146 | * is required. The following version tags are of significance: |
| 147 | * |
| 148 | * Version < 4.0 -> QLEControllerPrivateCommon |
| 149 | * Version < 5.42 -> QLEControllerPrivateBluez |
| 150 | * Version >= 5.42 -> QLEControllerPrivateBluezDBus |
| 151 | * |
| 152 | * This function utilizes a singleton pattern. It always returns a cached |
| 153 | * version tag which is determined on first call. This is necessary to |
| 154 | * avoid continuesly running the somewhat expensive tests. |
| 155 | * |
| 156 | * The function must never return a null QVersionNumber. |
| 157 | */ |
| 158 | QVersionNumber bluetoothdVersion() |
| 159 | { |
| 160 | if (bluezDaemonVersion()->isNull()) { |
| 161 | // Register DBus specific meta types (copied from isBluez5()) |
| 162 | // Not all code paths run through isBluez5() but still need the |
| 163 | // registration. |
| 164 | qDBusRegisterMetaType<InterfaceList>(); |
| 165 | qDBusRegisterMetaType<ManagedObjectList>(); |
| 166 | qDBusRegisterMetaType<ManufacturerDataList>(); |
| 167 | qDBusRegisterMetaType<ServiceDataList>(); |
| 168 | |
| 169 | qCDebug(QT_BT_BLUEZ) << "Detecting bluetoothd version" ; |
| 170 | //Order of matching |
| 171 | // 1. Pick whatever the user decides via BLUETOOTH_FORCE_DBUS_LE_VERSION |
| 172 | // Set version to below version 5.42 to use custom/old GATT stack implementation |
| 173 | const QString version = qEnvironmentVariable(varName: "BLUETOOTH_FORCE_DBUS_LE_VERSION" ); |
| 174 | if (!version.isNull()) { |
| 175 | const QVersionNumber vn = QVersionNumber::fromString(string: version); |
| 176 | if (!vn.isNull()) { |
| 177 | *bluezDaemonVersion() = vn; |
| 178 | qCDebug(QT_BT_BLUEZ) << "Forcing Bluez LE API selection:" |
| 179 | << bluezDaemonVersion()->toString(); |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | // 2. Find bluetoothd binary and check "bluetoothd --version" |
| 184 | if (bluezDaemonVersion()->isNull() && qt_haveLinuxProcfs()) { |
| 185 | QDBusConnection session = QDBusConnection::systemBus(); |
| 186 | qint64 pid = session.interface()->servicePid(QStringLiteral("org.bluez" )).value(); |
| 187 | QByteArray buffer; |
| 188 | |
| 189 | auto determineBinaryVersion = [](const QString &binary) -> QVersionNumber { |
| 190 | QProcess process; |
| 191 | process.start(program: binary, arguments: {QStringLiteral("--version" )}); |
| 192 | process.waitForFinished(); |
| 193 | |
| 194 | const QString version = QString::fromLocal8Bit(ba: process.readAll()); |
| 195 | const QVersionNumber vn = QVersionNumber::fromString(string: version); |
| 196 | if (!vn.isNull()) |
| 197 | qCDebug(QT_BT_BLUEZ) << "Detected bluetoothd version" << vn; |
| 198 | return vn; |
| 199 | }; |
| 200 | |
| 201 | //try reading /proc/<pid>/exe first -> requires process owner |
| 202 | qCDebug(QT_BT_BLUEZ) << "Using /proc/<pid>/exe" ; |
| 203 | const QString procExe = QStringLiteral("/proc/%1/exe" ).arg(a: pid); |
| 204 | const QVersionNumber vn = determineBinaryVersion(procExe); |
| 205 | if (!vn.isNull()) |
| 206 | *bluezDaemonVersion() = vn; |
| 207 | |
| 208 | if (bluezDaemonVersion()->isNull()) { |
| 209 | qCDebug(QT_BT_BLUEZ) << "Using /proc/<pid>/cmdline" ; |
| 210 | //try to reading /proc/<pid>/cmdline (does not require additional process rights) |
| 211 | QFile procFile(QStringLiteral("/proc/%1/cmdline" ).arg(a: pid)); |
| 212 | if (procFile.open(flags: QIODevice::ReadOnly|QIODevice::Text)) { |
| 213 | buffer = procFile.readAll(); |
| 214 | procFile.close(); |
| 215 | |
| 216 | // cmdline params separated by character \0 -> first is bluetoothd binary |
| 217 | const QString binary = QString::fromLocal8Bit(ba: buffer.split(sep: '\0').at(i: 0)); |
| 218 | QFileInfo info(binary); |
| 219 | if (info.isExecutable()) |
| 220 | *bluezDaemonVersion() = determineBinaryVersion(binary); |
| 221 | else |
| 222 | qCDebug(QT_BT_BLUEZ) << "Cannot determine bluetoothd version via cmdline:" |
| 223 | << binary; |
| 224 | } |
| 225 | } |
| 226 | } |
| 227 | |
| 228 | // 3. Fall back to custom ATT backend, if possible? |
| 229 | if (bluezDaemonVersion()->isNull()) { |
| 230 | // Check mandatory HCI ioctls are available |
| 231 | if (mandatoryHciIoctlsAvailable()) { |
| 232 | // default 4.0 for now -> implies custom (G)ATT implementation |
| 233 | *bluezDaemonVersion() = QVersionNumber(4, 0); |
| 234 | } |
| 235 | } |
| 236 | |
| 237 | // 4. Ultimate fallback -> enable dummy backend |
| 238 | if (bluezDaemonVersion()->isNull()) { |
| 239 | // version 3 represents disabled BTLE |
| 240 | // bluezDaemonVersion should not be null to avoid repeated version tests |
| 241 | *bluezDaemonVersion() = QVersionNumber(3, 0); |
| 242 | qCWarning(QT_BT_BLUEZ) << "Cannot determine bluetoothd version and required Bluetooth HCI ioctols" ; |
| 243 | qCWarning(QT_BT_BLUEZ) << "Disabling Qt Bluetooth LE feature" ; |
| 244 | } |
| 245 | |
| 246 | qCDebug(QT_BT_BLUEZ) << "Bluetoothd:" << bluezDaemonVersion()->toString(); |
| 247 | } |
| 248 | |
| 249 | Q_ASSERT(!bluezDaemonVersion()->isNull()); |
| 250 | return *bluezDaemonVersion(); |
| 251 | } |
| 252 | |
| 253 | struct AdapterData |
| 254 | { |
| 255 | public: |
| 256 | AdapterData() : reference(1), wasListeningAlready(false) {} |
| 257 | |
| 258 | int reference; |
| 259 | bool wasListeningAlready; |
| 260 | OrgFreedesktopDBusPropertiesInterface *propteryListener = nullptr; |
| 261 | }; |
| 262 | |
| 263 | class QtBluezDiscoveryManagerPrivate |
| 264 | { |
| 265 | public: |
| 266 | QMap<QString, AdapterData *> references; |
| 267 | OrgFreedesktopDBusObjectManagerInterface *manager = nullptr; |
| 268 | }; |
| 269 | |
| 270 | Q_GLOBAL_STATIC(QtBluezDiscoveryManager, discoveryManager) |
| 271 | |
| 272 | /*! |
| 273 | \internal |
| 274 | \class QtBluezDiscoveryManager |
| 275 | |
| 276 | This class manages the access to "org.bluez.Adapter1::Start/StopDiscovery. |
| 277 | |
| 278 | The flag is a system flag. We want to ensure that the various Qt classes don't turn |
| 279 | the flag on and off and thereby get into their way. If some other system component |
| 280 | changes the flag (e.g. adapter removed) we notify all Qt classes about the change by emitting |
| 281 | \l discoveryInterrupted(QString). Classes should indicate this via an appropriate |
| 282 | error message to the user. |
| 283 | |
| 284 | Once the signal was emitted, all existing requests for discovery mode on the same adapter |
| 285 | have to be renewed via \l registerDiscoveryInterest(QString). |
| 286 | */ |
| 287 | |
| 288 | QtBluezDiscoveryManager::QtBluezDiscoveryManager(QObject *parent) : |
| 289 | QObject(parent) |
| 290 | { |
| 291 | qCDebug(QT_BT_BLUEZ) << "Creating QtBluezDiscoveryManager" ; |
| 292 | d = new QtBluezDiscoveryManagerPrivate(); |
| 293 | |
| 294 | d->manager = new OrgFreedesktopDBusObjectManagerInterface( |
| 295 | QStringLiteral("org.bluez" ), QStringLiteral("/" ), |
| 296 | QDBusConnection::systemBus(), this); |
| 297 | connect(asender: d->manager, SIGNAL(InterfacesRemoved(QDBusObjectPath,QStringList)), |
| 298 | SLOT(InterfacesRemoved(QDBusObjectPath,QStringList))); |
| 299 | } |
| 300 | |
| 301 | QtBluezDiscoveryManager::~QtBluezDiscoveryManager() |
| 302 | { |
| 303 | qCDebug(QT_BT_BLUEZ) << "Destroying QtBluezDiscoveryManager" ; |
| 304 | |
| 305 | const QList<QString> adapterPaths = d->references.keys(); |
| 306 | for (const QString &adapterPath : adapterPaths) { |
| 307 | AdapterData *data = d->references.take(key: adapterPath); |
| 308 | delete data->propteryListener; |
| 309 | |
| 310 | // turn discovery off if it wasn't on already |
| 311 | if (!data->wasListeningAlready) { |
| 312 | OrgBluezAdapter1Interface iface(QStringLiteral("org.bluez" ), adapterPath, |
| 313 | QDBusConnection::systemBus()); |
| 314 | iface.StopDiscovery(); |
| 315 | } |
| 316 | |
| 317 | delete data; |
| 318 | } |
| 319 | |
| 320 | delete d; |
| 321 | } |
| 322 | |
| 323 | QtBluezDiscoveryManager *QtBluezDiscoveryManager::instance() |
| 324 | { |
| 325 | return discoveryManager(); |
| 326 | } |
| 327 | |
| 328 | bool QtBluezDiscoveryManager::registerDiscoveryInterest(const QString &adapterPath) |
| 329 | { |
| 330 | if (adapterPath.isEmpty()) |
| 331 | return false; |
| 332 | |
| 333 | // already monitored adapter? -> increase ref count -> done |
| 334 | if (d->references.contains(key: adapterPath)) { |
| 335 | d->references[adapterPath]->reference++; |
| 336 | return true; |
| 337 | } |
| 338 | |
| 339 | AdapterData *data = new AdapterData(); |
| 340 | |
| 341 | OrgFreedesktopDBusPropertiesInterface *propIface = new OrgFreedesktopDBusPropertiesInterface( |
| 342 | QStringLiteral("org.bluez" ), adapterPath, QDBusConnection::systemBus()); |
| 343 | connect(sender: propIface, signal: &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged, |
| 344 | context: this, slot: &QtBluezDiscoveryManager::PropertiesChanged); |
| 345 | data->propteryListener = propIface; |
| 346 | |
| 347 | OrgBluezAdapter1Interface iface(QStringLiteral("org.bluez" ), adapterPath, |
| 348 | QDBusConnection::systemBus()); |
| 349 | data->wasListeningAlready = iface.discovering(); |
| 350 | |
| 351 | d->references[adapterPath] = data; |
| 352 | |
| 353 | if (!data->wasListeningAlready) |
| 354 | iface.StartDiscovery(); |
| 355 | |
| 356 | return true; |
| 357 | } |
| 358 | |
| 359 | void QtBluezDiscoveryManager::unregisterDiscoveryInterest(const QString &adapterPath) |
| 360 | { |
| 361 | if (!d->references.contains(key: adapterPath)) |
| 362 | return; |
| 363 | |
| 364 | AdapterData *data = d->references[adapterPath]; |
| 365 | data->reference--; |
| 366 | |
| 367 | if (data->reference > 0) // more than one client requested discovery mode |
| 368 | return; |
| 369 | |
| 370 | d->references.remove(key: adapterPath); |
| 371 | if (!data->wasListeningAlready) { // Qt turned discovery mode on, Qt has to turn it off again |
| 372 | OrgBluezAdapter1Interface iface(QStringLiteral("org.bluez" ), adapterPath, |
| 373 | QDBusConnection::systemBus()); |
| 374 | iface.StopDiscovery(); |
| 375 | } |
| 376 | |
| 377 | delete data->propteryListener; |
| 378 | delete data; |
| 379 | } |
| 380 | |
| 381 | //void QtBluezDiscoveryManager::dumpState() const |
| 382 | //{ |
| 383 | // qCDebug(QT_BT_BLUEZ) << "-------------------------"; |
| 384 | // if (d->references.isEmpty()) { |
| 385 | // qCDebug(QT_BT_BLUEZ) << "No running registration"; |
| 386 | // } else { |
| 387 | // const QList<QString> paths = d->references.keys(); |
| 388 | // for (const QString &path : paths) { |
| 389 | // qCDebug(QT_BT_BLUEZ) << path << "->" << d->references[path]->reference; |
| 390 | // } |
| 391 | // } |
| 392 | // qCDebug(QT_BT_BLUEZ) << "-------------------------"; |
| 393 | //} |
| 394 | |
| 395 | void QtBluezDiscoveryManager::InterfacesRemoved(const QDBusObjectPath &object_path, |
| 396 | const QStringList &interfaces) |
| 397 | { |
| 398 | if (!d->references.contains(key: object_path.path()) |
| 399 | || !interfaces.contains(QStringLiteral("org.bluez.Adapter1" ))) |
| 400 | return; |
| 401 | |
| 402 | removeAdapterFromMonitoring(dbusPath: object_path.path()); |
| 403 | } |
| 404 | |
| 405 | void QtBluezDiscoveryManager::PropertiesChanged(const QString &interface, |
| 406 | const QVariantMap &changed_properties, |
| 407 | const QStringList &invalidated_properties, |
| 408 | const QDBusMessage &) |
| 409 | { |
| 410 | Q_UNUSED(invalidated_properties); |
| 411 | |
| 412 | OrgFreedesktopDBusPropertiesInterface *propIface = |
| 413 | qobject_cast<OrgFreedesktopDBusPropertiesInterface *>(object: sender()); |
| 414 | |
| 415 | if (!propIface) |
| 416 | return; |
| 417 | |
| 418 | if (interface == QStringLiteral("org.bluez.Adapter1" ) |
| 419 | && d->references.contains(key: propIface->path()) |
| 420 | && changed_properties.contains(QStringLiteral("Discovering" ))) { |
| 421 | bool isDiscovering = changed_properties.value(QStringLiteral("Discovering" )).toBool(); |
| 422 | if (!isDiscovering) { |
| 423 | |
| 424 | /* |
| 425 | Once we stop the Discovering flag will switch a few ms later. This comes through this code |
| 426 | path. If a new device discovery is started while we are still |
| 427 | waiting for the flag change signal, then the new device discovery will be aborted prematurely. |
| 428 | To compensate we check whether there was renewed interest. |
| 429 | */ |
| 430 | |
| 431 | AdapterData *data = d->references[propIface->path()]; |
| 432 | if (!data) { |
| 433 | removeAdapterFromMonitoring(dbusPath: propIface->path()); |
| 434 | } else { |
| 435 | OrgBluezAdapter1Interface iface(QStringLiteral("org.bluez" ), propIface->path(), |
| 436 | QDBusConnection::systemBus()); |
| 437 | iface.StartDiscovery(); |
| 438 | } |
| 439 | } |
| 440 | } |
| 441 | } |
| 442 | |
| 443 | void QtBluezDiscoveryManager::removeAdapterFromMonitoring(const QString &dbusPath) |
| 444 | { |
| 445 | // remove adapter from monitoring |
| 446 | AdapterData *data = d->references.take(key: dbusPath); |
| 447 | delete data->propteryListener; |
| 448 | delete data; |
| 449 | |
| 450 | emit discoveryInterrupted(adapterPath: dbusPath); |
| 451 | } |
| 452 | |
| 453 | /* |
| 454 | Finds the path for the local adapter with \a wantedAddress or returns an |
| 455 | empty string if no local adapter with the given address can be found. |
| 456 | |
| 457 | If \a wantedAddress is \c null it returns the first powered-on adapter, or |
| 458 | the first adapter if all of them are powered off, or an empty string if |
| 459 | none is available. |
| 460 | |
| 461 | If \a ok is false the lookup was aborted due to a dbus error and this function |
| 462 | returns an empty string. |
| 463 | */ |
| 464 | QString findAdapterForAddress(const QBluetoothAddress &wantedAddress, bool *ok = nullptr) |
| 465 | { |
| 466 | OrgFreedesktopDBusObjectManagerInterface manager(QStringLiteral("org.bluez" ), |
| 467 | QStringLiteral("/" ), |
| 468 | QDBusConnection::systemBus()); |
| 469 | |
| 470 | QDBusPendingReply<ManagedObjectList> reply = manager.GetManagedObjects(); |
| 471 | reply.waitForFinished(); |
| 472 | if (reply.isError()) { |
| 473 | if (ok) |
| 474 | *ok = false; |
| 475 | |
| 476 | return QString(); |
| 477 | } |
| 478 | |
| 479 | struct AdapterInfo |
| 480 | { |
| 481 | QString path; |
| 482 | QBluetoothAddress address; |
| 483 | bool powered; |
| 484 | }; |
| 485 | QList<AdapterInfo> localAdapters; |
| 486 | |
| 487 | ManagedObjectList managedObjectList = reply.value(); |
| 488 | for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) { |
| 489 | const QDBusObjectPath &path = it.key(); |
| 490 | const InterfaceList &ifaceList = it.value(); |
| 491 | |
| 492 | for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) { |
| 493 | const QString &iface = jt.key(); |
| 494 | |
| 495 | if (iface == QStringLiteral("org.bluez.Adapter1" )) { |
| 496 | AdapterInfo info; |
| 497 | info.path = path.path(); |
| 498 | info.address = QBluetoothAddress(ifaceList.value(key: iface).value( |
| 499 | QStringLiteral("Address" )).toString()); |
| 500 | info.powered = ifaceList.value(key: iface).value(QStringLiteral("Powered" )).toBool(); |
| 501 | if (!info.address.isNull()) |
| 502 | localAdapters.append(t: info); |
| 503 | break; |
| 504 | } |
| 505 | } |
| 506 | } |
| 507 | |
| 508 | if (ok) |
| 509 | *ok = true; |
| 510 | |
| 511 | if (localAdapters.isEmpty()) |
| 512 | return QString(); // -> no local adapter found |
| 513 | |
| 514 | auto findFirstPowered = [&localAdapters]() { |
| 515 | Q_ASSERT(!localAdapters.isEmpty()); |
| 516 | auto it = std::find_if(first: localAdapters.cbegin(), last: localAdapters.cend(), |
| 517 | pred: [](const AdapterInfo &info) { |
| 518 | return info.powered == true; |
| 519 | }); |
| 520 | if (it != localAdapters.cend()) |
| 521 | return it->path; |
| 522 | // simply return first if none is powered |
| 523 | return localAdapters.cbegin()->path; |
| 524 | }; |
| 525 | |
| 526 | if (wantedAddress.isNull()) |
| 527 | return findFirstPowered(); |
| 528 | |
| 529 | for (const AdapterInfo &info : std::as_const(t&: localAdapters)) { |
| 530 | if (info.address == wantedAddress) |
| 531 | return info.path; // -> found local adapter with wanted address |
| 532 | } |
| 533 | |
| 534 | return QString(); // nothing matching found |
| 535 | } |
| 536 | |
| 537 | /* |
| 538 | |
| 539 | Checks the presence of peripheral interface and returns path to the adapter |
| 540 | |
| 541 | */ |
| 542 | |
| 543 | QString adapterWithDBusPeripheralInterface(const QBluetoothAddress &localAddress) |
| 544 | { |
| 545 | initializeBluez5(); |
| 546 | |
| 547 | // First find the object path to the desired adapter |
| 548 | bool ok = false; |
| 549 | const QString hostAdapterPath = findAdapterForAddress(wantedAddress: localAddress, ok: &ok); |
| 550 | if (!ok || hostAdapterPath.isEmpty()) |
| 551 | return {}; |
| 552 | |
| 553 | // Then check if that adapter provides peripheral dbus interface |
| 554 | OrgFreedesktopDBusObjectManagerInterface manager(QStringLiteral("org.bluez" ), |
| 555 | QStringLiteral("/" ), |
| 556 | QDBusConnection::systemBus()); |
| 557 | QDBusPendingReply<ManagedObjectList> reply = manager.GetManagedObjects(); |
| 558 | reply.waitForFinished(); |
| 559 | if (reply.isError()) |
| 560 | return {}; |
| 561 | |
| 562 | using namespace Qt::StringLiterals; |
| 563 | // For example /org/bluez/hci0 contains org.bluezLEAdvertisingManager1 |
| 564 | const bool peripheralSupported = reply.value() |
| 565 | .value(key: QDBusObjectPath(hostAdapterPath)) |
| 566 | .contains(key: "org.bluez.LEAdvertisingManager1"_L1 ); |
| 567 | |
| 568 | qCDebug(QT_BT_BLUEZ) << "Peripheral role" |
| 569 | << (peripheralSupported ? "" : "not" ) |
| 570 | << "supported on" << hostAdapterPath; |
| 571 | return peripheralSupported ? hostAdapterPath : QString{}; |
| 572 | } |
| 573 | |
| 574 | /* |
| 575 | Removes every character that cannot be used in QDbusObjectPath |
| 576 | |
| 577 | See QDbusUtil::isValidObjectPath(QString) for more details. |
| 578 | */ |
| 579 | QString sanitizeNameForDBus(const QString &text) |
| 580 | { |
| 581 | QString appName = text; |
| 582 | for (qsizetype i = 0; i < appName.size(); ++i) { |
| 583 | ushort us = appName[i].unicode(); |
| 584 | bool valid = (us >= 'a' && us <= 'z') |
| 585 | || (us >= 'A' && us <= 'Z') |
| 586 | || (us >= '0' && us <= '9') |
| 587 | || (us == '_'); |
| 588 | |
| 589 | if (!valid) |
| 590 | appName[i] = QLatin1Char('_'); |
| 591 | } |
| 592 | |
| 593 | return appName; |
| 594 | } |
| 595 | |
| 596 | QT_END_NAMESPACE |
| 597 | |
| 598 | #include "moc_bluez5_helper_p.cpp" |
| 599 | |