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
9QT_BEGIN_NAMESPACE
10
11Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
12
13using namespace Qt::StringLiterals;
14
15static constexpr QLatin1String appObjectPathTemplate{"/qt/btle/application/%1%2/%3"};
16
17QtBluezPeripheralApplication::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
30QtBluezPeripheralApplication::~QtBluezPeripheralApplication()
31{
32 unregisterApplication();
33}
34
35void 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
83void 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
100void 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
111void 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
122void 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
134void 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
209bool 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
221bool 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
232bool 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
239ManagedObjectList 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
253QLowEnergyHandle 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
265QLowEnergyHandle 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
278QT_END_NAMESPACE
279
280#include "moc_bluezperipheralapplication_p.cpp"
281

source code of qtconnectivity/src/bluetooth/bluez/bluezperipheralapplication.cpp