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;
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(path: objectPath, object: 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(path: 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(key: "device"_L1).value<QDBusObjectPath>().path();
76 if (!remoteDevice.isEmpty())
77 emit remoteDeviceAccessEvent(remoteDeviceObjectPath: remoteDevice, mtu: options.value(key: "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(args: characteristicPath).arg(a: ordinal),
86 descriptorData.uuid().toString(mode: 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(pos: 0, n: maximumAttributeLength);
95 } else {
96 m_value = descriptorData.value();
97 }
98 initializeFlags(data: descriptorData);
99}
100
101InterfaceList QtBluezPeripheralDescriptor::properties() const
102{
103 InterfaceList properties;
104 properties.insert(key: bluezDescriptorInterface,
105 value: {
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(key: "offset"_L1).toUInt();
123 const quint16 mtu = options.value(key: "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(index: offset, len: 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(key: "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(characteristicHandle: m_characteristicHandle, descriptorHandle: 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(t: "read"_L1);
176 if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired)
177 m_flags.append(t: "encrypt-read"_L1);
178 if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttAuthenticationRequired)
179 m_flags.append(t: "encrypt-authenticated-read"_L1);
180
181 if (data.isWritable())
182 m_flags.append(t: "write"_L1);
183 if (data.writeConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired)
184 m_flags.append(t: "encrypt-write"_L1);
185 if (data.writeConstraints() & QBluetooth::AttAccessConstraint::AttAuthenticationRequired)
186 m_flags.append(t: "encrypt-authenticated-write"_L1);
187
188 if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttAuthorizationRequired
189 || data.writeConstraints() & QBluetooth::AttAccessConstraint::AttAuthorizationRequired)
190 m_flags.append(t: "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(args: servicePath).arg(a: ordinal),
203 characteristicData.uuid().toString(mode: QUuid::WithoutBraces), handle, parent),
204 m_adaptor(new OrgBluezGattCharacteristic1Adaptor(this)),
205 m_servicePath(servicePath),
206 m_minimumValueLength(std::min(a: characteristicData.minimumValueLength(),
207 b: maximumAttributeLength)),
208 m_maximumValueLength(std::min(a: characteristicData.maximumValueLength(),
209 b: maximumAttributeLength))
210{
211 initializeFlags(data: characteristicData);
212 initializeValue(value: characteristicData.value());
213}
214
215InterfaceList QtBluezPeripheralCharacteristic::properties() const
216{
217 InterfaceList properties;
218 properties.insert(key: bluezCharacteristicInterface,
219 value: {
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(key: "offset"_L1).toUInt();
237 const quint16 mtu = options.value(key: "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(index: offset, len: 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(key: "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 interface: bluezCharacteristicInterface, changed_properties: {{"Value"_L1, m_value}}, invalidated_properties: {});
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(t: "broadcast"_L1);
326 if (data.properties() & QLowEnergyCharacteristic::PropertyType::WriteNoResponse)
327 m_flags.append(t: "write-without-response"_L1);
328 if (data.properties() & QLowEnergyCharacteristic::PropertyType::Read)
329 m_flags.append(t: "read"_L1);
330 if (data.properties() & QLowEnergyCharacteristic::PropertyType::Write)
331 m_flags.append(t: "write"_L1);
332 if (data.properties() & QLowEnergyCharacteristic::PropertyType::Notify)
333 m_flags.append(t: "notify"_L1);
334 if (data.properties() & QLowEnergyCharacteristic::PropertyType::Indicate)
335 m_flags.append(t: "indicate"_L1);
336 if (data.properties() & QLowEnergyCharacteristic::PropertyType::WriteSigned)
337 m_flags.append(t: "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(i: 0);
349 if (properties & 0x01)
350 m_flags.append(t: "reliable-write"_L1);
351 if (properties & 0x02)
352 m_flags.append(t: "writable-auxiliaries"_L1);
353 }
354 }
355 }
356
357 if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired)
358 m_flags.append(t: "encrypt-read"_L1);
359 if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttAuthenticationRequired)
360 m_flags.append(t: "encrypt-authenticated-read"_L1);
361 if (data.writeConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired)
362 m_flags.append(t: "encrypt-write"_L1);
363 if (data.writeConstraints() & QBluetooth::AttAccessConstraint::AttAuthenticationRequired)
364 m_flags.append(t: "encrypt-authenticated-write"_L1);
365
366 if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttAuthorizationRequired
367 || data.writeConstraints() & QBluetooth::AttAccessConstraint::AttAuthorizationRequired)
368 m_flags.append(t: "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(args: applicationPath).arg(a: ordinal),
381 serviceData.uuid().toString(mode: 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(t: QDBusObjectPath(objectPath));
390}
391
392InterfaceList QtBluezPeripheralService::properties() const {
393 InterfaceList interfaces;
394 interfaces.insert(key: bluezServiceInterface,value: {
395 {"UUID"_L1, uuid},
396 {"Primary"_L1, m_isPrimary},
397 {"Includes"_L1, QVariant::fromValue(value: m_includedServices)}
398 });
399 return interfaces;
400};
401
402QT_END_NAMESPACE
403
404#include "moc_bluezperipheralobjects_p.cpp"
405

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