Revision control
Copy as Markdown
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "bluezperipheralobjects_p.h"
#include "propertiesadaptor_p.h"
#include "gattservice1adaptor_p.h"
#include "gattcharacteristic1adaptor_p.h"
#include "gattdescriptor1adaptor_p.h"
#include <QtCore/QLoggingCategory>
#include <QtDBus/QDBusConnection>
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
using namespace Qt::StringLiterals;
using namespace QtBluetoothPrivate; // for D-Bus adaptors
static constexpr auto characteristicPathTemplate{"%1/char%2"_L1};
static constexpr auto descriptorPathTemplate{"%1/desc%2"_L1};
static constexpr auto servicePathTemplate{"%1/service%2"_L1};
// The interface names and error values are from BlueZ "gatt-api" documentation
static constexpr auto bluezServiceInterface{"org.bluez.GattService1"_L1};
static constexpr auto bluezCharacteristicInterface{"org.bluez.GattCharacteristic1"_L1};
static constexpr auto bluezDescriptorInterface{"org.bluez.GattDescriptor1"_L1};
static constexpr auto bluezErrorInvalidValueLength{"org.bluez.Error.InvalidValueLength"_L1};
static constexpr auto bluezErrorInvalidOffset{"org.bluez.Error.InvalidOffset"_L1};
static constexpr auto bluezErrorNotAuthorized{"org.bluez.Error.NotAuthorized"_L1};
// Bluetooth Core v5.3, 3.2.9, Vol 3, Part F
static constexpr int maximumAttributeLength{512};
QtBluezPeripheralGattObject::QtBluezPeripheralGattObject(const QString& objectPath,
const QString& uuid, QLowEnergyHandle handle, QObject* parent)
: QObject(parent), objectPath(objectPath), uuid(uuid), handle(handle),
propertiesAdaptor(new OrgFreedesktopDBusPropertiesAdaptor(this))
{}
QtBluezPeripheralGattObject::~QtBluezPeripheralGattObject()
{
unregisterObject();
}
bool QtBluezPeripheralGattObject::registerObject()
{
if (m_registered)
return true;
if (QDBusConnection::systemBus().registerObject(objectPath, this)) {
qCDebug(QT_BT_BLUEZ) << "Registered object on DBus:" << objectPath << uuid;
m_registered = true;
return true;
} else {
qCWarning(QT_BT_BLUEZ) << "Failed to register object on DBus:" << objectPath << uuid;
return false;
}
}
void QtBluezPeripheralGattObject::unregisterObject()
{
if (!m_registered)
return;
QDBusConnection::systemBus().unregisterObject(objectPath);
qCDebug(QT_BT_BLUEZ) << "Unregistered object on DBus:" << objectPath << uuid;
m_registered = false;
}
void QtBluezPeripheralGattObject::accessEvent(const QVariantMap& options)
{
// Report this event for connection management purposes
const auto remoteDevice = options.value("device"_L1).value<QDBusObjectPath>().path();
if (!remoteDevice.isEmpty())
emit remoteDeviceAccessEvent(remoteDevice, options.value("mtu"_L1).toUInt());
}
QtBluezPeripheralDescriptor::QtBluezPeripheralDescriptor(
const QLowEnergyDescriptorData& descriptorData,
const QString& characteristicPath, quint16 ordinal,
QLowEnergyHandle handle, QLowEnergyHandle characteristicHandle,
QObject* parent)
: QtBluezPeripheralGattObject(descriptorPathTemplate.arg(characteristicPath).arg(ordinal),
descriptorData.uuid().toString(QUuid::WithoutBraces), handle, parent),
m_adaptor(new OrgBluezGattDescriptor1Adaptor(this)),
m_characteristicPath(characteristicPath),
m_characteristicHandle(characteristicHandle)
{
if (descriptorData.value().size() > maximumAttributeLength) {
qCWarning(QT_BT_BLUEZ) << "Descriptor value is too large, cropping it to"
<< maximumAttributeLength;
m_value = descriptorData.value().sliced(0, maximumAttributeLength);
} else {
m_value = descriptorData.value();
}
initializeFlags(descriptorData);
}
InterfaceList QtBluezPeripheralDescriptor::properties() const
{
InterfaceList properties;
properties.insert(bluezDescriptorInterface,
{
{"UUID"_L1, uuid},
{"Characteristic"_L1, QDBusObjectPath(m_characteristicPath)},
{"Flags"_L1, m_flags}
});
return properties;
}
// org.bluez.GattDescriptor1
// This function is invoked when remote device reads the value
QByteArray QtBluezPeripheralDescriptor::ReadValue(const QVariantMap &options, QString& error)
{
accessEvent(options);
// Offset is set by Bluez when the value size is more than MTU size.
// Bluez deduces the value size from the first ReadValue. If the
// received data size is larger than MTU, Bluez will take the first MTU bytes and
// issue more ReadValue calls with the 'offset' set
const quint16 offset = options.value("offset"_L1).toUInt();
const quint16 mtu = options.value("mtu"_L1).toUInt();
if (offset > m_value.length() - 1) {
qCWarning(QT_BT_BLUEZ) << "Invalid offset" << offset << ", value len:" << m_value.length();
error = bluezErrorInvalidOffset;
return {};
}
if (offset > 0)
return m_value.mid(offset, mtu);
else
return m_value;
}
// org.bluez.GattDescriptor1
// This function is invoked when remote device writes a value
QString QtBluezPeripheralDescriptor::WriteValue(const QByteArray &value,
const QVariantMap &options)
{
accessEvent(options);
if (options.value("prepare-authorize"_L1).toBool()) {
// Qt API doesn't provide the means for application to authorize
qCWarning(QT_BT_BLUEZ) << "Descriptor write requires authorization."
<< "The client device needs to be trusted beforehand";
return bluezErrorNotAuthorized;
}
if (value.size() > maximumAttributeLength) {
qCWarning(QT_BT_BLUEZ) << "Descriptor value is too large:" << value.size();
return bluezErrorInvalidValueLength;
}
m_value = value;
emit valueUpdatedByRemote(m_characteristicHandle, handle, value);
return {};
}
// This function is called when the value has been updated locally (server-side)
bool QtBluezPeripheralDescriptor::localValueUpdate(const QByteArray& value)
{
if (value.size() > maximumAttributeLength) {
qCWarning(QT_BT_BLUEZ) << "Descriptor value is too large:" << value.size();
return false;
}
m_value = value;
return true;
}
void QtBluezPeripheralDescriptor::initializeFlags(const QLowEnergyDescriptorData& data)
{
// Flag tokens are from org.bluez.GattDescriptor1 documentation
if (data.isReadable())
m_flags.append("read"_L1);
if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired)
m_flags.append("encrypt-read"_L1);
if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttAuthenticationRequired)
m_flags.append("encrypt-authenticated-read"_L1);
if (data.isWritable())
m_flags.append("write"_L1);
if (data.writeConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired)
m_flags.append("encrypt-write"_L1);
if (data.writeConstraints() & QBluetooth::AttAccessConstraint::AttAuthenticationRequired)
m_flags.append("encrypt-authenticated-write"_L1);
if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttAuthorizationRequired
|| data.writeConstraints() & QBluetooth::AttAccessConstraint::AttAuthorizationRequired)
m_flags.append("authorize"_L1);
if (m_flags.isEmpty()) {
qCWarning(QT_BT_BLUEZ) << "Descriptor property flags not set" << uuid
<< "Peripheral may fail to register";
}
}
QtBluezPeripheralCharacteristic::QtBluezPeripheralCharacteristic(
const QLowEnergyCharacteristicData& characteristicData,
const QString& servicePath, quint16 ordinal,
QLowEnergyHandle handle, QObject* parent)
: QtBluezPeripheralGattObject(characteristicPathTemplate.arg(servicePath).arg(ordinal),
characteristicData.uuid().toString(QUuid::WithoutBraces), handle, parent),
m_adaptor(new OrgBluezGattCharacteristic1Adaptor(this)),
m_servicePath(servicePath),
m_minimumValueLength(std::min(characteristicData.minimumValueLength(),
maximumAttributeLength)),
m_maximumValueLength(std::min(characteristicData.maximumValueLength(),
maximumAttributeLength))
{
initializeFlags(characteristicData);
initializeValue(characteristicData.value());
}
InterfaceList QtBluezPeripheralCharacteristic::properties() const
{
InterfaceList properties;
properties.insert(bluezCharacteristicInterface,
{
{"UUID"_L1, uuid},
{"Service"_L1, QDBusObjectPath(m_servicePath)},
{"Flags"_L1, m_flags}
});
return properties;
}
// org.bluez.GattCharacteristic1
// This function is invoked when remote device reads the value
QByteArray QtBluezPeripheralCharacteristic::ReadValue(const QVariantMap &options, QString& error)
{
accessEvent(options);
// Offset is set by Bluez when the value size is more than MTU size.
// Bluez deduces the value size from the first ReadValue. If the
// received data size is larger than MTU, Bluez will take the first MTU bytes and
// issue more ReadValue calls with the 'offset' set
const quint16 offset = options.value("offset"_L1).toUInt();
const quint16 mtu = options.value("mtu"_L1).toUInt();
if (offset > m_value.length() - 1) {
qCWarning(QT_BT_BLUEZ) << "Invalid offset" << offset << ", value len:" << m_value.length();
error = bluezErrorInvalidOffset;
return {};
}
if (offset > 0)
return m_value.mid(offset, mtu);
else
return m_value;
}
// org.bluez.GattCharacteristic1
// This function is invoked when remote device writes a value
QString QtBluezPeripheralCharacteristic::WriteValue(const QByteArray &value,
const QVariantMap &options)
{
accessEvent(options);
if (options.value("prepare-authorize"_L1).toBool()) {
// Qt API doesn't provide the means for application to authorize
qCWarning(QT_BT_BLUEZ) << "Characteristic write requires authorization."
<< "The client device needs to be trusted beforehand";
return bluezErrorNotAuthorized;
}
if (value.size() < m_minimumValueLength || value.size() > m_maximumValueLength) {
qCWarning(QT_BT_BLUEZ) << "Characteristic value has invalid length" << value.size()
<< "min:" << m_minimumValueLength
<< "max:" << m_maximumValueLength;
return bluezErrorInvalidValueLength;
}
m_value = value;
emit valueUpdatedByRemote(handle, value);
return {};
}
// This function is called when the value has been updated locally (server-side)
bool QtBluezPeripheralCharacteristic::localValueUpdate(const QByteArray& value)
{
if (value.size() < m_minimumValueLength || value.size() > m_maximumValueLength) {
qCWarning(QT_BT_BLUEZ) << "Characteristic value has invalid length" << value.size()
<< "min:" << m_minimumValueLength
<< "max:" << m_maximumValueLength;
return false;
}
m_value = value;
if (m_notifying) {
emit propertiesAdaptor->PropertiesChanged(
bluezCharacteristicInterface, {{"Value"_L1, m_value}}, {});
}
return true;
}
// org.bluez.GattCharacteristic1
// These are called when remote client enables or disables NTF/IND
void QtBluezPeripheralCharacteristic::StartNotify()
{
qCDebug(QT_BT_BLUEZ) << "NTF or IND enabled for characteristic" << uuid;
m_notifying = true;
}
void QtBluezPeripheralCharacteristic::StopNotify()
{
qCDebug(QT_BT_BLUEZ) << "NTF or IND disabled for characteristic" << uuid;
m_notifying = false;
}
void QtBluezPeripheralCharacteristic::initializeValue(const QByteArray& value)
{
const auto valueSize = value.size();
if (valueSize < m_minimumValueLength || valueSize > m_maximumValueLength) {
qCWarning(QT_BT_BLUEZ) << "Characteristic value has invalid length" << valueSize
<< "min:" << m_minimumValueLength
<< "max:" << m_maximumValueLength;
m_value = QByteArray(m_minimumValueLength, 0);
} else {
m_value = value;
}
}
void QtBluezPeripheralCharacteristic::initializeFlags(const QLowEnergyCharacteristicData& data)
{
// Flag tokens are from org.bluez.GattCharacteristic1 documentation
if (data.properties() & QLowEnergyCharacteristic::PropertyType::Broadcasting)
m_flags.append("broadcast"_L1);
if (data.properties() & QLowEnergyCharacteristic::PropertyType::WriteNoResponse)
m_flags.append("write-without-response"_L1);
if (data.properties() & QLowEnergyCharacteristic::PropertyType::Read)
m_flags.append("read"_L1);
if (data.properties() & QLowEnergyCharacteristic::PropertyType::Write)
m_flags.append("write"_L1);
if (data.properties() & QLowEnergyCharacteristic::PropertyType::Notify)
m_flags.append("notify"_L1);
if (data.properties() & QLowEnergyCharacteristic::PropertyType::Indicate)
m_flags.append("indicate"_L1);
if (data.properties() & QLowEnergyCharacteristic::PropertyType::WriteSigned)
m_flags.append("authenticated-signed-writes"_L1);
if (data.properties() & QLowEnergyCharacteristic::PropertyType::ExtendedProperty) {
// If extended properties property is set, check if we have the descriptor
// describing them. Bluez will generate the actual descriptor based on these
// flags. For clarity: the 'extended-properties' token mentioned in the Bluez
// API is implied by these flags.
const auto descriptors = data.descriptors();
for (const auto &descriptor : descriptors) {
// Core Bluetooth v5.3 Vol 3, Part G, 3.3.3.1
if (descriptor.uuid()
== QBluetoothUuid::DescriptorType::CharacteristicExtendedProperties
&& descriptor.value().size() == 2) {
const auto properties = descriptor.value().at(0);
if (properties & 0x01)
m_flags.append("reliable-write"_L1);
if (properties & 0x02)
m_flags.append("writable-auxiliaries"_L1);
}
}
}
if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired)
m_flags.append("encrypt-read"_L1);
if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttAuthenticationRequired)
m_flags.append("encrypt-authenticated-read"_L1);
if (data.writeConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired)
m_flags.append("encrypt-write"_L1);
if (data.writeConstraints() & QBluetooth::AttAccessConstraint::AttAuthenticationRequired)
m_flags.append("encrypt-authenticated-write"_L1);
if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttAuthorizationRequired
|| data.writeConstraints() & QBluetooth::AttAccessConstraint::AttAuthorizationRequired)
m_flags.append("authorize"_L1);
if (m_flags.isEmpty()) {
qCWarning(QT_BT_BLUEZ) << "Characteristic property flags not set" << uuid
<< "Peripheral may fail to register";
}
}
QtBluezPeripheralService::QtBluezPeripheralService(const QLowEnergyServiceData &serviceData,
const QString& applicationPath, quint16 ordinal,
QLowEnergyHandle handle, QObject* parent)
: QtBluezPeripheralGattObject(servicePathTemplate.arg(applicationPath).arg(ordinal),
serviceData.uuid().toString(QUuid::WithoutBraces), handle, parent),
m_isPrimary(serviceData.type() == QLowEnergyServiceData::ServiceTypePrimary),
m_adaptor(new OrgBluezGattService1Adaptor(this))
{
}
void QtBluezPeripheralService::addIncludedService(const QString& objectPath) {
qCDebug(QT_BT_BLUEZ) << "Adding included service" << objectPath << "for" << uuid;
m_includedServices.append(QDBusObjectPath(objectPath));
}
InterfaceList QtBluezPeripheralService::properties() const {
InterfaceList interfaces;
interfaces.insert(bluezServiceInterface,{
{"UUID"_L1, uuid},
{"Primary"_L1, m_isPrimary},
{"Includes"_L1, QVariant::fromValue(m_includedServices)}
});
return interfaces;
};
QT_END_NAMESPACE
#include "moc_bluezperipheralobjects_p.cpp"