From 38b765206c352cc88123a03025024b3e5e368a4a Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Wed, 2 Dec 2015 15:02:47 +0100 Subject: Bluetooth LE: Implement ATT access permissions. Change-Id: I456d083d45569ea8d61f0a659f72646d653143d1 Reviewed-by: Alex Blasche --- src/bluetooth/qbluetooth.cpp | 14 +++++ src/bluetooth/qbluetooth.h | 14 +++++ src/bluetooth/qlowenergycharacteristicdata.cpp | 49 ++++++++++++++- src/bluetooth/qlowenergycharacteristicdata.h | 8 ++- src/bluetooth/qlowenergycontroller_bluez.cpp | 64 +++++++++++++------ src/bluetooth/qlowenergycontroller_p.h | 3 +- src/bluetooth/qlowenergydescriptordata.cpp | 72 +++++++++++++++++++++- src/bluetooth/qlowenergydescriptordata.h | 10 ++- .../server/qlowenergycontroller-gattserver.cpp | 8 ++- .../test/tst_qlowenergycontroller-gattserver.cpp | 30 ++++++++- 10 files changed, 243 insertions(+), 29 deletions(-) diff --git a/src/bluetooth/qbluetooth.cpp b/src/bluetooth/qbluetooth.cpp index 7a1f42ea..03b08bb4 100644 --- a/src/bluetooth/qbluetooth.cpp +++ b/src/bluetooth/qbluetooth.cpp @@ -67,6 +67,20 @@ namespace QBluetooth { Simple Pairing from Bluetooth 2.1 or greater is required. Legacy pairing is not permitted. */ + +/*! + \enum QBluetooth::AttAccessConstraint + + This enum describes the possible requirements for reading or writing an ATT attribute. + + \value AttAuthorizationRequired + The client needs authorization from the ATT server to access the attribute. + \value AttAuthenticationRequired + The client needs to be authenticated to access the attribute. + \value AttEncryptionRequired + The attribute can only be accessed if the connection is encrypted. +*/ + } /*! diff --git a/src/bluetooth/qbluetooth.h b/src/bluetooth/qbluetooth.h index f448af02..1053b388 100644 --- a/src/bluetooth/qbluetooth.h +++ b/src/bluetooth/qbluetooth.h @@ -39,6 +39,10 @@ QT_BEGIN_NAMESPACE namespace QBluetooth { + +// TODO Qt 6: Merge these two enums? But note that ATT Authorization has no equivalent +// on the socket security level. + enum Security { NoSecurity = 0x00, Authorization = 0x01, @@ -49,6 +53,16 @@ enum Security { Q_DECLARE_FLAGS(SecurityFlags, Security) Q_DECLARE_OPERATORS_FOR_FLAGS(SecurityFlags) + +enum AttAccessConstraint { + AttAuthorizationRequired = 0x1, + AttAuthenticationRequired = 0x2, + AttEncryptionRequired = 0x4, +}; + +Q_DECLARE_FLAGS(AttAccessConstraints, AttAccessConstraint) +Q_DECLARE_OPERATORS_FOR_FLAGS(AttAccessConstraints) + } typedef quint16 QLowEnergyHandle; diff --git a/src/bluetooth/qlowenergycharacteristicdata.cpp b/src/bluetooth/qlowenergycharacteristicdata.cpp index a64af7a3..f11779a5 100644 --- a/src/bluetooth/qlowenergycharacteristicdata.cpp +++ b/src/bluetooth/qlowenergycharacteristicdata.cpp @@ -51,6 +51,8 @@ struct QLowEnergyCharacteristicDataPrivate : public QSharedData QLowEnergyCharacteristic::PropertyTypes properties; QList descriptors; QByteArray value; + QBluetooth::AttAccessConstraints readConstraints; + QBluetooth::AttAccessConstraints writeConstraints; }; /*! @@ -156,6 +158,44 @@ void QLowEnergyCharacteristicData::addDescriptor(const QLowEnergyDescriptorData qCWarning(QT_BT) << "not adding invalid descriptor to characteristic"; } +/*! + Specifies that clients need to fulfill \a constraints to read the value of this characteristic. + */ +void QLowEnergyCharacteristicData::setReadConstraints(QBluetooth::AttAccessConstraints constraints) +{ + d->readConstraints = constraints; +} + +/*! + Returns the constraints needed for a client to read the value of this characteristic. + If \l properties() does not include \l QLowEnergyCharacteristic::Read, this value is irrelevant. + By default, there are no read constraints. + */ +QBluetooth::AttAccessConstraints QLowEnergyCharacteristicData::readConstraints() const +{ + return d->readConstraints; +} + +/*! + Specifies that clients need to fulfill \a constraints to write the value of this characteristic. + */ +void QLowEnergyCharacteristicData::setWriteConstraints(QBluetooth::AttAccessConstraints constraints) +{ + d->writeConstraints = constraints; +} + +/*! + Returns the constraints needed for a client to write the value of this characteristic. + If \l properties() does not include either of \l QLowEnergyCharacteristic::Write, + \l QLowEnergyCharacteristic::WriteNoResponse and \l QLowEnergyCharacteristic::WriteSigned, + this value is irrelevant. + By default, there are no write constraints. + */ +QBluetooth::AttAccessConstraints QLowEnergyCharacteristicData::writeConstraints() const +{ + return d->writeConstraints; +} + /*! Returns true if and only if this characteristic is valid, that is, it has a non-null UUID. */ @@ -175,8 +215,13 @@ bool QLowEnergyCharacteristicData::isValid() const */ bool operator==(const QLowEnergyCharacteristicData &cd1, const QLowEnergyCharacteristicData &cd2) { - return cd1.d == cd2.d || (cd1.uuid() == cd2.uuid() && cd1.properties() == cd2.properties() - && cd1.descriptors() == cd2.descriptors() && cd1.value() == cd2.value()); + return cd1.d == cd2.d || ( + cd1.uuid() == cd2.uuid() + && cd1.properties() == cd2.properties() + && cd1.descriptors() == cd2.descriptors() + && cd1.value() == cd2.value() + && cd1.readConstraints() == cd2.readConstraints() + && cd1.writeConstraints() == cd2.writeConstraints()); } /*! diff --git a/src/bluetooth/qlowenergycharacteristicdata.h b/src/bluetooth/qlowenergycharacteristicdata.h index ac78af5e..dc99f002 100644 --- a/src/bluetooth/qlowenergycharacteristicdata.h +++ b/src/bluetooth/qlowenergycharacteristicdata.h @@ -64,12 +64,16 @@ public: void setDescriptors(const QList &descriptors); void addDescriptor(const QLowEnergyDescriptorData &descriptor); + void setReadConstraints(QBluetooth::AttAccessConstraints constraints); + QBluetooth::AttAccessConstraints readConstraints() const; + + void setWriteConstraints(QBluetooth::AttAccessConstraints constraints); + QBluetooth::AttAccessConstraints writeConstraints() const; + bool isValid() const; void swap(QLowEnergyCharacteristicData &other) Q_DECL_NOTHROW { qSwap(d, other.d); } - // TODO: authentication/authorization requirements (separate for reading and writing!) - private: QSharedDataPointer d; }; diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp index 461be068..a298ea25 100644 --- a/src/bluetooth/qlowenergycontroller_bluez.cpp +++ b/src/bluetooth/qlowenergycontroller_bluez.cpp @@ -132,6 +132,8 @@ QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ) +using namespace QBluetooth; + const int maxPrepareQueueSize = 1024; static inline QBluetoothUuid convert_uuid128(const quint128 *p) @@ -2493,6 +2495,8 @@ void QLowEnergyControllerPrivate::addToGenericAttributeList(const QLowEnergyServ attribute.groupEndHandle = attribute.handle; attribute.type = cd.uuid(); attribute.properties = cd.properties(); + attribute.readConstraints = cd.readConstraints(); + attribute.writeConstraints = cd.writeConstraints(); attribute.value = cd.value(); localAttributes[attribute.handle] = attribute; @@ -2500,7 +2504,33 @@ void QLowEnergyControllerPrivate::addToGenericAttributeList(const QLowEnergyServ attribute.handle = ++currentHandle; attribute.groupEndHandle = attribute.handle; attribute.type = dd.uuid(); - attribute.properties = QLowEnergyCharacteristic::Read; // TODO: The different descriptor types have different read/write capabilities. + attribute.properties = QLowEnergyCharacteristic::PropertyTypes(); + attribute.readConstraints = AttAccessConstraints(); + attribute.writeConstraints = AttAccessConstraints(); + // Spec v4.2, Vol. 3, Part G, 3.3.3.x + if (attribute.type == QBluetoothUuid::CharacteristicExtendedProperties + || attribute.type == QBluetoothUuid::CharacteristicPresentationFormat + || attribute.type == QBluetoothUuid::CharacteristicAggregateFormat) { + attribute.properties = QLowEnergyCharacteristic::Read; + } else if (attribute.type == QBluetoothUuid::ClientCharacteristicConfiguration + || attribute.type == QBluetoothUuid::ServerCharacteristicConfiguration) { + attribute.properties = QLowEnergyCharacteristic::Read + | QLowEnergyCharacteristic::Write + | QLowEnergyCharacteristic::WriteNoResponse + | QLowEnergyCharacteristic::WriteSigned; + attribute.writeConstraints = dd.writeConstraints(); + } else { + if (dd.isReadable()) + attribute.properties |= QLowEnergyCharacteristic::Read; + if (dd.isWritable()) { + attribute.properties |= QLowEnergyCharacteristic::Write; + attribute.properties |= QLowEnergyCharacteristic::WriteNoResponse; + attribute.properties |= QLowEnergyCharacteristic::WriteSigned; + } + attribute.readConstraints = dd.readConstraints(); + attribute.writeConstraints = dd.writeConstraints(); + } + attribute.value = dd.value(); localAttributes[attribute.handle] = attribute; } @@ -2556,27 +2586,23 @@ QVector QLowEnergyControllerPrivate::get int QLowEnergyControllerPrivate::checkPermissions(const Attribute &attr, QLowEnergyCharacteristic::PropertyType type) { - // TODO: Actual permission checks. - if (false) - return ATT_ERROR_INSUF_AUTHORIZATION; - if (false) + const bool isReadAccess = type == QLowEnergyCharacteristic::Read; + const bool isWriteAccess = type == QLowEnergyCharacteristic::Write + || type == QLowEnergyCharacteristic::WriteNoResponse + || type == QLowEnergyCharacteristic::WriteSigned; + Q_ASSERT(isReadAccess || isWriteAccess); + if (!(attr.properties & type)) + return isReadAccess ? ATT_ERROR_READ_NOT_PERM : ATT_ERROR_WRITE_NOT_PERM; + const AttAccessConstraints constraints = isReadAccess + ? attr.readConstraints : attr.writeConstraints; + if (constraints.testFlag(AttAuthorizationRequired)) + return ATT_ERROR_INSUF_AUTHORIZATION; // TODO: emit signal (and offer authorization function)? + if (constraints.testFlag(AttEncryptionRequired) && securityLevel() < BT_SECURITY_MEDIUM) + return ATT_ERROR_INSUF_ENCRYPTION; + if (constraints.testFlag(AttAuthenticationRequired) && securityLevel() < BT_SECURITY_HIGH) return ATT_ERROR_INSUF_AUTHENTICATION; if (false) return ATT_ERROR_INSUF_ENCR_KEY_SIZE; - if (false) - return ATT_ERROR_INSUF_ENCRYPTION; - if (!(attr.properties & type)) { - switch (type) { - case QLowEnergyCharacteristic::Read: - return ATT_ERROR_READ_NOT_PERM; - case QLowEnergyCharacteristic::Write: - case QLowEnergyCharacteristic::WriteSigned: - case QLowEnergyCharacteristic::WriteNoResponse: - return ATT_ERROR_WRITE_NOT_PERM; - default: - Q_ASSERT(false); // Cannot happen. - } - } return 0; } diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h index f18a0384..e693e84d 100644 --- a/src/bluetooth/qlowenergycontroller_p.h +++ b/src/bluetooth/qlowenergycontroller_p.h @@ -179,9 +179,10 @@ public: QLowEnergyHandle handle; QLowEnergyHandle groupEndHandle; QLowEnergyCharacteristic::PropertyTypes properties; + QBluetooth::AttAccessConstraints readConstraints; + QBluetooth::AttAccessConstraints writeConstraints; QBluetoothUuid type; QByteArray value; - // TODO: authentication/authorization requirements }; QVector localAttributes; diff --git a/src/bluetooth/qlowenergydescriptordata.cpp b/src/bluetooth/qlowenergydescriptordata.cpp index 40bb571c..db5ea238 100644 --- a/src/bluetooth/qlowenergydescriptordata.cpp +++ b/src/bluetooth/qlowenergydescriptordata.cpp @@ -39,8 +39,14 @@ QT_BEGIN_NAMESPACE struct QLowEnergyDescriptorDataPrivate : public QSharedData { + QLowEnergyDescriptorDataPrivate() : readable(true), writable(true) {} + QBluetoothUuid uuid; QByteArray value; + QBluetooth::AttAccessConstraints readConstraints; + QBluetooth::AttAccessConstraints writeConstraints; + bool readable; + bool writable; }; /*! @@ -53,6 +59,10 @@ struct QLowEnergyDescriptorDataPrivate : public QSharedData An object of this class provides a descriptor to be added to a \l QLowEnergyCharacteristicData object via \l QLowEnergyCharacteristicData::addDescriptor(). + \note The member functions related to access permissions are only applicable to those + types of descriptors for which the Bluetooth specification does not prescribe if + and how their values can be accessed. + \sa QLowEnergyCharacteristicData \sa QLowEnergyServiceData \sa QLowEnergyController::addService @@ -126,6 +136,60 @@ bool QLowEnergyDescriptorData::isValid() const return !uuid().isNull(); } +/*! + Specifies whether the value of this descriptor is \a readable and if so, under which + \a constraints. + \sa setWritePermissions() + */ +void QLowEnergyDescriptorData::setReadPermissions(bool readable, + QBluetooth::AttAccessConstraints constraints) +{ + d->readable = readable; + d->readConstraints = constraints; +} + +/*! Returns \c true if the value of this descriptor is readable and \c false otherwise. */ +bool QLowEnergyDescriptorData::isReadable() const +{ + return d->readable; +} + +/*! + Returns the constraints under which the value of this descriptor can be read. This value + is only relevant if \l isReadable() returns \c true. + */ +QBluetooth::AttAccessConstraints QLowEnergyDescriptorData::readConstraints() const +{ + return d->readConstraints; +} + +/*! + Specifies whether the value of this descriptor is \a writable and if so, under which + \a constraints. + \sa setReadPermissions() + */ +void QLowEnergyDescriptorData::setWritePermissions(bool writable, + QBluetooth::AttAccessConstraints constraints) +{ + d->writable = writable; + d->writeConstraints = constraints; +} + +/*! Returns \c true if the value of this descriptor is writable and \c false otherwise. */ +bool QLowEnergyDescriptorData::isWritable() const +{ + return d->writable; +} + +/*! + Returns the constraints under which the value of this descriptor can be written. This value + is only relevant if \l isWritable() returns \c true. + */ +QBluetooth::AttAccessConstraints QLowEnergyDescriptorData::writeConstraints() const +{ + return d->writeConstraints; +} + /*! \fn void QLowEnergyDescriptorData::swap(QLowEnergyDescriptorData &other) Swaps this object with \a other. @@ -137,7 +201,13 @@ bool QLowEnergyDescriptorData::isValid() const */ bool operator==(const QLowEnergyDescriptorData &d1, const QLowEnergyDescriptorData &d2) { - return d1.d == d2.d || (d1.uuid() == d2.uuid() && d1.value() == d2.value()); + return d1.d == d2.d || ( + d1.uuid() == d2.uuid() + && d1.value() == d2.value() + && d1.isReadable() == d2.isReadable() + && d1.isWritable() == d2.isWritable() + && d1.readConstraints() == d2.readConstraints() + && d1.writeConstraints() == d2.writeConstraints()); } /*! diff --git a/src/bluetooth/qlowenergydescriptordata.h b/src/bluetooth/qlowenergydescriptordata.h index 189b73e4..c46eb249 100644 --- a/src/bluetooth/qlowenergydescriptordata.h +++ b/src/bluetooth/qlowenergydescriptordata.h @@ -64,7 +64,15 @@ public: bool isValid() const; - // TODO: read permissions, write permissions, authentication/authorization (only applicable for some descriptors) + void setReadPermissions(bool readable, + QBluetooth::AttAccessConstraints constraints = QBluetooth::AttAccessConstraints()); + bool isReadable() const; + QBluetooth::AttAccessConstraints readConstraints() const; + + void setWritePermissions(bool writable, + QBluetooth::AttAccessConstraints constraints = QBluetooth::AttAccessConstraints()); + bool isWritable() const; + QBluetooth::AttAccessConstraints writeConstraints() const; void swap(QLowEnergyDescriptorData &other) Q_DECL_NOTHROW { qSwap(d, other.d); } diff --git a/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp b/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp index ddba0ddb..b1fc7256 100644 --- a/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp +++ b/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp @@ -100,7 +100,7 @@ void addGenericAccessService() charData.setUuid(QBluetoothUuid::Appearance); charData.setProperties(QLowEnergyCharacteristic::Read); QByteArray value(2, 0); - value[0] = -1; // 128 => Generic computer. + value[0] = 64; // Generic Phone charData.setValue(value); serviceData.addCharacteristic(charData); @@ -120,6 +120,12 @@ void addCustomService() charData.setValue(QByteArray(1024, 'x')); // Long value to test "Read Blob". serviceData.addCharacteristic(charData); + charData.setUuid(QBluetoothUuid(quint16(0x5001))); // Made up. + charData.setProperties(QLowEnergyCharacteristic::Read); + charData.setReadConstraints(QBluetooth::AttAuthorizationRequired); // To test read failure. + serviceData.addCharacteristic(charData); + charData.setValue("something"); + addService(serviceData); } diff --git a/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp b/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp index 12d0db17..25098273 100644 --- a/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp +++ b/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp @@ -46,6 +46,8 @@ #include +using namespace QBluetooth; + class TestQLowEnergyControllerGattServer : public QObject { Q_OBJECT @@ -213,7 +215,7 @@ void TestQLowEnergyControllerGattServer::initialServices() QCOMPARE(appearanceChar.descriptors().count(), 0); QCOMPARE(appearanceChar.properties(), QLowEnergyCharacteristic::Read); QByteArray appearanceValue(2, 0); - appearanceValue[0] = -1; + appearanceValue[0] = 64; QCOMPARE(appearanceChar.value(), appearanceValue); const QScopedPointer runningSpeedService( @@ -254,12 +256,24 @@ void TestQLowEnergyControllerGattServer::initialServices() QVERIFY(spy->wait(3000)); } QCOMPARE(customService->includedServices().count(), 0); - QCOMPARE(customService->characteristics().count(), 1); + QCOMPARE(customService->characteristics().count(), 2); QLowEnergyCharacteristic customChar = customService->characteristic(QBluetoothUuid(quint16(0x5000))); QVERIFY(customChar.isValid()); QCOMPARE(customChar.descriptors().count(), 0); QCOMPARE(customChar.value(), QByteArray(1024, 'x')); + + QLowEnergyCharacteristic customChar2 + = customService->characteristic(QBluetoothUuid(quint16(0x5001))); + QVERIFY(customChar2.isValid()); + QCOMPARE(customChar2.descriptors().count(), 0); + QCOMPARE(customChar2.value(), QByteArray()); // Was not readable due to authorization requirement. + + customService->writeCharacteristic(customChar, "whatever"); + spy.reset(new QSignalSpy(customService.data(), static_cast(&QLowEnergyService::error))); + QVERIFY(spy->wait(3000)); + QCOMPARE(customService->error(), QLowEnergyService::CharacteristicWriteError); } void TestQLowEnergyControllerGattServer::controllerType() @@ -282,6 +296,13 @@ void TestQLowEnergyControllerGattServer::serviceData() descData.setValue("xyz"); QCOMPARE(descData.value().constData(), "xyz"); + descData.setReadPermissions(true, AttAuthenticationRequired); + QCOMPARE(descData.isReadable(), true); + QCOMPARE(descData.readConstraints(), AttAuthenticationRequired); + + descData.setWritePermissions(false); + QCOMPARE(descData.isWritable(), false); + QLowEnergyDescriptorData descData2(QBluetoothUuid::ReportReference, "abc"); QVERIFY(descData2 != QLowEnergyDescriptorData()); QVERIFY(descData2.isValid()); @@ -304,6 +325,11 @@ void TestQLowEnergyControllerGattServer::serviceData() charData.setProperties(props); QCOMPARE(charData.properties(), props); + charData.setReadConstraints(AttEncryptionRequired); + QCOMPARE(charData.readConstraints(), AttEncryptionRequired); + charData.setWriteConstraints(AttAuthenticationRequired | AttAuthorizationRequired); + QCOMPARE(charData.writeConstraints(), AttAuthenticationRequired | AttAuthorizationRequired); + charData.setDescriptors(QList() << descData << descData2); QLowEnergyDescriptorData descData3(QBluetoothUuid::ExternalReportReference, "someval"); charData.addDescriptor(descData3); -- cgit v1.2.3