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