summaryrefslogtreecommitdiffstats
path: root/src/bluetooth
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@theqtcompany.com>2015-12-08 11:36:58 +0100
committerAlex Blasche <alexander.blasche@theqtcompany.com>2015-12-15 15:13:34 +0000
commitc4f5a247cccda4bad46aeff530364f7e4da2df57 (patch)
treee2ee868fa1ce9bfae08bb4ecb146c48c66789c09 /src/bluetooth
parent39781901b1183429cb62310a1cee4c3ffa49a0ec (diff)
Bluetooth LE: Implement GATT server write support.
Write Request, Write Command and Execute Write Request are fully implemented now. Signed Write support is still missing. Notifications and Indications are sent. The server side gets informed via the respective signals when a client writes a characteristic or descriptor. Change-Id: Icba6a0270f6e1c4c3ed2ba61b55c1a5fbb69752b Reviewed-by: Alex Blasche <alexander.blasche@theqtcompany.com>
Diffstat (limited to 'src/bluetooth')
-rw-r--r--src/bluetooth/qlowenergycharacteristicdata.cpp43
-rw-r--r--src/bluetooth/qlowenergycharacteristicdata.h4
-rw-r--r--src/bluetooth/qlowenergycontroller.cpp2
-rw-r--r--src/bluetooth/qlowenergycontroller_bluez.cpp507
-rw-r--r--src/bluetooth/qlowenergycontroller_p.h63
-rw-r--r--src/bluetooth/qlowenergyservice.cpp73
-rw-r--r--src/bluetooth/qlowenergyservice.h3
-rw-r--r--src/bluetooth/qlowenergyserviceprivate.cpp2
-rw-r--r--src/bluetooth/qlowenergyserviceprivate_p.h4
9 files changed, 587 insertions, 114 deletions
diff --git a/src/bluetooth/qlowenergycharacteristicdata.cpp b/src/bluetooth/qlowenergycharacteristicdata.cpp
index f11779a5..a200f1c0 100644
--- a/src/bluetooth/qlowenergycharacteristicdata.cpp
+++ b/src/bluetooth/qlowenergycharacteristicdata.cpp
@@ -39,13 +39,19 @@
#include <QtCore/qloggingcategory.h>
#include <QtCore/qdebug.h>
+#include <climits>
+
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(QT_BT)
struct QLowEnergyCharacteristicDataPrivate : public QSharedData
{
- QLowEnergyCharacteristicDataPrivate() : properties(QLowEnergyCharacteristic::Unknown) {}
+ QLowEnergyCharacteristicDataPrivate()
+ : properties(QLowEnergyCharacteristic::Unknown)
+ , minimumValueLength(0)
+ , maximumValueLength(INT_MAX)
+ {}
QBluetoothUuid uuid;
QLowEnergyCharacteristic::PropertyTypes properties;
@@ -53,6 +59,8 @@ struct QLowEnergyCharacteristicDataPrivate : public QSharedData
QByteArray value;
QBluetooth::AttAccessConstraints readConstraints;
QBluetooth::AttAccessConstraints writeConstraints;
+ int minimumValueLength;
+ int maximumValueLength;
};
/*!
@@ -197,6 +205,35 @@ QBluetooth::AttAccessConstraints QLowEnergyCharacteristicData::writeConstraints(
}
/*!
+ Specifies \a minimum and \a maximum to be the smallest and largest length, respectively,
+ that the value of this characteristic can have. The unit is bytes. If \a minimum and
+ \a maximum are equal, the characteristic has a fixed-length value.
+ */
+void QLowEnergyCharacteristicData::setValueLength(int minimum, int maximum)
+{
+ d->minimumValueLength = minimum;
+ d->maximumValueLength = qMax(minimum, maximum);
+}
+
+/*!
+ Returns the minimum length in bytes that the value of this characteristic can have.
+ The default is zero.
+ */
+int QLowEnergyCharacteristicData::minimumValueLength() const
+{
+ return d->minimumValueLength;
+}
+
+/*!
+ Returns the maximum length in bytes that the value of this characteristic can have.
+ By default, there is no limit beyond the constraints of the data type.
+ */
+int QLowEnergyCharacteristicData::maximumValueLength() const
+{
+ return d->maximumValueLength;
+}
+
+/*!
Returns true if and only if this characteristic is valid, that is, it has a non-null UUID.
*/
bool QLowEnergyCharacteristicData::isValid() const
@@ -221,7 +258,9 @@ bool operator==(const QLowEnergyCharacteristicData &cd1, const QLowEnergyCharact
&& cd1.descriptors() == cd2.descriptors()
&& cd1.value() == cd2.value()
&& cd1.readConstraints() == cd2.readConstraints()
- && cd1.writeConstraints() == cd2.writeConstraints());
+ && cd1.writeConstraints() == cd2.writeConstraints()
+ && cd1.minimumValueLength() == cd2.maximumValueLength()
+ && cd1.maximumValueLength() == cd2.maximumValueLength());
}
/*!
diff --git a/src/bluetooth/qlowenergycharacteristicdata.h b/src/bluetooth/qlowenergycharacteristicdata.h
index dc99f002..56493198 100644
--- a/src/bluetooth/qlowenergycharacteristicdata.h
+++ b/src/bluetooth/qlowenergycharacteristicdata.h
@@ -70,6 +70,10 @@ public:
void setWriteConstraints(QBluetooth::AttAccessConstraints constraints);
QBluetooth::AttAccessConstraints writeConstraints() const;
+ void setValueLength(int minimum, int maximum);
+ int minimumValueLength() const;
+ int maximumValueLength() const;
+
bool isValid() const;
void swap(QLowEnergyCharacteristicData &other) Q_DECL_NOTHROW { qSwap(d, other.d); }
diff --git a/src/bluetooth/qlowenergycontroller.cpp b/src/bluetooth/qlowenergycontroller.cpp
index cf166281..d4d41d69 100644
--- a/src/bluetooth/qlowenergycontroller.cpp
+++ b/src/bluetooth/qlowenergycontroller.cpp
@@ -838,6 +838,8 @@ QLowEnergyService *QLowEnergyController::addService(const QLowEnergyServiceData
// for it.
const auto servicePrivate = QSharedPointer<QLowEnergyServicePrivate>::create();
+ servicePrivate->state = QLowEnergyService::LocalService;
+ servicePrivate->setController(d_ptr);
servicePrivate->uuid = service.uuid();
servicePrivate->type = service.type() == QLowEnergyServiceData::ServiceTypePrimary
? QLowEnergyService::PrimaryService : QLowEnergyService::IncludedService;
diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp
index a298ea25..2ebff8ab 100644
--- a/src/bluetooth/qlowenergycontroller_bluez.cpp
+++ b/src/bluetooth/qlowenergycontroller_bluez.cpp
@@ -39,6 +39,7 @@
#include "bluez/hcimanager_p.h"
#include <QtCore/QLoggingCategory>
+#include <QtBluetooth/QBluetoothLocalDevice>
#include <QtBluetooth/QBluetoothSocket>
#include <QtBluetooth/QLowEnergyCharacteristicData>
#include <QtBluetooth/QLowEnergyDescriptorData>
@@ -46,6 +47,7 @@
#include <QtBluetooth/QLowEnergyServiceData>
#include <algorithm>
+#include <climits>
#include <cstring>
#include <errno.h>
#include <sys/types.h>
@@ -239,7 +241,6 @@ template<> void putDataAndIncrement(const QByteArray &value, char *&dst)
dst += value.count();
}
-
QLowEnergyControllerPrivate::QLowEnergyControllerPrivate()
: QObject(),
state(QLowEnergyController::UnconnectedState),
@@ -433,6 +434,8 @@ void QLowEnergyControllerPrivate::l2cpDisconnected()
{
Q_Q(QLowEnergyController);
+ if (role == QLowEnergyController::PeripheralRole)
+ storeClientConfigurations();
invalidateServices();
resetController();
setState(QLowEnergyController::UnconnectedState);
@@ -472,6 +475,8 @@ void QLowEnergyControllerPrivate::resetController()
{
openRequests.clear();
openPrepareWriteRequests.clear();
+ scheduledIndications.clear();
+ indicationInFlight = false;
requestPending = false;
encryptionChangePending = false;
receivedMtuExchangeRequest = false;
@@ -539,6 +544,14 @@ void QLowEnergyControllerPrivate::l2cpReadyRead()
case ATT_OP_EXECUTE_WRITE_REQUEST:
handleExecuteWriteRequest(incomingPacket);
return;
+ case ATT_OP_HANDLE_VAL_CONFIRMATION:
+ if (indicationInFlight) {
+ indicationInFlight = false;
+ sendNextIndication();
+ } else {
+ qCWarning(QT_BT_BLUEZ) << "received unexpected handle value confirmation";
+ }
+ return;
default:
//only solicited replies finish pending requests
requestPending = false;
@@ -1714,49 +1727,13 @@ void QLowEnergyControllerPrivate::writeCharacteristic(
if (!service->characteristicList.contains(charHandle))
return;
- const QLowEnergyHandle valueHandle = service->characteristicList[charHandle].valueHandle;
- const int size = WRITE_REQUEST_HEADER_SIZE + newValue.size();
-
- quint8 packet[WRITE_REQUEST_HEADER_SIZE];
- if (writeWithResponse) {
- if (newValue.size() > (mtuSize - WRITE_REQUEST_HEADER_SIZE)) {
- sendNextPrepareWriteRequest(charHandle, newValue, 0);
- sendNextPendingRequest();
- return;
- } else {
- // write value fits into single package
- packet[0] = ATT_OP_WRITE_REQUEST;
- }
+ QLowEnergyServicePrivate::CharData &charData = service->characteristicList[charHandle];
+ if (role == QLowEnergyController::PeripheralRole) {
+ writeCharacteristicForPeripheral(charData, newValue);
} else {
- // write without response
- packet[0] = ATT_OP_WRITE_COMMAND;
- }
-
- bt_put_unaligned(htobs(valueHandle), (quint16 *) &packet[1]);
-
- QByteArray data(size, Qt::Uninitialized);
- memcpy(data.data(), packet, WRITE_REQUEST_HEADER_SIZE);
- memcpy(&(data.data()[WRITE_REQUEST_HEADER_SIZE]), newValue.constData(), newValue.size());
-
- qCDebug(QT_BT_BLUEZ) << "Writing characteristic" << hex << charHandle
- << "(size:" << size << "with response:" << writeWithResponse << ")";
-
- // Advantage of write without response is the quick turnaround.
- // It can be send at any time and does not produce responses.
- // Therefore we will not put them into the openRequest queue at all.
- if (!writeWithResponse) {
- sendPacket(data);
- return;
+ writeCharacteristicForCentral(charHandle, charData.valueHandle, newValue,
+ writeWithResponse);
}
-
- Request request;
- request.payload = data;
- request.command = ATT_OP_WRITE_REQUEST;
- request.reference = charHandle;
- request.reference2 = newValue;
- openRequests.enqueue(request);
-
- sendNextPendingRequest();
}
void QLowEnergyControllerPrivate::writeDescriptor(
@@ -1767,32 +1744,10 @@ void QLowEnergyControllerPrivate::writeDescriptor(
{
Q_ASSERT(!service.isNull());
- if (newValue.size() > (mtuSize - WRITE_REQUEST_HEADER_SIZE)) {
- sendNextPrepareWriteRequest(descriptorHandle, newValue, 0);
- sendNextPendingRequest();
- return;
- }
-
- quint8 packet[WRITE_REQUEST_HEADER_SIZE];
- packet[0] = ATT_OP_WRITE_REQUEST;
- bt_put_unaligned(htobs(descriptorHandle), (quint16 *) &packet[1]);
-
- const int size = WRITE_REQUEST_HEADER_SIZE + newValue.size();
- QByteArray data(size, Qt::Uninitialized);
- memcpy(data.data(), packet, WRITE_REQUEST_HEADER_SIZE);
- memcpy(&(data.data()[WRITE_REQUEST_HEADER_SIZE]), newValue.constData(), newValue.size());
-
- qCDebug(QT_BT_BLUEZ) << "Writing descriptor" << hex << descriptorHandle
- << "(size:" << size << ")";
-
- Request request;
- request.payload = data;
- request.command = ATT_OP_WRITE_REQUEST;
- request.reference = (charHandle | (descriptorHandle << 16));
- request.reference2 = newValue;
- openRequests.enqueue(request);
-
- sendNextPendingRequest();
+ if (role == QLowEnergyController::PeripheralRole)
+ writeDescriptorForPeripheral(service, charHandle, descriptorHandle, newValue);
+ else
+ writeDescriptorForCentral(charHandle, descriptorHandle, newValue);
}
/*!
@@ -2251,14 +2206,205 @@ void QLowEnergyControllerPrivate::handleReadByGroupTypeRequest(const QByteArray
sendListResponse(responsePrefix, elementSize, results, elemWriter);
}
+void QLowEnergyControllerPrivate::updateLocalAttributeValue(
+ QLowEnergyHandle handle,
+ const QByteArray &value,
+ QLowEnergyCharacteristic &characteristic,
+ QLowEnergyDescriptor &descriptor)
+{
+ localAttributes[handle].value = value;
+ foreach (const auto &service, localServices) {
+ if (handle < service->startHandle || handle > service->endHandle)
+ continue;
+ for (auto charIt = service->characteristicList.begin();
+ charIt != service->characteristicList.end(); ++charIt) {
+ QLowEnergyServicePrivate::CharData &charData = charIt.value();
+ if (handle == charIt.key() + 1) { // Char value decl comes right after char decl.
+ charData.value = value;
+ characteristic = QLowEnergyCharacteristic(service, charIt.key());
+ return;
+ }
+ for (auto descIt = charData.descriptorList.begin();
+ descIt != charData.descriptorList.end(); ++descIt) {
+ if (handle == descIt.key()) {
+ descIt.value().value = value;
+ descriptor = QLowEnergyDescriptor(service, charIt.key(), handle);
+ return;
+ }
+ }
+ }
+ }
+ qFatal("local services map inconsistent with local attribute map");
+}
+
+static bool isNotificationEnabled(quint16 clientConfigValue) { return clientConfigValue & 0x1; }
+static bool isIndicationEnabled(quint16 clientConfigValue) { return clientConfigValue & 0x2; }
+
+void QLowEnergyControllerPrivate::writeCharacteristicForPeripheral(
+ QLowEnergyServicePrivate::CharData &charData,
+ const QByteArray &newValue)
+{
+ const QLowEnergyHandle valueHandle = charData.valueHandle;
+ Q_ASSERT(valueHandle <= lastLocalHandle);
+ Attribute &attribute = localAttributes[valueHandle];
+ if (newValue.count() < attribute.minLength || newValue.count() > attribute.maxLength) {
+ qCWarning(QT_BT_BLUEZ) << "ignoring value of invalid length" << newValue.count()
+ << "for attribute" << valueHandle;
+ return;
+ }
+ attribute.value = newValue;
+ charData.value = newValue;
+ const bool hasNotifyProperty = attribute.properties & QLowEnergyCharacteristic::Notify;
+ const bool hasIndicateProperty
+ = attribute.properties & QLowEnergyCharacteristic::Indicate;
+ if (!hasNotifyProperty && !hasIndicateProperty)
+ return;
+ foreach (const QLowEnergyServicePrivate::DescData &desc, charData.descriptorList) {
+ if (desc.uuid != QBluetoothUuid::ClientCharacteristicConfiguration)
+ continue;
+
+ // Notify/indicate currently connected client.
+ const bool isConnected = state == QLowEnergyController::ConnectedState;
+ if (isConnected) {
+ Q_ASSERT(desc.value.count() == 2);
+ quint16 configValue = bt_get_le16(desc.value.constData());
+ if (isNotificationEnabled(configValue) && hasNotifyProperty) {
+ sendNotification(valueHandle);
+ } else if (isIndicationEnabled(configValue) && hasIndicateProperty) {
+ if (indicationInFlight)
+ scheduledIndications << valueHandle;
+ else
+ sendIndication(valueHandle);
+ }
+ }
+
+ // Prepare notification/indication of unconnected, bonded clients.
+ for (auto it = clientConfigData.begin(); it != clientConfigData.end(); ++it) {
+ if (isConnected && it.key() == remoteDevice.toUInt64())
+ continue;
+ QVector<ClientConfigurationData> &configDataList = it.value();
+ for (ClientConfigurationData &configData : configDataList) {
+ if (configData.charValueHandle != valueHandle)
+ continue;
+ if ((isNotificationEnabled(configData.configValue) && hasNotifyProperty)
+ || (isIndicationEnabled(configData.configValue) && hasIndicateProperty)) {
+ configData.charValueWasUpdated = true;
+ break;
+ }
+ }
+ }
+ break;
+ }
+}
+
+void QLowEnergyControllerPrivate::writeCharacteristicForCentral(
+ QLowEnergyHandle charHandle,
+ QLowEnergyHandle valueHandle,
+ const QByteArray &newValue,
+ bool writeWithResponse)
+{
+ const int size = WRITE_REQUEST_HEADER_SIZE + newValue.size();
+
+ quint8 packet[WRITE_REQUEST_HEADER_SIZE];
+ if (writeWithResponse) {
+ if (newValue.size() > (mtuSize - WRITE_REQUEST_HEADER_SIZE)) {
+ sendNextPrepareWriteRequest(charHandle, newValue, 0);
+ sendNextPendingRequest();
+ return;
+ } else {
+ // write value fits into single package
+ packet[0] = ATT_OP_WRITE_REQUEST;
+ }
+ } else {
+ // write without response
+ packet[0] = ATT_OP_WRITE_COMMAND;
+ }
+
+ bt_put_unaligned(htobs(valueHandle), (quint16 *) &packet[1]);
+
+ QByteArray data(size, Qt::Uninitialized);
+ memcpy(data.data(), packet, WRITE_REQUEST_HEADER_SIZE);
+ memcpy(&(data.data()[WRITE_REQUEST_HEADER_SIZE]), newValue.constData(), newValue.size());
+
+ qCDebug(QT_BT_BLUEZ) << "Writing characteristic" << hex << charHandle
+ << "(size:" << size << "with response:" << writeWithResponse << ")";
+
+ // Advantage of write without response is the quick turnaround.
+ // It can be send at any time and does not produce responses.
+ // Therefore we will not put them into the openRequest queue at all.
+ if (!writeWithResponse) {
+ sendPacket(data);
+ return;
+ }
+
+ Request request;
+ request.payload = data;
+ request.command = ATT_OP_WRITE_REQUEST;
+ request.reference = charHandle;
+ request.reference2 = newValue;
+ openRequests.enqueue(request);
+
+ sendNextPendingRequest();
+}
+
+void QLowEnergyControllerPrivate::writeDescriptorForPeripheral(
+ const QSharedPointer<QLowEnergyServicePrivate> &service,
+ const QLowEnergyHandle charHandle,
+ const QLowEnergyHandle descriptorHandle,
+ const QByteArray &newValue)
+{
+ Q_ASSERT(descriptorHandle <= lastLocalHandle);
+ Attribute &attribute = localAttributes[descriptorHandle];
+ if (newValue.count() < attribute.minLength || newValue.count() > attribute.maxLength) {
+ qCWarning(QT_BT_BLUEZ) << "invalid value of size" << newValue.count()
+ << "for attribute" << descriptorHandle;
+ return;
+ }
+ attribute.value = newValue;
+ service->characteristicList[charHandle].descriptorList[descriptorHandle].value = newValue;
+}
+
+void QLowEnergyControllerPrivate::writeDescriptorForCentral(
+ const QLowEnergyHandle charHandle,
+ const QLowEnergyHandle descriptorHandle,
+ const QByteArray &newValue)
+{
+ if (newValue.size() > (mtuSize - WRITE_REQUEST_HEADER_SIZE)) {
+ sendNextPrepareWriteRequest(descriptorHandle, newValue, 0);
+ sendNextPendingRequest();
+ return;
+ }
+
+ quint8 packet[WRITE_REQUEST_HEADER_SIZE];
+ packet[0] = ATT_OP_WRITE_REQUEST;
+ bt_put_unaligned(htobs(descriptorHandle), (quint16 *) &packet[1]);
+
+ const int size = WRITE_REQUEST_HEADER_SIZE + newValue.size();
+ QByteArray data(size, Qt::Uninitialized);
+ memcpy(data.data(), packet, WRITE_REQUEST_HEADER_SIZE);
+ memcpy(&(data.data()[WRITE_REQUEST_HEADER_SIZE]), newValue.constData(), newValue.size());
+
+ qCDebug(QT_BT_BLUEZ) << "Writing descriptor" << hex << descriptorHandle
+ << "(size:" << size << ")";
+
+ Request request;
+ request.payload = data;
+ request.command = ATT_OP_WRITE_REQUEST;
+ request.reference = (charHandle | (descriptorHandle << 16));
+ request.reference2 = newValue;
+ openRequests.enqueue(request);
+
+ sendNextPendingRequest();
+}
+
void QLowEnergyControllerPrivate::handleWriteRequestOrCommand(const QByteArray &packet)
{
// Spec v4.2, Vol 3, Part F, 3.4.5.1-3
- if (!checkPacketSize(packet, 3, mtuSize))
- return;
const bool isRequest = packet.at(0) == ATT_OP_WRITE_REQUEST;
const bool isSigned = packet.at(0) == ATT_OP_SIGNED_WRITE_COMMAND;
+ if (!checkPacketSize(packet, isSigned ? 15 : 3, mtuSize))
+ return;
const QLowEnergyHandle handle = bt_get_le16(packet.constData() + 1);
qCDebug(QT_BT_BLUEZ) << "client sends" << (isSigned ? "signed" : "") << "write"
<< (isRequest ? "request" : "command") << "for handle" << handle;
@@ -2266,15 +2412,16 @@ void QLowEnergyControllerPrivate::handleWriteRequestOrCommand(const QByteArray &
if (!checkHandle(packet, handle))
return;
+ int valueLength;
if (isSigned) {
// const QByteArray signature = packet.right(12);
return; // TODO: Check signature and continue if it's valid. Store sign counter.
- // TODO: extract value
+ valueLength = packet.count() - 15;
} else {
- // TODO: Extract value.
+ valueLength = packet.count() - 3;
}
- const Attribute &attribute = localAttributes.at(handle);
+ Attribute &attribute = localAttributes[handle];
const QLowEnergyCharacteristic::PropertyType type = isRequest
? QLowEnergyCharacteristic::Write : isSigned
? QLowEnergyCharacteristic::WriteSigned : QLowEnergyCharacteristic::WriteNoResponse;
@@ -2283,16 +2430,32 @@ void QLowEnergyControllerPrivate::handleWriteRequestOrCommand(const QByteArray &
sendErrorResponse(packet.at(0), handle, permissionsError);
return;
}
- if (false /* value is greater than maximum */) {
+ if (valueLength > attribute.maxLength) {
sendErrorResponse(packet.at(0), handle, ATT_ERROR_INVAL_ATTR_VALUE_LEN);
return;
}
- // TODO: Write attribute
+ // If the attribute value has a fixed size and the value in the packet is shorter,
+ // then we overwrite only the start of the attribute value and keep the rest.
+ QByteArray value = packet.mid(3, valueLength);
+ if (attribute.minLength == attribute.maxLength && valueLength < attribute.minLength)
+ value += attribute.value.mid(valueLength, attribute.maxLength - valueLength);
- if (isRequest)
- sendErrorResponse(packet.at(0), 0, ATT_ERROR_REQUEST_NOT_SUPPORTED);
- //sendPacket(QByteArray(1, ATT_OP_WRITE_RESPONSE));
+ QLowEnergyCharacteristic characteristic;
+ QLowEnergyDescriptor descriptor;
+ updateLocalAttributeValue(handle, value, characteristic, descriptor);
+
+ if (isRequest) {
+ const QByteArray response = QByteArray(1, ATT_OP_WRITE_RESPONSE);
+ sendPacket(response);
+ }
+
+ if (characteristic.isValid()) {
+ emit characteristic.d_ptr->characteristicChanged(characteristic, value);
+ } else {
+ Q_ASSERT(descriptor.isValid());
+ emit descriptor.d_ptr->descriptorWritten(descriptor, value);
+ }
}
void QLowEnergyControllerPrivate::handlePrepareWriteRequest(const QByteArray &packet)
@@ -2338,22 +2501,40 @@ void QLowEnergyControllerPrivate::handleExecuteWriteRequest(const QByteArray &pa
QVector<WriteRequest> requests = openPrepareWriteRequests;
openPrepareWriteRequests.clear();
+ QVector<QLowEnergyCharacteristic> characteristics;
+ QVector<QLowEnergyDescriptor> descriptors;
if (!cancel) {
- if (false /* maximum value size exceeded */) {
- sendErrorResponse(packet.at(0), 0 /* handle? */, ATT_ERROR_INVAL_ATTR_VALUE_LEN);
- return;
- }
- if (false /* offset invalid */) {
- sendErrorResponse(packet.at(0), 0 /* handle? */, ATT_ERROR_INVALID_OFFSET);
- return;
+ foreach (const WriteRequest &request, requests) {
+ Attribute &attribute = localAttributes[request.handle];
+ if (request.valueOffset > attribute.value.count()) {
+ sendErrorResponse(packet.at(0), request.handle, ATT_ERROR_INVALID_OFFSET);
+ return;
+ }
+ const QByteArray newValue = attribute.value.left(request.valueOffset) + request.value;
+ if (newValue.count() > attribute.maxLength) {
+ sendErrorResponse(packet.at(0), request.handle, ATT_ERROR_INVAL_ATTR_VALUE_LEN);
+ return;
+ }
+ QLowEnergyCharacteristic characteristic;
+ QLowEnergyDescriptor descriptor;
+ // TODO: Redundant attribute lookup for the case of the same handle appearing
+ // more than once.
+ updateLocalAttributeValue(request.handle, newValue, characteristic, descriptor);
+ if (characteristic.isValid()) {
+ characteristics << characteristic;
+ } else if (descriptor.isValid()) {
+ Q_ASSERT(descriptor.isValid());
+ descriptors << descriptor;
+ }
}
+ }
- // TODO: Write attribute.
+ sendPacket(QByteArray(1, ATT_OP_EXECUTE_WRITE_RESPONSE));
- }
- openPrepareWriteRequests.clear();
- sendErrorResponse(packet.at(0), 0, ATT_ERROR_REQUEST_NOT_SUPPORTED);
- //sendPacket(QByteArray(1, ATT_OP_EXECUTE_WRITE_RESPONSE));
+ foreach (const QLowEnergyCharacteristic &characteristic, characteristics)
+ emit characteristic.d_ptr->characteristicChanged(characteristic, characteristic.value());
+ foreach (const QLowEnergyDescriptor &descriptor, descriptors)
+ emit descriptor.d_ptr->descriptorWritten(descriptor, descriptor.value());
}
void QLowEnergyControllerPrivate::sendErrorResponse(quint8 request, quint16 handle, quint8 code)
@@ -2388,6 +2569,40 @@ void QLowEnergyControllerPrivate::sendListResponse(const QByteArray &packetStart
sendPacket(response);
}
+void QLowEnergyControllerPrivate::sendNotification(QLowEnergyHandle handle)
+{
+ sendNotificationOrIndication(ATT_OP_HANDLE_VAL_NOTIFICATION, handle);
+}
+
+void QLowEnergyControllerPrivate::sendIndication(QLowEnergyHandle handle)
+{
+ Q_ASSERT(!indicationInFlight);
+ indicationInFlight = true;
+ sendNotificationOrIndication(ATT_OP_HANDLE_VAL_INDICATION, handle);
+}
+
+void QLowEnergyControllerPrivate::sendNotificationOrIndication(
+ quint8 opCode,
+ QLowEnergyHandle handle)
+{
+ Q_ASSERT(handle <= lastLocalHandle);
+ const Attribute &attribute = localAttributes.at(handle);
+ const int maxValueLength = qMin(attribute.value.count(), mtuSize - 3);
+ QByteArray packet(3 + maxValueLength, Qt::Uninitialized);
+ packet[0] = opCode;
+ putBtData(handle, packet.data() + 1);
+ using namespace std;
+ memcpy(packet.data() + 3, attribute.value.constData(), maxValueLength);
+ qCDebug(QT_BT_BLUEZ) << "sending notification/indication:" << packet.toHex();
+ sendPacket(packet);
+}
+
+void QLowEnergyControllerPrivate::sendNextIndication()
+{
+ if (!scheduledIndications.isEmpty())
+ sendIndication(scheduledIndications.takeFirst());
+}
+
void QLowEnergyControllerPrivate::handleConnectionRequest()
{
if (state != QLowEnergyController::AdvertisingState) {
@@ -2418,10 +2633,10 @@ void QLowEnergyControllerPrivate::handleConnectionRequest()
connect(l2cpSocket, &QIODevice::readyRead, this, &QLowEnergyControllerPrivate::l2cpReadyRead);
l2cpSocket->d_ptr->lowEnergySocketType = addressType == QLowEnergyController::PublicAddress
? BDADDR_LE_PUBLIC : BDADDR_LE_RANDOM;
- l2cpSocket->setSocketDescriptor(clientSocket, QBluetoothServiceInfo::L2capProtocol);
+ l2cpSocket->setSocketDescriptor(clientSocket, QBluetoothServiceInfo::L2capProtocol,
+ QBluetoothSocket::ConnectedState, QIODevice::ReadWrite | QIODevice::Unbuffered);
+ restoreClientConfigurations();
setState(QLowEnergyController::ConnectedState);
- // TODO: Send notifications and indications if this is a bonded device.
- // The latter need to be queued.
}
void QLowEnergyControllerPrivate::closeServerSocket()
@@ -2434,6 +2649,88 @@ void QLowEnergyControllerPrivate::closeServerSocket()
serverSocketNotifier = nullptr;
}
+bool QLowEnergyControllerPrivate::isBonded() const
+{
+ // Pairing does not necessarily imply bonding, but we don't know whether the
+ // bonding flag was set in the original pairing request.
+ return QBluetoothLocalDevice(localAdapter).pairingStatus(remoteDevice)
+ != QBluetoothLocalDevice::Unpaired;
+}
+
+QVector<QLowEnergyControllerPrivate::TempClientConfigurationData> QLowEnergyControllerPrivate::gatherClientConfigData()
+{
+ QVector<TempClientConfigurationData> data;
+ foreach (const auto &service, localServices) {
+ for (auto charIt = service->characteristicList.begin();
+ charIt != service->characteristicList.end(); ++charIt) {
+ QLowEnergyServicePrivate::CharData &charData = charIt.value();
+ for (auto descIt = charData.descriptorList.begin();
+ descIt != charData.descriptorList.end(); ++descIt) {
+ QLowEnergyServicePrivate::DescData &descData = descIt.value();
+ if (descData.uuid == QBluetoothUuid::ClientCharacteristicConfiguration) {
+ data << TempClientConfigurationData(&descData, charData.valueHandle,
+ descIt.key());
+ break;
+ }
+ }
+ }
+ }
+ return data;
+}
+
+void QLowEnergyControllerPrivate::storeClientConfigurations()
+{
+ if (!isBonded()) {
+ clientConfigData.remove(remoteDevice.toUInt64());
+ return;
+ }
+ QVector<ClientConfigurationData> clientConfigs;
+ const QVector<TempClientConfigurationData> &tempConfigList = gatherClientConfigData();
+ foreach (const auto &tempConfigData, tempConfigList) {
+ Q_ASSERT(tempConfigData.descData->value.count() == 2);
+ const quint16 value = bt_get_le16(tempConfigData.descData->value.constData());
+ if (value != 0) {
+ clientConfigs << ClientConfigurationData(tempConfigData.charValueHandle,
+ tempConfigData.configHandle, value);
+ }
+ }
+ clientConfigData.insert(remoteDevice.toUInt64(), clientConfigs);
+}
+
+void QLowEnergyControllerPrivate::restoreClientConfigurations()
+{
+ const QVector<TempClientConfigurationData> &tempConfigList = gatherClientConfigData();
+ const QVector<ClientConfigurationData> &restoredClientConfigs = isBonded()
+ ? clientConfigData.value(remoteDevice.toUInt64()) : QVector<ClientConfigurationData>();
+ QVector<QLowEnergyHandle> notifications;
+ foreach (const auto &tempConfigData, tempConfigList) {
+ bool wasRestored = false;
+ foreach (const auto &restoredData, restoredClientConfigs) {
+ if (restoredData.charValueHandle == tempConfigData.charValueHandle) {
+ Q_ASSERT(tempConfigData.descData->value.count() == 2);
+ putBtData(restoredData.configValue, tempConfigData.descData->value.data());
+ wasRestored = true;
+ if (restoredData.charValueWasUpdated) {
+ if (isNotificationEnabled(restoredData.configValue))
+ notifications << restoredData.charValueHandle;
+ else if (isIndicationEnabled(restoredData.configValue))
+ scheduledIndications << restoredData.charValueHandle;
+ }
+ break;
+ }
+ }
+ if (!wasRestored)
+ tempConfigData.descData->value = QByteArray(2, 0); // Default value.
+ Q_ASSERT(lastLocalHandle >= tempConfigData.configHandle);
+ Q_ASSERT(tempConfigData.configHandle > tempConfigData.charValueHandle);
+ localAttributes[tempConfigData.configHandle].value = tempConfigData.descData->value;
+ }
+
+ foreach (const QLowEnergyHandle handle, notifications)
+ sendNotification(handle);
+ sendNextIndication();
+}
+
static QByteArray uuidToByteArray(const QBluetoothUuid &uuid)
{
QByteArray ba;
@@ -2498,6 +2795,8 @@ void QLowEnergyControllerPrivate::addToGenericAttributeList(const QLowEnergyServ
attribute.readConstraints = cd.readConstraints();
attribute.writeConstraints = cd.writeConstraints();
attribute.value = cd.value();
+ attribute.minLength = cd.minimumValueLength();
+ attribute.maxLength = cd.maximumValueLength();
localAttributes[attribute.handle] = attribute;
foreach (const QLowEnergyDescriptorData &dd, cd.descriptors()) {
@@ -2507,11 +2806,19 @@ void QLowEnergyControllerPrivate::addToGenericAttributeList(const QLowEnergyServ
attribute.properties = QLowEnergyCharacteristic::PropertyTypes();
attribute.readConstraints = AttAccessConstraints();
attribute.writeConstraints = AttAccessConstraints();
+ attribute.minLength = 0;
+ attribute.maxLength = INT_MAX;
+
// Spec v4.2, Vol. 3, Part G, 3.3.3.x
- if (attribute.type == QBluetoothUuid::CharacteristicExtendedProperties
- || attribute.type == QBluetoothUuid::CharacteristicPresentationFormat
- || attribute.type == QBluetoothUuid::CharacteristicAggregateFormat) {
+ if (attribute.type == QBluetoothUuid::CharacteristicExtendedProperties) {
attribute.properties = QLowEnergyCharacteristic::Read;
+ attribute.minLength = attribute.maxLength = 2;
+ } else if (attribute.type == QBluetoothUuid::CharacteristicPresentationFormat) {
+ attribute.properties = QLowEnergyCharacteristic::Read;
+ attribute.minLength = attribute.maxLength = 7;
+ } else if (attribute.type == QBluetoothUuid::CharacteristicAggregateFormat) {
+ attribute.properties = QLowEnergyCharacteristic::Read;
+ attribute.minLength = 4;
} else if (attribute.type == QBluetoothUuid::ClientCharacteristicConfiguration
|| attribute.type == QBluetoothUuid::ServerCharacteristicConfiguration) {
attribute.properties = QLowEnergyCharacteristic::Read
@@ -2519,6 +2826,7 @@ void QLowEnergyControllerPrivate::addToGenericAttributeList(const QLowEnergyServ
| QLowEnergyCharacteristic::WriteNoResponse
| QLowEnergyCharacteristic::WriteSigned;
attribute.writeConstraints = dd.writeConstraints();
+ attribute.minLength = attribute.maxLength = 2;
} else {
if (dd.isReadable())
attribute.properties |= QLowEnergyCharacteristic::Read;
@@ -2532,6 +2840,13 @@ void QLowEnergyControllerPrivate::addToGenericAttributeList(const QLowEnergyServ
}
attribute.value = dd.value();
+ if (attribute.value.count() < attribute.minLength
+ || attribute.value.count() > attribute.maxLength) {
+ qCWarning(QT_BT_BLUEZ) << "attribute of type" << attribute.type
+ << "has invalid length of" << attribute.value.count()
+ << "bytes";
+ attribute.value = QByteArray(attribute.minLength, 0);
+ }
localAttributes[attribute.handle] = attribute;
}
}
diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h
index e693e84d..f3b28f5c 100644
--- a/src/bluetooth/qlowenergycontroller_p.h
+++ b/src/bluetooth/qlowenergycontroller_p.h
@@ -183,6 +183,8 @@ public:
QBluetooth::AttAccessConstraints writeConstraints;
QBluetoothUuid type;
QByteArray value;
+ int minLength;
+ int maxLength;
};
QVector<Attribute> localAttributes;
@@ -211,6 +213,32 @@ private:
};
QVector<WriteRequest> openPrepareWriteRequests;
+ // Invariant: !scheduledIndications.isEmpty => indicationInFlight == true
+ QVector<QLowEnergyHandle> scheduledIndications;
+ bool indicationInFlight = false;
+
+ struct TempClientConfigurationData {
+ TempClientConfigurationData(QLowEnergyServicePrivate::DescData *dd = nullptr,
+ QLowEnergyHandle chHndl = 0, QLowEnergyHandle coHndl = 0)
+ : descData(dd), charValueHandle(chHndl), configHandle(coHndl) {}
+
+ QLowEnergyServicePrivate::DescData *descData;
+ QLowEnergyHandle charValueHandle;
+ QLowEnergyHandle configHandle;
+ };
+
+ struct ClientConfigurationData {
+ ClientConfigurationData(QLowEnergyHandle chHndl = 0, QLowEnergyHandle coHndl = 0,
+ quint16 val = 0)
+ : charValueHandle(chHndl), configHandle(coHndl), configValue(val) {}
+
+ QLowEnergyHandle charValueHandle;
+ QLowEnergyHandle configHandle;
+ quint16 configValue;
+ bool charValueWasUpdated = false;
+ };
+ QHash<quint64, QVector<ClientConfigurationData>> clientConfigData;
+
bool requestPending;
quint16 mtuSize;
int securityLevelValue;
@@ -224,6 +252,11 @@ private:
void handleConnectionRequest();
void closeServerSocket();
+ bool isBonded() const;
+ QVector<TempClientConfigurationData> gatherClientConfigData();
+ void storeClientConfigurations();
+ void restoreClientConfigurations();
+
void sendPacket(const QByteArray &packet);
void sendNextPendingRequest();
void processReply(const Request &request, const QByteArray &reply);
@@ -280,6 +313,11 @@ private:
void sendListResponse(const QByteArray &packetStart, int elemSize,
const QVector<Attribute> &attributes, const ElemWriter &elemWriter);
+ void sendNotification(QLowEnergyHandle handle);
+ void sendIndication(QLowEnergyHandle handle);
+ void sendNotificationOrIndication(quint8 opCode, QLowEnergyHandle handle);
+ void sendNextIndication();
+
void ensureUniformAttributes(QVector<Attribute> &attributes, const std::function<int(const Attribute &)> &getSize);
void ensureUniformUuidSizes(QVector<Attribute> &attributes);
void ensureUniformValueSizes(QVector<Attribute> &attributes);
@@ -292,6 +330,31 @@ private:
int checkReadPermissions(const Attribute &attr);
int checkReadPermissions(QVector<Attribute> &attributes);
+ void updateLocalAttributeValue(
+ QLowEnergyHandle handle,
+ const QByteArray &value,
+ QLowEnergyCharacteristic &characteristic,
+ QLowEnergyDescriptor &descriptor);
+
+ void writeCharacteristicForPeripheral(
+ QLowEnergyServicePrivate::CharData &charData,
+ const QByteArray &newValue);
+ void writeCharacteristicForCentral(
+ QLowEnergyHandle charHandle,
+ QLowEnergyHandle valueHandle,
+ const QByteArray &newValue,
+ bool writeWithResponse);
+
+ void writeDescriptorForPeripheral(
+ const QSharedPointer<QLowEnergyServicePrivate> &service,
+ const QLowEnergyHandle charHandle,
+ const QLowEnergyHandle descriptorHandle,
+ const QByteArray &newValue);
+ void writeDescriptorForCentral(
+ const QLowEnergyHandle charHandle,
+ const QLowEnergyHandle descriptorHandle,
+ const QByteArray &newValue);
+
private slots:
void l2cpConnected();
void l2cpDisconnected();
diff --git a/src/bluetooth/qlowenergyservice.cpp b/src/bluetooth/qlowenergyservice.cpp
index 9cccbc93..a05ba4ec 100644
--- a/src/bluetooth/qlowenergyservice.cpp
+++ b/src/bluetooth/qlowenergyservice.cpp
@@ -220,6 +220,9 @@ QT_BEGIN_NAMESPACE
\l serviceUuid() and \l serviceName().
\value DiscoveringServices The service details are being discovered.
\value ServiceDiscovered The service details have been discovered.
+ \value LocalService The service is associated with a controller object in the
+ \l{QLowEnergyController::PeripheralRole}{peripheral role}. Such
+ service objects do not change their state.
*/
/*!
@@ -302,15 +305,20 @@ QT_BEGIN_NAMESPACE
/*!
\fn void QLowEnergyService::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
- This signal is emitted when the value of \a characteristic is changed
- by an event on the peripheral. The \a newValue parameter contains the
- updated value of the \a characteristic.
-
- The signal emission implies that change notifications must
+ If the associated controller object is in the \l {QLowEnergyController::CentralRole}{central}
+ role, this signal is emitted when the value of \a characteristic is changed by an event on the
+ peripheral. In that case, the signal emission implies that change notifications must
have been activated via the characteristic's
\l {QBluetoothUuid::ClientCharacteristicConfiguration}{ClientCharacteristicConfiguration}
descriptor prior to the change event on the peripheral. More details on how this might be
done can be found further \l{notifications}{above}.
+
+ If the controller is in the \l {QLowEnergyController::PeripheralRole}{peripheral} role, that is,
+ the service object was created via \l QLowEnergyController::addService, the signal is emitted
+ when a GATT client has written the value of the characteristic using a write request or command.
+
+ The \a newValue parameter contains the updated value of the \a characteristic.
+
*/
/*!
@@ -329,8 +337,10 @@ QT_BEGIN_NAMESPACE
\fn void QLowEnergyService::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue)
This signal is emitted when the value of \a descriptor
- is successfully changed to \a newValue. The change must have been caused
- by calling \l writeDescriptor().
+ is successfully changed to \a newValue. If the associated controller object is in the
+ \l {QLowEnergyController::CentralRole}{central} role, the change must have been caused
+ by calling \l writeDescriptor(). Otherwise, the signal is the result of a write request or
+ command from a GATT client to the respective descriptor.
\sa writeDescriptor()
*/
@@ -611,7 +621,13 @@ void QLowEnergyService::readCharacteristic(
}
/*!
- Writes \a newValue as value for the \a characteristic. If the operation is successful,
+ Writes \a newValue as value for the \a characteristic. The exact semantics depend on
+ the role that the associated controller object is in.
+
+ \b {Central role}
+
+ The call results in a write request or command to a remote peripheral.
+ If the operation is successful,
the \l characteristicWritten() signal is emitted; otherwise the \l CharacteristicWriteError
is set.
@@ -640,6 +656,21 @@ void QLowEnergyService::readCharacteristic(
characteristic may only support \l WriteWithResponse. If the hardware returns
with an error the \l CharacteristicWriteError is set.
+ \b {Peripheral role}
+
+ The call results in the value of the characteristic getting updated in the local database.
+
+ If a client is currently connected and it has enabled notifications or indications for
+ the characteristic, the respective information will be sent.
+ If a device has enabled notifications or indications for the characteristic and that device
+ is currently not connected, but a bond exists between it and the local device, then
+ the notification or indication will be sent on the next reconnection.
+
+ If there is a constraint on the length of the characteristic value and \a newValue
+ does not adhere to that constraint, the behavior is unspecified.
+
+ \note The \a mode argument is ignored in peripheral mode.
+
\sa QLowEnergyService::characteristicWritten(), QLowEnergyService::readCharacteristic()
*/
@@ -650,7 +681,10 @@ void QLowEnergyService::writeCharacteristic(
//TODO check behavior when writing to WriteSigned characteristic
Q_D(QLowEnergyService);
- if (d->controller == Q_NULLPTR || state() != ServiceDiscovered || !contains(characteristic)) {
+ if (d->controller == Q_NULLPTR
+ || (d->controller->role == QLowEnergyController::CentralRole
+ && state() != ServiceDiscovered)
+ || !contains(characteristic)) {
d->setError(QLowEnergyService::OperationError);
return;
}
@@ -727,9 +761,14 @@ void QLowEnergyService::readDescriptor(
}
/*!
- Writes \a newValue as value for \a descriptor. If the operation is successful,
- the \l descriptorWritten() signal is emitted; otherwise the \l DescriptorWriteError
- is emitted.
+ Writes \a newValue as value for \a descriptor. The exact semantics depend on
+ the role that the associated controller object is in.
+
+ \b {Central role}
+
+ A call to this function results in a write request to the remote device.
+ If the operation is successful, the \l descriptorWritten() signal is emitted; otherwise
+ the \l DescriptorWriteError is emitted.
All descriptor and characteristic requests towards the same remote device are
serialised. A queue is employed when issuing multiple write requests at the same time.
@@ -741,6 +780,11 @@ void QLowEnergyService::readDescriptor(
belongs to the service. If one of these conditions is
not true the \l QLowEnergyService::OperationError is set.
+ \b {Peripheral Role}
+
+ The value is written to the local service database. If the contents of \a newValue are not
+ valid for \a descriptor, the behavior is unspecified.
+
\sa descriptorWritten(), readDescriptor()
*/
void QLowEnergyService::writeDescriptor(const QLowEnergyDescriptor &descriptor,
@@ -748,7 +792,10 @@ void QLowEnergyService::writeDescriptor(const QLowEnergyDescriptor &descriptor,
{
Q_D(QLowEnergyService);
- if (d->controller == Q_NULLPTR || state() != ServiceDiscovered || !contains(descriptor)) {
+ if (d->controller == Q_NULLPTR
+ || (d->controller->role == QLowEnergyController::CentralRole
+ && state() != ServiceDiscovered)
+ || !contains(descriptor)) {
d->setError(QLowEnergyService::OperationError);
return;
}
diff --git a/src/bluetooth/qlowenergyservice.h b/src/bluetooth/qlowenergyservice.h
index 1dd35ab1..6e65aefd 100644
--- a/src/bluetooth/qlowenergyservice.h
+++ b/src/bluetooth/qlowenergyservice.h
@@ -69,7 +69,8 @@ public:
DiscoveryRequired, // we know start/end handle but nothing more
//TODO Rename DiscoveringServices -> DiscoveringDetails or DiscoveringService
DiscoveringServices,// discoverDetails() called and running
- ServiceDiscovered // all details have been synchronized
+ ServiceDiscovered, // all details have been synchronized
+ LocalService,
};
Q_ENUM(ServiceState)
diff --git a/src/bluetooth/qlowenergyserviceprivate.cpp b/src/bluetooth/qlowenergyserviceprivate.cpp
index 6f112017..a29d5648 100644
--- a/src/bluetooth/qlowenergyserviceprivate.cpp
+++ b/src/bluetooth/qlowenergyserviceprivate.cpp
@@ -33,6 +33,8 @@
#include "qlowenergyserviceprivate_p.h"
+#include "qlowenergycontroller_p.h"
+
QT_BEGIN_NAMESPACE
QLowEnergyServicePrivate::QLowEnergyServicePrivate(QObject *parent) :
diff --git a/src/bluetooth/qlowenergyserviceprivate_p.h b/src/bluetooth/qlowenergyserviceprivate_p.h
index fc2276e6..c17932cf 100644
--- a/src/bluetooth/qlowenergyserviceprivate_p.h
+++ b/src/bluetooth/qlowenergyserviceprivate_p.h
@@ -51,10 +51,10 @@
#include <QtBluetooth/QLowEnergyService>
#include <QtBluetooth/QLowEnergyCharacteristic>
-#include "qlowenergycontroller_p.h"
-
QT_BEGIN_NAMESPACE
+class QLowEnergyControllerPrivate;
+
class QLowEnergyServicePrivate : public QObject
{
Q_OBJECT