Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
bluezperipheralobjects.cpp
Go to the documentation of this file.
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
5
10
11#include <QtCore/QLoggingCategory>
12#include <QtDBus/QDBusConnection>
13
15
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
37 const QString& uuid, QLowEnergyHandle handle, QObject* parent)
38 : QObject(parent), objectPath(objectPath), uuid(uuid), handle(handle),
39 propertiesAdaptor(new OrgFreedesktopDBusPropertiesAdaptor(this))
40{}
41
46
48{
49 if (m_registered)
50 return true;
51
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
63{
64 if (!m_registered)
65 return;
66 QDBusConnection::systemBus().unregisterObject(objectPath);
67 qCDebug(QT_BT_BLUEZ) << "Unregistered object on DBus:" << objectPath << uuid;
68 m_registered = false;
69}
70
72{
73 // Report this event for connection management purposes
74 const auto remoteDevice = options.value("device"_L1).value<QDBusObjectPath>().path();
75 if (!remoteDevice.isEmpty())
76 emit remoteDeviceAccessEvent(remoteDevice, options.value("mtu"_L1).toUInt());
77}
78
80 const QLowEnergyDescriptorData& descriptorData,
81 const QString& characteristicPath, quint16 ordinal,
82 QLowEnergyHandle handle, QLowEnergyHandle characteristicHandle,
83 QObject* parent)
84 : QtBluezPeripheralGattObject(descriptorPathTemplate.arg(characteristicPath).arg(ordinal),
85 descriptorData.uuid().toString(QUuid::WithoutBraces), handle, parent),
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"
93 m_value = descriptorData.value().sliced(0, maximumAttributeLength);
94 } else {
95 m_value = descriptorData.value();
96 }
97 initializeFlags(descriptorData);
98}
99
101{
104 {
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
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("offset"_L1).toUInt();
122 const quint16 mtu = options.value("mtu"_L1).toUInt();
123
124 if (offset > m_value.length() - 1) {
125 qCWarning(QT_BT_BLUEZ) << "Invalid offset" << offset << ", value len:" << m_value.length();
127 return {};
128 }
129
130 if (offset > 0)
131 return m_value.mid(offset, mtu);
132 else
133 return m_value;
134}
135
136// org.bluez.GattDescriptor1
137// This function is invoked when remote device writes a value
139 const QVariantMap &options)
140{
141 accessEvent(options);
142
143 if (options.value("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";
148 }
149
150 if (value.size() > maximumAttributeLength) {
151 qCWarning(QT_BT_BLUEZ) << "Descriptor value is too large:" << value.size();
153 }
154 m_value = value;
155 emit valueUpdatedByRemote(m_characteristicHandle, handle, value);
156 return {};
157}
158
159// This function is called when the value has been updated locally (server-side)
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("read"_L1);
176 m_flags.append("encrypt-read"_L1);
178 m_flags.append("encrypt-authenticated-read"_L1);
179
180 if (data.isWritable())
181 m_flags.append("write"_L1);
183 m_flags.append("encrypt-write"_L1);
185 m_flags.append("encrypt-authenticated-write"_L1);
186
189 m_flags.append("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
198 const QLowEnergyCharacteristicData& characteristicData,
199 const QString& servicePath, quint16 ordinal,
202 characteristicData.uuid().toString(QUuid::WithoutBraces), handle, parent),
204 m_servicePath(servicePath),
205 m_minimumValueLength(std::min(characteristicData.minimumValueLength(),
207 m_maximumValueLength(std::min(characteristicData.maximumValueLength(),
209{
210 initializeFlags(characteristicData);
211 initializeValue(characteristicData.value());
212}
213
215{
218 {
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
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("offset"_L1).toUInt();
236 const quint16 mtu = options.value("mtu"_L1).toUInt();
237
238 if (offset > m_value.length() - 1) {
239 qCWarning(QT_BT_BLUEZ) << "Invalid offset" << offset << ", value len:" << m_value.length();
241 return {};
242 }
243
244 if (offset > 0)
245 return m_value.mid(offset, mtu);
246 else
247 return m_value;
248}
249
250// org.bluez.GattCharacteristic1
251// This function is invoked when remote device writes a value
253 const QVariantMap &options)
254{
255 accessEvent(options);
256
257 if (options.value("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";
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;
269 }
270 m_value = value;
272 return {};
273}
274
275// This function is called when the value has been updated locally (server-side)
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) {
287 bluezCharacteristicInterface, {{"Value"_L1, m_value}}, {});
288 }
289 return true;
290}
291
292// org.bluez.GattCharacteristic1
293// These are called when remote client enables or disables NTF/IND
295{
296 qCDebug(QT_BT_BLUEZ) << "NTF or IND enabled for characteristic" << uuid;
297 m_notifying = true;
298}
299
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
324 m_flags.append("broadcast"_L1);
326 m_flags.append("write-without-response"_L1);
328 m_flags.append("read"_L1);
330 m_flags.append("write"_L1);
332 m_flags.append("notify"_L1);
334 m_flags.append("indicate"_L1);
336 m_flags.append("authenticated-signed-writes"_L1);
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()
346 && descriptor.value().size() == 2) {
347 const auto properties = descriptor.value().at(0);
348 if (properties & 0x01)
349 m_flags.append("reliable-write"_L1);
350 if (properties & 0x02)
351 m_flags.append("writable-auxiliaries"_L1);
352 }
353 }
354 }
355
357 m_flags.append("encrypt-read"_L1);
359 m_flags.append("encrypt-authenticated-read"_L1);
361 m_flags.append("encrypt-write"_L1);
363 m_flags.append("encrypt-authenticated-write"_L1);
364
367 m_flags.append("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
377 const QString& applicationPath, quint16 ordinal,
379 : QtBluezPeripheralGattObject(servicePathTemplate.arg(applicationPath).arg(ordinal),
380 serviceData.uuid().toString(QUuid::WithoutBraces), handle, parent),
381 m_isPrimary(serviceData.type() == QLowEnergyServiceData::ServiceTypePrimary),
382 m_adaptor(new OrgBluezGattService1Adaptor(this))
383{
384}
385
387 qCDebug(QT_BT_BLUEZ) << "Adding included service" << objectPath << "for" << uuid;
388 m_includedServices.append(QDBusObjectPath(objectPath));
389}
390
392 InterfaceList interfaces;
393 interfaces.insert(bluezServiceInterface,{
394 {"UUID"_L1, uuid},
395 {"Primary"_L1, m_isPrimary},
396 {"Includes"_L1, QVariant::fromValue(m_includedServices)}
397 });
398 return interfaces;
399};
400
402
403#include "moc_bluezperipheralobjects_p.cpp"
static constexpr auto servicePathTemplate
static constexpr auto descriptorPathTemplate
static constexpr auto characteristicPathTemplate
static constexpr auto bluezErrorInvalidOffset
static constexpr auto bluezServiceInterface
static constexpr int maximumAttributeLength
static constexpr auto bluezErrorInvalidValueLength
static constexpr auto bluezCharacteristicInterface
static constexpr auto bluezDescriptorInterface
static constexpr auto bluezErrorNotAuthorized
void PropertiesChanged(const QString &interface, const QVariantMap &changed_properties, const QStringList &invalidated_properties)
\inmodule QtCore
Definition qbytearray.h:57
qsizetype length() const noexcept
Same as size().
Definition qbytearray.h:499
QByteArray sliced(qsizetype pos) const &
Definition qbytearray.h:200
QByteArray mid(qsizetype index, qsizetype len=-1) const &
static QDBusConnection systemBus()
Returns a QDBusConnection object opened with the system bus.
\inmodule QtDBus
void append(parameter_type t)
Definition qlist.h:458
The QLowEnergyCharacteristicData class is used to set up GATT service data. \inmodule QtBluetooth.
The QLowEnergyDescriptorData class is used to create GATT service data. \inmodule QtBluetooth.
The QLowEnergyServiceData class is used to set up GATT service data. \inmodule QtBluetooth.
iterator insert(const Key &key, const T &value)
Definition qmap.h:688
T value(const Key &key, const T &defaultValue=T()) const
Definition qmap.h:357
\inmodule QtCore
Definition qobject.h:103
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
\inmodule QtCore
Definition quuid.h:31
T value() const &
Definition qvariant.h:516
uint toUInt(bool *ok=nullptr) const
Returns the variant as an unsigned int if the variant has userType() \l QMetaType::UInt,...
bool toBool() const
Returns the variant as a bool if the variant has userType() Bool.
static auto fromValue(T &&value) noexcept(std::is_nothrow_copy_constructible_v< T > &&Private::CanUseInternalSpace< T >) -> std::enable_if_t< std::conjunction_v< std::is_copy_constructible< T >, std::is_destructible< T > >, QVariant >
Definition qvariant.h:536
Q_INVOKABLE QString WriteValue(const QByteArray &value, const QVariantMap &options)
InterfaceList properties() const final
QtBluezPeripheralCharacteristic(const QLowEnergyCharacteristicData &characteristicData, const QString &servicePath, quint16 ordinal, QLowEnergyHandle handle, QObject *parent)
Q_INVOKABLE QByteArray ReadValue(const QVariantMap &options, QString &error)
void valueUpdatedByRemote(QLowEnergyHandle handle, const QByteArray &value)
bool localValueUpdate(const QByteArray &value)
Q_INVOKABLE QString WriteValue(const QByteArray &value, const QVariantMap &options)
QtBluezPeripheralDescriptor(const QLowEnergyDescriptorData &descriptorData, const QString &characteristicPath, quint16 ordinal, QLowEnergyHandle handle, QLowEnergyHandle characteristicHandle, QObject *parent)
Q_INVOKABLE QByteArray ReadValue(const QVariantMap &options, QString &error)
bool localValueUpdate(const QByteArray &value)
void valueUpdatedByRemote(QLowEnergyHandle characteristicHandle, QLowEnergyHandle descriptorHandle, const QByteArray &value)
InterfaceList properties() const final
OrgFreedesktopDBusPropertiesAdaptor * propertiesAdaptor
void accessEvent(const QVariantMap &options)
void remoteDeviceAccessEvent(const QString &remoteDeviceObjectPath, quint16 mtu)
QtBluezPeripheralGattObject(const QString &objectPath, const QString &uuid, QLowEnergyHandle handle, QObject *parent=nullptr)
void addIncludedService(const QString &objectPath)
InterfaceList properties() const final
QtBluezPeripheralService(const QLowEnergyServiceData &serviceData, const QString &applicationPath, quint16 ordinal, QLowEnergyHandle handle, QObject *parent)
#define this
Definition dialogs.cpp:9
Combined button and popup list for selecting options.
quint16 QLowEnergyHandle
Definition qbluetooth.h:42
DBusConnection const char DBusError * error
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qCWarning(category,...)
#define qCDebug(category,...)
#define Q_DECLARE_LOGGING_CATEGORY(name)
GLuint64 GLenum void * handle
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum type
GLenum GLuint GLintptr offset
GLsizei const GLchar *const * path
SSL_CTX int void * arg
#define emit
unsigned short quint16
Definition qtypes.h:48
char * toString(const MyType &t)
[31]