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 "bluezperipheralobjects_p.h"
5
6#include "propertiesadaptor_p.h"
7#include "gattservice1adaptor_p.h"
8#include "gattcharacteristic1adaptor_p.h"
9#include "gattdescriptor1adaptor_p.h"
10
11#include <QtCore/QLoggingCategory>
12#include <QtDBus/QDBusConnection>
13
14QT_BEGIN_NAMESPACE
15
16Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
17
18using namespace Qt::StringLiterals;
19
20static constexpr auto characteristicPathTemplate{"%1/char%2"_L1};
21static constexpr auto descriptorPathTemplate{"%1/desc%2"_L1};
22static constexpr auto servicePathTemplate{"%1/service%2"_L1};
23
24// The interface names and error values are from BlueZ "gatt-api" documentation
25static constexpr auto bluezServiceInterface{"org.bluez.GattService1"_L1};
26static constexpr auto bluezCharacteristicInterface{"org.bluez.GattCharacteristic1"_L1};
27static constexpr auto bluezDescriptorInterface{"org.bluez.GattDescriptor1"_L1};
28
29static constexpr auto bluezErrorInvalidValueLength{"org.bluez.Error.InvalidValueLength"_L1};
30static constexpr auto bluezErrorInvalidOffset{"org.bluez.Error.InvalidOffset"_L1};
31static constexpr auto bluezErrorNotAuthorized{"org.bluez.Error.NotAuthorized"_L1};
32// Bluetooth Core v5.3, 3.2.9, Vol 3, Part F
33static constexpr int maximumAttributeLength{512};
34
35
36QtBluezPeripheralGattObject::QtBluezPeripheralGattObject(const QString& objectPath,
37 const QString& uuid, QLowEnergyHandle handle, QObject* parent)
38 : QObject(parent), objectPath(objectPath), uuid(uuid), handle(handle),
39 propertiesAdaptor(new OrgFreedesktopDBusPropertiesAdaptor(this))
40{}
41
42QtBluezPeripheralGattObject::~QtBluezPeripheralGattObject()
43{
44 unregisterObject();
45}
46
47bool QtBluezPeripheralGattObject::registerObject()
48{
49 if (m_registered)
50 return true;
51
52 if (QDBusConnection::systemBus().registerObject(path: objectPath, object: this)) {
53 qCDebug(QT_BT_BLUEZ) << "Registered object on DBus:" << objectPath << uuid;
54 m_registered = true;
55 return true;
56 } else {
57 qCWarning(QT_BT_BLUEZ) << "Failed to register object on DBus:" << objectPath << uuid;
58 return false;
59 }
60}
61
62void QtBluezPeripheralGattObject::unregisterObject()
63{
64 if (!m_registered)
65 return;
66 QDBusConnection::systemBus().unregisterObject(path: objectPath);
67 qCDebug(QT_BT_BLUEZ) << "Unregistered object on DBus:" << objectPath << uuid;
68 m_registered = false;
69}
70
71void QtBluezPeripheralGattObject::accessEvent(const QVariantMap& options)
72{
73 // Report this event for connection management purposes
74 const auto remoteDevice = options.value(key: "device"_L1).value<QDBusObjectPath>().path();
75 if (!remoteDevice.isEmpty())
76 emit remoteDeviceAccessEvent(remoteDeviceObjectPath: remoteDevice, mtu: options.value(key: "mtu"_L1).toUInt());
77}
78
79QtBluezPeripheralDescriptor::QtBluezPeripheralDescriptor(
80 const QLowEnergyDescriptorData& descriptorData,
81 const QString& characteristicPath, quint16 ordinal,
82 QLowEnergyHandle handle, QLowEnergyHandle characteristicHandle,
83 QObject* parent)
84 : QtBluezPeripheralGattObject(descriptorPathTemplate.arg(args: characteristicPath).arg(a: ordinal),
85 descriptorData.uuid().toString(mode: QUuid::WithoutBraces), handle, parent),
86 m_adaptor(new OrgBluezGattDescriptor1Adaptor(this)),
87 m_characteristicPath(characteristicPath),
88 m_characteristicHandle(characteristicHandle)
89{
90 if (descriptorData.value().size() > maximumAttributeLength) {
91 qCWarning(QT_BT_BLUEZ) << "Descriptor value is too large, cropping it to"
92 << maximumAttributeLength;
93 m_value = descriptorData.value().sliced(pos: 0, n: maximumAttributeLength);
94 } else {
95 m_value = descriptorData.value();
96 }
97 initializeFlags(data: descriptorData);
98}
99
100InterfaceList QtBluezPeripheralDescriptor::properties() const
101{
102 InterfaceList properties;
103 properties.insert(key: bluezDescriptorInterface,
104 value: {
105 {"UUID"_L1, uuid},
106 {"Characteristic"_L1, QDBusObjectPath(m_characteristicPath)},
107 {"Flags"_L1, m_flags}
108 });
109 return properties;
110}
111
112// org.bluez.GattDescriptor1
113// This function is invoked when remote device reads the value
114QByteArray QtBluezPeripheralDescriptor::ReadValue(const QVariantMap &options, QString& error)
115{
116 accessEvent(options);
117 // Offset is set by Bluez when the value size is more than MTU size.
118 // Bluez deduces the value size from the first ReadValue. If the
119 // received data size is larger than MTU, Bluez will take the first MTU bytes and
120 // issue more ReadValue calls with the 'offset' set
121 const quint16 offset = options.value(key: "offset"_L1).toUInt();
122 const quint16 mtu = options.value(key: "mtu"_L1).toUInt();
123
124 if (offset > m_value.length() - 1) {
125 qCWarning(QT_BT_BLUEZ) << "Invalid offset" << offset << ", value len:" << m_value.length();
126 error = bluezErrorInvalidOffset;
127 return {};
128 }
129
130 if (offset > 0)
131 return m_value.mid(index: offset, len: mtu);
132 else
133 return m_value;
134}
135
136// org.bluez.GattDescriptor1
137// This function is invoked when remote device writes a value
138QString QtBluezPeripheralDescriptor::WriteValue(const QByteArray &value,
139 const QVariantMap &options)
140{
141 accessEvent(options);
142
143 if (options.value(key: "prepare-authorize"_L1).toBool()) {
144 // Qt API doesn't provide the means for application to authorize
145 qCWarning(QT_BT_BLUEZ) << "Descriptor write requires authorization."
146 << "The client device needs to be trusted beforehand";
147 return bluezErrorNotAuthorized;
148 }
149
150 if (value.size() > maximumAttributeLength) {
151 qCWarning(QT_BT_BLUEZ) << "Descriptor value is too large:" << value.size();
152 return bluezErrorInvalidValueLength;
153 }
154 m_value = value;
155 emit valueUpdatedByRemote(characteristicHandle: m_characteristicHandle, descriptorHandle: handle, value);
156 return {};
157}
158
159// This function is called when the value has been updated locally (server-side)
160bool QtBluezPeripheralDescriptor::localValueUpdate(const QByteArray& value)
161{
162 if (value.size() > maximumAttributeLength) {
163 qCWarning(QT_BT_BLUEZ) << "Descriptor value is too large:" << value.size();
164 return false;
165 }
166 m_value = value;
167 return true;
168}
169
170void QtBluezPeripheralDescriptor::initializeFlags(const QLowEnergyDescriptorData& data)
171{
172 // Flag tokens are from org.bluez.GattDescriptor1 documentation
173 if (data.isReadable())
174 m_flags.append(t: "read"_L1);
175 if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired)
176 m_flags.append(t: "encrypt-read"_L1);
177 if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttAuthenticationRequired)
178 m_flags.append(t: "encrypt-authenticated-read"_L1);
179
180 if (data.isWritable())
181 m_flags.append(t: "write"_L1);
182 if (data.writeConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired)
183 m_flags.append(t: "encrypt-write"_L1);
184 if (data.writeConstraints() & QBluetooth::AttAccessConstraint::AttAuthenticationRequired)
185 m_flags.append(t: "encrypt-authenticated-write"_L1);
186
187 if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttAuthorizationRequired
188 || data.writeConstraints() & QBluetooth::AttAccessConstraint::AttAuthorizationRequired)
189 m_flags.append(t: "authorize"_L1);
190
191 if (m_flags.isEmpty()) {
192 qCWarning(QT_BT_BLUEZ) << "Descriptor property flags not set" << uuid
193 << "Peripheral may fail to register";
194 }
195}
196
197QtBluezPeripheralCharacteristic::QtBluezPeripheralCharacteristic(
198 const QLowEnergyCharacteristicData& characteristicData,
199 const QString& servicePath, quint16 ordinal,
200 QLowEnergyHandle handle, QObject* parent)
201 : QtBluezPeripheralGattObject(characteristicPathTemplate.arg(args: servicePath).arg(a: ordinal),
202 characteristicData.uuid().toString(mode: QUuid::WithoutBraces), handle, parent),
203 m_adaptor(new OrgBluezGattCharacteristic1Adaptor(this)),
204 m_servicePath(servicePath),
205 m_minimumValueLength(std::min(a: characteristicData.minimumValueLength(),
206 b: maximumAttributeLength)),
207 m_maximumValueLength(std::min(a: characteristicData.maximumValueLength(),
208 b: maximumAttributeLength))
209{
210 initializeFlags(data: characteristicData);
211 initializeValue(value: characteristicData.value());
212}
213
214InterfaceList QtBluezPeripheralCharacteristic::properties() const
215{
216 InterfaceList properties;
217 properties.insert(key: bluezCharacteristicInterface,
218 value: {
219 {"UUID"_L1, uuid},
220 {"Service"_L1, QDBusObjectPath(m_servicePath)},
221 {"Flags"_L1, m_flags}
222 });
223 return properties;
224}
225
226// org.bluez.GattCharacteristic1
227// This function is invoked when remote device reads the value
228QByteArray QtBluezPeripheralCharacteristic::ReadValue(const QVariantMap &options, QString& error)
229{
230 accessEvent(options);
231 // Offset is set by Bluez when the value size is more than MTU size.
232 // Bluez deduces the value size from the first ReadValue. If the
233 // received data size is larger than MTU, Bluez will take the first MTU bytes and
234 // issue more ReadValue calls with the 'offset' set
235 const quint16 offset = options.value(key: "offset"_L1).toUInt();
236 const quint16 mtu = options.value(key: "mtu"_L1).toUInt();
237
238 if (offset > m_value.length() - 1) {
239 qCWarning(QT_BT_BLUEZ) << "Invalid offset" << offset << ", value len:" << m_value.length();
240 error = bluezErrorInvalidOffset;
241 return {};
242 }
243
244 if (offset > 0)
245 return m_value.mid(index: offset, len: mtu);
246 else
247 return m_value;
248}
249
250// org.bluez.GattCharacteristic1
251// This function is invoked when remote device writes a value
252QString QtBluezPeripheralCharacteristic::WriteValue(const QByteArray &value,
253 const QVariantMap &options)
254{
255 accessEvent(options);
256
257 if (options.value(key: "prepare-authorize"_L1).toBool()) {
258 // Qt API doesn't provide the means for application to authorize
259 qCWarning(QT_BT_BLUEZ) << "Characteristic write requires authorization."
260 << "The client device needs to be trusted beforehand";
261 return bluezErrorNotAuthorized;
262 }
263
264 if (value.size() < m_minimumValueLength || value.size() > m_maximumValueLength) {
265 qCWarning(QT_BT_BLUEZ) << "Characteristic value has invalid length" << value.size()
266 << "min:" << m_minimumValueLength
267 << "max:" << m_maximumValueLength;
268 return bluezErrorInvalidValueLength;
269 }
270 m_value = value;
271 emit valueUpdatedByRemote(handle, value);
272 return {};
273}
274
275// This function is called when the value has been updated locally (server-side)
276bool QtBluezPeripheralCharacteristic::localValueUpdate(const QByteArray& value)
277{
278 if (value.size() < m_minimumValueLength || value.size() > m_maximumValueLength) {
279 qCWarning(QT_BT_BLUEZ) << "Characteristic value has invalid length" << value.size()
280 << "min:" << m_minimumValueLength
281 << "max:" << m_maximumValueLength;
282 return false;
283 }
284 m_value = value;
285 if (m_notifying) {
286 emit propertiesAdaptor->PropertiesChanged(
287 interface: bluezCharacteristicInterface, changed_properties: {{"Value"_L1, m_value}}, invalidated_properties: {});
288 }
289 return true;
290}
291
292// org.bluez.GattCharacteristic1
293// These are called when remote client enables or disables NTF/IND
294void QtBluezPeripheralCharacteristic::StartNotify()
295{
296 qCDebug(QT_BT_BLUEZ) << "NTF or IND enabled for characteristic" << uuid;
297 m_notifying = true;
298}
299
300void QtBluezPeripheralCharacteristic::StopNotify()
301{
302 qCDebug(QT_BT_BLUEZ) << "NTF or IND disabled for characteristic" << uuid;
303 m_notifying = false;
304}
305
306
307void QtBluezPeripheralCharacteristic::initializeValue(const QByteArray& value)
308{
309 const auto valueSize = value.size();
310 if (valueSize < m_minimumValueLength || valueSize > m_maximumValueLength) {
311 qCWarning(QT_BT_BLUEZ) << "Characteristic value has invalid length" << valueSize
312 << "min:" << m_minimumValueLength
313 << "max:" << m_maximumValueLength;
314 m_value = QByteArray(m_minimumValueLength, 0);
315 } else {
316 m_value = value;
317 }
318}
319
320void QtBluezPeripheralCharacteristic::initializeFlags(const QLowEnergyCharacteristicData& data)
321{
322 // Flag tokens are from org.bluez.GattCharacteristic1 documentation
323 if (data.properties() & QLowEnergyCharacteristic::PropertyType::Broadcasting)
324 m_flags.append(t: "broadcast"_L1);
325 if (data.properties() & QLowEnergyCharacteristic::PropertyType::WriteNoResponse)
326 m_flags.append(t: "write-without-response"_L1);
327 if (data.properties() & QLowEnergyCharacteristic::PropertyType::Read)
328 m_flags.append(t: "read"_L1);
329 if (data.properties() & QLowEnergyCharacteristic::PropertyType::Write)
330 m_flags.append(t: "write"_L1);
331 if (data.properties() & QLowEnergyCharacteristic::PropertyType::Notify)
332 m_flags.append(t: "notify"_L1);
333 if (data.properties() & QLowEnergyCharacteristic::PropertyType::Indicate)
334 m_flags.append(t: "indicate"_L1);
335 if (data.properties() & QLowEnergyCharacteristic::PropertyType::WriteSigned)
336 m_flags.append(t: "authenticated-signed-writes"_L1);
337 if (data.properties() & QLowEnergyCharacteristic::PropertyType::ExtendedProperty) {
338 // If extended properties property is set, check if we have the descriptor
339 // describing them. Bluez will generate the actual descriptor based on these
340 // flags. For clarity: the 'extended-properties' token mentioned in the Bluez
341 // API is implied by these flags.
342 for (const auto& descriptor : data.descriptors()) {
343 // Core Bluetooth v5.3 Vol 3, Part G, 3.3.3.1
344 if (descriptor.uuid()
345 == QBluetoothUuid::DescriptorType::CharacteristicExtendedProperties
346 && descriptor.value().size() == 2) {
347 const auto properties = descriptor.value().at(i: 0);
348 if (properties & 0x01)
349 m_flags.append(t: "reliable-write"_L1);
350 if (properties & 0x02)
351 m_flags.append(t: "writable-auxiliaries"_L1);
352 }
353 }
354 }
355
356 if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired)
357 m_flags.append(t: "encrypt-read"_L1);
358 if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttAuthenticationRequired)
359 m_flags.append(t: "encrypt-authenticated-read"_L1);
360 if (data.writeConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired)
361 m_flags.append(t: "encrypt-write"_L1);
362 if (data.writeConstraints() & QBluetooth::AttAccessConstraint::AttAuthenticationRequired)
363 m_flags.append(t: "encrypt-authenticated-write"_L1);
364
365 if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttAuthorizationRequired
366 || data.writeConstraints() & QBluetooth::AttAccessConstraint::AttAuthorizationRequired)
367 m_flags.append(t: "authorize"_L1);
368
369 if (m_flags.isEmpty()) {
370 qCWarning(QT_BT_BLUEZ) << "Characteristic property flags not set" << uuid
371 << "Peripheral may fail to register";
372 }
373}
374
375
376QtBluezPeripheralService::QtBluezPeripheralService(const QLowEnergyServiceData &serviceData,
377 const QString& applicationPath, quint16 ordinal,
378 QLowEnergyHandle handle, QObject* parent)
379 : QtBluezPeripheralGattObject(servicePathTemplate.arg(args: applicationPath).arg(a: ordinal),
380 serviceData.uuid().toString(mode: QUuid::WithoutBraces), handle, parent),
381 m_isPrimary(serviceData.type() == QLowEnergyServiceData::ServiceTypePrimary),
382 m_adaptor(new OrgBluezGattService1Adaptor(this))
383{
384}
385
386void QtBluezPeripheralService::addIncludedService(const QString& objectPath) {
387 qCDebug(QT_BT_BLUEZ) << "Adding included service" << objectPath << "for" << uuid;
388 m_includedServices.append(t: QDBusObjectPath(objectPath));
389}
390
391InterfaceList QtBluezPeripheralService::properties() const {
392 InterfaceList interfaces;
393 interfaces.insert(key: bluezServiceInterface,value: {
394 {"UUID"_L1, uuid},
395 {"Primary"_L1, m_isPrimary},
396 {"Includes"_L1, QVariant::fromValue(value: m_includedServices)}
397 });
398 return interfaces;
399};
400
401QT_END_NAMESPACE
402
403#include "moc_bluezperipheralobjects_p.cpp"
404

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