| 1 | // Copyright (C) 2022 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 |  | 
| 4 | #include "bluezperipheralapplication_p.h" | 
| 5 | #include "bluezperipheralobjects_p.h" | 
| 6 | #include "objectmanageradaptor_p.h" | 
| 7 | #include "gattmanager1_p.h" | 
| 8 |  | 
| 9 | QT_BEGIN_NAMESPACE | 
| 10 |  | 
| 11 | Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ) | 
| 12 |  | 
| 13 | using namespace Qt::StringLiterals; | 
| 14 |  | 
| 15 | static constexpr QLatin1String appObjectPathTemplate{"/qt/btle/application/%1%2/%3" }; | 
| 16 |  | 
| 17 | QtBluezPeripheralApplication::QtBluezPeripheralApplication(const QString& hostAdapterPath, | 
| 18 |                                                            QObject* parent) | 
| 19 |     : QObject(parent), | 
| 20 |       m_objectPath(QString(appObjectPathTemplate). | 
| 21 |                       arg(a: sanitizeNameForDBus(text: QCoreApplication::applicationName())). | 
| 22 |                       arg(a: QCoreApplication::applicationPid()). | 
| 23 |                       arg(a: QRandomGenerator::global()->generate())) | 
| 24 | { | 
| 25 |     m_objectManager = new OrgFreedesktopDBusObjectManagerAdaptor(this); | 
| 26 |     m_gattManager = new OrgBluezGattManager1Interface("org.bluez"_L1 , hostAdapterPath, | 
| 27 |                                                       QDBusConnection::systemBus(), this); | 
| 28 | } | 
| 29 |  | 
| 30 | QtBluezPeripheralApplication::~QtBluezPeripheralApplication() | 
| 31 | { | 
| 32 |     unregisterApplication(); | 
| 33 | } | 
| 34 |  | 
| 35 | void QtBluezPeripheralApplication::registerApplication() | 
| 36 | { | 
| 37 |     if (m_applicationRegistered) { | 
| 38 |         // Can happen eg. if advertisement is start-stop-started | 
| 39 |         qCDebug(QT_BT_BLUEZ) << "Bluez peripheral application already registered" ; | 
| 40 |         return; | 
| 41 |     } | 
| 42 |  | 
| 43 |     if (m_services.isEmpty()) { | 
| 44 |         // Registering the application to bluez without services would fail | 
| 45 |         qCDebug(QT_BT_BLUEZ) << "No services, omiting Bluez peripheral application registration" ; | 
| 46 |         return; | 
| 47 |     } | 
| 48 |  | 
| 49 |     qCDebug(QT_BT_BLUEZ) << "Registering bluez peripheral application:"  << m_objectPath; | 
| 50 |  | 
| 51 |     // Register this application object on DBus | 
| 52 |     if (!QDBusConnection::systemBus().registerObject(path: m_objectPath, object: m_objectManager, | 
| 53 |                                                      options: QDBusConnection::ExportAllContents)) { | 
| 54 |         qCWarning(QT_BT_BLUEZ) << "Peripheral application object registration failed" ; | 
| 55 |         emit errorOccurred(); | 
| 56 |         return; | 
| 57 |     } | 
| 58 |  | 
| 59 |     // Register the service objects on DBus | 
| 60 |     registerServices(); | 
| 61 |  | 
| 62 |     // Register the gatt application to Bluez. After successful registration Bluez | 
| 63 |     // is aware of this peripheral application and will inquiry which services this application | 
| 64 |     // provides, see GetManagedObjects() | 
| 65 |     auto reply = m_gattManager->RegisterApplication(application: QDBusObjectPath(m_objectPath), options: {}); | 
| 66 |     QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); | 
| 67 |     QObject::connect(sender: watcher, signal: &QDBusPendingCallWatcher::finished, context: this, | 
| 68 |                      slot: [this](QDBusPendingCallWatcher* watcher) { | 
| 69 |         QDBusPendingReply<> reply = *watcher; | 
| 70 |         if (reply.isError()) { | 
| 71 |             qCWarning(QT_BT_BLUEZ) << "Application registration failed"  << reply.error(); | 
| 72 |             QDBusConnection::systemBus().unregisterObject(path: m_objectPath); | 
| 73 |             emit errorOccurred(); | 
| 74 |         } else { | 
| 75 |             qCDebug(QT_BT_BLUEZ) << "Peripheral application registered as"  << m_objectPath; | 
| 76 |             m_applicationRegistered = true; | 
| 77 |             emit registered(); | 
| 78 |         } | 
| 79 |         watcher->deleteLater(); | 
| 80 |     }); | 
| 81 | } | 
| 82 |  | 
| 83 | void QtBluezPeripheralApplication::unregisterApplication() | 
| 84 | { | 
| 85 |     if (!m_applicationRegistered) | 
| 86 |         return; | 
| 87 |     m_applicationRegistered = false; | 
| 88 |     auto reply = m_gattManager->UnregisterApplication(application: QDBusObjectPath(m_objectPath)); | 
| 89 |     reply.waitForFinished(); | 
| 90 |     if (reply.isError()) | 
| 91 |         qCWarning(QT_BT_BLUEZ) << "Error in unregistering peripheral application" ; | 
| 92 |     else | 
| 93 |         qCDebug(QT_BT_BLUEZ) << "Peripheral application unregistered successfully" ; | 
| 94 |     QDBusConnection::systemBus().unregisterObject(path: m_objectPath); | 
| 95 |     unregisterServices(); | 
| 96 |  | 
| 97 |     qCDebug(QT_BT_BLUEZ) << "Unregistered Bluez peripheral application on DBus:"  << m_objectPath; | 
| 98 | } | 
| 99 |  | 
| 100 | void QtBluezPeripheralApplication::registerServices() | 
| 101 | { | 
| 102 |     // Register the service objects on DBus | 
| 103 |     for (const auto service: std::as_const(t&: m_services)) | 
| 104 |         service->registerObject(); | 
| 105 |     for (const auto& characteristic : std::as_const(t&: m_characteristics)) | 
| 106 |         characteristic->registerObject(); | 
| 107 |     for (const auto& descriptor : std::as_const(t&: m_descriptors)) | 
| 108 |         descriptor->registerObject(); | 
| 109 | } | 
| 110 |  | 
| 111 | void QtBluezPeripheralApplication::unregisterServices() | 
| 112 | { | 
| 113 |     // Unregister the service objects from DBus | 
| 114 |     for (const auto service: std::as_const(t&: m_services)) | 
| 115 |         service->unregisterObject(); | 
| 116 |     for (const auto& characteristic : std::as_const(t&: m_characteristics)) | 
| 117 |         characteristic->unregisterObject(); | 
| 118 |     for (const auto& descriptor : std::as_const(t&: m_descriptors)) | 
| 119 |         descriptor->unregisterObject(); | 
| 120 | } | 
| 121 |  | 
| 122 | void QtBluezPeripheralApplication::reset() | 
| 123 | { | 
| 124 |     unregisterApplication(); | 
| 125 |  | 
| 126 |     qDeleteAll(c: m_services); | 
| 127 |     m_services.clear(); | 
| 128 |     qDeleteAll(c: m_descriptors); | 
| 129 |     m_descriptors.clear(); | 
| 130 |     qDeleteAll(c: m_characteristics); | 
| 131 |     m_characteristics.clear(); | 
| 132 | } | 
| 133 |  | 
| 134 | void QtBluezPeripheralApplication::addService(const QLowEnergyServiceData &serviceData, | 
| 135 |                       QSharedPointer<QLowEnergyServicePrivate> servicePrivate, | 
| 136 |                       QLowEnergyHandle serviceHandle) | 
| 137 | { | 
| 138 |     if (m_applicationRegistered) { | 
| 139 |         qCWarning(QT_BT_BLUEZ) << "Adding services to a registered application is not supported "  | 
| 140 |                                   "on Bluez DBus. Add services only before first advertisement or "  | 
| 141 |                                   "after disconnection" ; | 
| 142 |         return; | 
| 143 |     } | 
| 144 |  | 
| 145 |     // The ordinal numbers in the below object creation are used to create paths such as: | 
| 146 |     // ../service0/char0/desc0 | 
| 147 |     // ../service0/char1/desc0 | 
| 148 |     // ../service1/char0/desc0 | 
| 149 |     // ../service1/char0/desc1 | 
| 150 |     // For the Service object itself the ordinal number is the size of the service container | 
| 151 |     QtBluezPeripheralService* service = new QtBluezPeripheralService( | 
| 152 |                 serviceData, m_objectPath, m_services.size(), serviceHandle, this); | 
| 153 |     m_services.insert(key: serviceHandle, value: service); | 
| 154 |  | 
| 155 |     // Add included services | 
| 156 |     for (const auto includedService : serviceData.includedServices()) { | 
| 157 |         // As per Qt documentation the included service must have been added earlier | 
| 158 |         for (const auto s : std::as_const(t&: m_services)) { | 
| 159 |             if (QBluetoothUuid(s->uuid) == includedService->serviceUuid()) { | 
| 160 |                 service->addIncludedService(objectPath: s->objectPath); | 
| 161 |             } | 
| 162 |         } | 
| 163 |     } | 
| 164 |  | 
| 165 |     // Set characteristics and their descriptors | 
| 166 |     quint16 characteristicOrdinal{0}; | 
| 167 |     for (const auto& characteristicData : serviceData.characteristics()) { | 
| 168 |         auto characteristicHandle = handleForCharacteristic( | 
| 169 |                     uuid: characteristicData.uuid(), service: servicePrivate); | 
| 170 |         QtBluezPeripheralCharacteristic* characteristic = | 
| 171 |                 new QtBluezPeripheralCharacteristic(characteristicData, | 
| 172 |                     service->objectPath, characteristicOrdinal++, | 
| 173 |                     characteristicHandle, this); | 
| 174 |         m_characteristics.insert(key: characteristicHandle, value: characteristic); | 
| 175 |         QObject::connect(sender: characteristic, signal: &QtBluezPeripheralCharacteristic::valueUpdatedByRemote, | 
| 176 |                          context: this, slot: &QtBluezPeripheralApplication::characteristicValueUpdatedByRemote); | 
| 177 |         QObject::connect(sender: characteristic, signal: &QtBluezPeripheralCharacteristic::remoteDeviceAccessEvent, | 
| 178 |                          context: this, slot: &QtBluezPeripheralApplication::remoteDeviceAccessEvent); | 
| 179 |  | 
| 180 |         quint16 descriptorOrdinal{0}; | 
| 181 |         for (const auto& descriptorData : characteristicData.descriptors()) { | 
| 182 |             // With bluez we don't use the CCCD user has provided, because Bluez | 
| 183 |             // generates it if 'notify/indicate' flag is set. Similarly the extended properties | 
| 184 |             // descriptor is generated by Bluez if the related flags are set. Using the application | 
| 185 |             // provided descriptors would result in duplicate descriptors. | 
| 186 |             if (descriptorData.uuid() | 
| 187 |                     == QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration | 
| 188 |                     || descriptorData.uuid() | 
| 189 |                     == QBluetoothUuid::DescriptorType::CharacteristicExtendedProperties) { | 
| 190 |                 continue; | 
| 191 |             } | 
| 192 |             auto descriptorHandle = handleForDescriptor(uuid: descriptorData.uuid(), | 
| 193 |                                                         service: servicePrivate, | 
| 194 |                                                         characteristicHandle); | 
| 195 |             QtBluezPeripheralDescriptor* descriptor = | 
| 196 |                     new QtBluezPeripheralDescriptor(descriptorData, | 
| 197 |                                                  characteristic->objectPath, descriptorOrdinal++, | 
| 198 |                                                  descriptorHandle, characteristicHandle, this); | 
| 199 |             QObject::connect(sender: descriptor, signal: &QtBluezPeripheralDescriptor::valueUpdatedByRemote, | 
| 200 |                              context: this, slot: &QtBluezPeripheralApplication::descriptorValueUpdatedByRemote); | 
| 201 |             QObject::connect(sender: descriptor, signal: &QtBluezPeripheralCharacteristic::remoteDeviceAccessEvent, | 
| 202 |                              context: this, slot: &QtBluezPeripheralApplication::remoteDeviceAccessEvent); | 
| 203 |             m_descriptors.insert(key: descriptorHandle, value: descriptor); | 
| 204 |         } | 
| 205 |     } | 
| 206 | } | 
| 207 |  | 
| 208 | // This function is called when characteristic is written to from Qt API | 
| 209 | bool QtBluezPeripheralApplication::localCharacteristicWrite(QLowEnergyHandle handle, | 
| 210 |                                                             const QByteArray& value) | 
| 211 | { | 
| 212 |     auto characteristic = m_characteristics.value(key: handle); | 
| 213 |     if (!characteristic) { | 
| 214 |         qCWarning(QT_BT_BLUEZ) << "DBus characteristic not found for write" ; | 
| 215 |         return false; | 
| 216 |     } | 
| 217 |     return characteristic->localValueUpdate(value); | 
| 218 | } | 
| 219 |  | 
| 220 | // This function is called when characteristic is written to from Qt API | 
| 221 | bool QtBluezPeripheralApplication::localDescriptorWrite(QLowEnergyHandle handle, | 
| 222 |                                                         const QByteArray& value) | 
| 223 | { | 
| 224 |     auto descriptor = m_descriptors.value(key: handle); | 
| 225 |     if (!descriptor) { | 
| 226 |         qCWarning(QT_BT_BLUEZ) << "DBus descriptor not found for write" ; | 
| 227 |         return false; | 
| 228 |     } | 
| 229 |     return descriptor->localValueUpdate(value); | 
| 230 | } | 
| 231 |  | 
| 232 | bool QtBluezPeripheralApplication::registrationNeeded() | 
| 233 | { | 
| 234 |     return !m_applicationRegistered && !m_services.isEmpty(); | 
| 235 | } | 
| 236 |  | 
| 237 | // org.freedesktop.DBus.ObjectManager | 
| 238 | // This is called by Bluez when we register the application | 
| 239 | ManagedObjectList QtBluezPeripheralApplication::GetManagedObjects() | 
| 240 | { | 
| 241 |     ManagedObjectList managedObjects; | 
| 242 |     for (const auto service: std::as_const(t&: m_services)) | 
| 243 |         managedObjects.insert(key: QDBusObjectPath(service->objectPath), value: service->properties()); | 
| 244 |     for (const auto& charac : std::as_const(t&: m_characteristics)) | 
| 245 |         managedObjects.insert(key: QDBusObjectPath(charac->objectPath), value: charac->properties()); | 
| 246 |     for (const auto& descriptor : std::as_const(t&: m_descriptors)) | 
| 247 |         managedObjects.insert(key: QDBusObjectPath(descriptor->objectPath), value: descriptor->properties()); | 
| 248 |  | 
| 249 |     return managedObjects; | 
| 250 | } | 
| 251 |  | 
| 252 | // Returns the Qt-internal handle for the characteristic | 
| 253 | QLowEnergyHandle QtBluezPeripheralApplication::handleForCharacteristic(QBluetoothUuid uuid, | 
| 254 |                                          QSharedPointer<QLowEnergyServicePrivate> service) | 
| 255 | { | 
| 256 |     const auto handles = service->characteristicList.keys(); | 
| 257 |     for (const auto handle : handles) { | 
| 258 |         if (uuid == service->characteristicList[handle].uuid) | 
| 259 |             return handle; | 
| 260 |     } | 
| 261 |     return 0; | 
| 262 | } | 
| 263 |  | 
| 264 | // Returns the Qt-internal handle for the descriptor | 
| 265 | QLowEnergyHandle QtBluezPeripheralApplication::handleForDescriptor(QBluetoothUuid uuid, | 
| 266 |                                      QSharedPointer<QLowEnergyServicePrivate> service, | 
| 267 |                                      QLowEnergyHandle characteristicHandle) | 
| 268 | { | 
| 269 |     const auto characteristicData = service->characteristicList[characteristicHandle]; | 
| 270 |     const auto handles = characteristicData.descriptorList.keys(); | 
| 271 |     for (const auto handle : handles) { | 
| 272 |         if (uuid == characteristicData.descriptorList[handle].uuid) | 
| 273 |             return handle; | 
| 274 |     } | 
| 275 |     return 0; | 
| 276 | } | 
| 277 |  | 
| 278 | QT_END_NAMESPACE | 
| 279 |  | 
| 280 | #include "moc_bluezperipheralapplication_p.cpp" | 
| 281 |  |