Merge PR #383: Nuki: Add support for Nuki 2.0

This commit is contained in:
Jenkins nymea 2021-04-28 13:54:34 +02:00
commit 61f320a26c
13 changed files with 297 additions and 214 deletions

View File

@ -72,6 +72,17 @@ QList<BluetoothGattDescriptor *> BluetoothGattCharacteristic::descriptors() cons
return m_descriptors;
}
BluetoothGattDescriptor *BluetoothGattCharacteristic::getDescriptor(const QBluetoothUuid &desciptorUuid) const
{
foreach (BluetoothGattDescriptor *descriptor, m_descriptors) {
if (descriptor->uuid() == desciptorUuid) {
return descriptor;
}
}
return nullptr;
}
BluetoothGattCharacteristic::BluetoothGattCharacteristic(const QDBusObjectPath &path, const QVariantMap &properties, QObject *parent) :
QObject(parent),
m_path(path),
@ -325,7 +336,7 @@ QDebug operator<<(QDebug debug, BluetoothGattCharacteristic *characteristic)
debug.noquote().nospace() << " B";
if (characteristic->properties().testFlag(BluetoothGattCharacteristic::Read))
debug.noquote().nospace() << " R ";
debug.noquote().nospace() << " R";
if (characteristic->properties().testFlag(BluetoothGattCharacteristic::WriteNoResponse))
debug.noquote().nospace() << " WNR";

View File

@ -82,7 +82,7 @@ public:
Properties properties() const;
QByteArray value() const;
QList<BluetoothGattDescriptor *> descriptors() const;
BluetoothGattDescriptor *getDescriptor(const QBluetoothUuid &desciptorUuid) const;
private:
explicit BluetoothGattCharacteristic(const QDBusObjectPath &path, const QVariantMap &properties, QObject *parent = 0);

View File

@ -75,10 +75,17 @@ BluetoothGattDescriptor::BluetoothGattDescriptor(const QDBusObjectPath &path, co
QDBusConnection::systemBus().connect(orgBluez, m_path.path(), "org.freedesktop.DBus.Properties", "PropertiesChanged", this, SLOT(onPropertiesChanged(QString,QVariantMap,QStringList)));
processProperties(properties);
QDBusPendingCall readingCall = m_descriptorInterface->asyncCall("GetAll", QVariantMap());
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(readingCall, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [=](){
qCDebug(dcBluez()) << "Get descriptor properties finished";
});
}
void BluetoothGattDescriptor::processProperties(const QVariantMap &properties)
{
qCDebug(dcBluez()) << "Descriptor properties" << properties;
foreach (const QString &propertyName, properties.keys()) {
if (propertyName == "UUID") {
m_uuid = QBluetoothUuid(properties.value(propertyName).toString());
@ -151,11 +158,11 @@ void BluetoothGattDescriptor::onReadingFinished(QDBusPendingCallWatcher *call)
void BluetoothGattDescriptor::onWritingFinished(QDBusPendingCallWatcher *call)
{
QByteArray value = m_asyncWrites.take(call);
QDBusPendingReply<void> reply = *call;
if (reply.isError()) {
qCWarning(dcBluez()) << "Could not write descriptor" << m_uuid.toString() << reply.error().name() << reply.error().message();
} else {
QByteArray value = m_asyncWrites.take(call);
qCDebug(dcBluez()) << "Async descriptor writing finished for" << m_uuid.toString() << value;
emit writingFinished(value);
}

View File

@ -181,6 +181,7 @@ void BluetoothManager::processInterfaceList(const QDBusObjectPath &objectPath, c
QDBusObjectPath serviceObjectPath = qvariant_cast<QDBusObjectPath>(properties.value("Service"));
BluetoothGattService *service = findService(serviceObjectPath);
if (service)
qCDebug(dcBluez()) << "Add characteristic" << serviceObjectPath.path() << properties << service;
service->addCharacteristicInternally(objectPath, properties);
}
@ -196,6 +197,7 @@ void BluetoothManager::processInterfaceList(const QDBusObjectPath &objectPath, c
QDBusObjectPath characterisitcObjectPath = qvariant_cast<QDBusObjectPath>(properties.value("Characteristic"));
BluetoothGattCharacteristic *characteristic = findCharacteristic(characterisitcObjectPath);
if (characteristic)
qCDebug(dcBluez()) << "Add descriptor" << characterisitcObjectPath.path() << properties << characteristic;
characteristic->addDescriptorInternally(objectPath, properties);
}

View File

@ -129,7 +129,6 @@ void IntegrationPluginNuki::discoverThings(ThingDiscoveryInfo *info)
m_bluetoothAdapter->startDiscovering();
QTimer::singleShot(5000, info, [this, info]() { onBluetoothDiscoveryFinished(info); });
}
void IntegrationPluginNuki::startPairing(ThingPairingInfo *info)
@ -159,15 +158,56 @@ void IntegrationPluginNuki::confirmPairing(ThingPairingInfo *info, const QString
return info->finish(Thing::ThingErrorThingNotFound);
}
// Create a temporary nuki for authentication
BluetoothDevice *bluetoothDevice = m_bluetoothAdapter->getDevice(address);
m_asyncSetupNuki = new Nuki(nullptr, bluetoothDevice, this);
connect(m_asyncSetupNuki, &Nuki::authenticationProcessFinished, this, &IntegrationPluginNuki::onNukiAuthenticationProcessFinished);
connect(m_asyncSetupNuki, &Nuki::availableChanged, this, &IntegrationPluginNuki::onAsyncSetupNukiAvailableChanged);
connect(m_asyncSetupNuki, &Nuki::authenticationProcessFinished, this, [=](const PairingTransactionId &pairingId, bool success){
if (m_asyncSetupNuki) {
qCDebug(dcNuki()) << "Deleting the temporary pairing device";
m_asyncSetupNuki->deleteLater();
m_asyncSetupNuki = nullptr;
}
if (!m_pairingInfo) {
qCWarning(dcNuki()) << "Authentication process finished, but have not valid pairing translaction id";
return;
}
if (m_pairingInfo->transactionId() != pairingId) {
qCWarning(dcNuki()) << "Authentication process finished, but have not valid pairing translaction id";
return;
}
m_pairingInfo->finish(success ? Thing::ThingErrorNoError : Thing::ThingErrorHardwareFailure);
m_pairingInfo = nullptr;
});
connect(m_asyncSetupNuki, &Nuki::availableChanged, this, [=](bool available){
// Remove possibly running Nuki setup devices on disconnected
if (m_asyncSetupNuki && !available) {
qCDebug(dcNuki()) << "Deleting the temporary pairing device";
m_asyncSetupNuki->deleteLater();
m_asyncSetupNuki = nullptr;
// Lost connection during authentication
if (m_pairingInfo) {
qCWarning(dcNuki()) << "Device disconnected during pairing.";
m_pairingInfo->finish(Thing::ThingErrorHardwareFailure);
m_pairingInfo = nullptr;
}
}
});
m_asyncSetupNuki->startAuthenticationProcess(info->transactionId());
m_pairingInfo = info;
connect(info, &ThingPairingInfo::destroyed, this, [this] { m_pairingInfo = nullptr; });
m_asyncSetupNuki->startAuthenticationProcess(info->transactionId());
connect(info, &ThingPairingInfo::destroyed, this, [this] {
m_pairingInfo = nullptr;
if (m_asyncSetupNuki) {
qCDebug(dcNuki()) << "Deleting the temporary pairing device";
m_asyncSetupNuki->deleteLater();
m_asyncSetupNuki = nullptr;
}
});
}
void IntegrationPluginNuki::postSetupThing(Thing *thing)
@ -225,7 +265,7 @@ void IntegrationPluginNuki::thingRemoved(Thing *thing)
Nuki *nuki = m_nukiDevices.key(thing);
nuki->clearSettings();
// FIXME: deauthenticate nymea from nuki thing
// TODO: deauthenticate nymea from nuki
qCDebug(dcNuki()) << "Delete pairing information from bluez" << nuki->bluetoothDevice();
m_bluetoothAdapter->removeDevice(nuki->bluetoothDevice()->address());
@ -275,8 +315,9 @@ void IntegrationPluginNuki::onBluetoothDiscoveryFinished(ThingDiscoveryInfo *inf
m_bluetoothAdapter->stopDiscovering();
foreach (BluetoothDevice *thing, m_bluetoothAdapter->devices()) {
if (!bluetoothDeviceAlreadyAdded(thing->address()) && thing->name().contains("Nuki")) {
ThingDescriptor descriptor(nukiThingClassId, "Nuki", thing->address().toString());
qCDebug(dcNuki()) << "Found bluetooth device" << thing->name() << thing->address().toString();
if (!bluetoothDeviceAlreadyAdded(thing->address()) && thing->name().toLower().contains("nuki")) {
qCDebug(dcNuki()) << "Found nuki smart lock which has not been added yet";
// Get serial number from name
QString serialNumber;
@ -287,6 +328,7 @@ void IntegrationPluginNuki::onBluetoothDiscoveryFinished(ThingDiscoveryInfo *inf
qCWarning(dcNuki()) << "Could not read serial number from bluetooth thing name" << thing->name();
}
ThingDescriptor descriptor(nukiThingClassId, "Nuki", thing->address().toString());
ParamList params;
params.append(Param(nukiThingNameParamTypeId, thing->name()));
params.append(Param(nukiThingMacParamTypeId, thing->address().toString()));
@ -298,35 +340,3 @@ void IntegrationPluginNuki::onBluetoothDiscoveryFinished(ThingDiscoveryInfo *inf
info->finish(Thing::ThingErrorNoError);
}
void IntegrationPluginNuki::onAsyncSetupNukiAvailableChanged(bool available)
{
// Remove possibly running Nuki setup devices on disconnected
if (!available && m_asyncSetupNuki) {
qCDebug(dcNuki()) << "Deleting the temporary pairing device";
m_asyncSetupNuki->deleteLater();
m_asyncSetupNuki = nullptr;
}
}
void IntegrationPluginNuki::onNukiAuthenticationProcessFinished(const PairingTransactionId &pairingTransactionId, bool success)
{
if (m_asyncSetupNuki) {
qCDebug(dcNuki()) << "Deleting the temporary pairing device";
m_asyncSetupNuki->deleteLater();
m_asyncSetupNuki = nullptr;
}
if (!m_pairingInfo) {
qCWarning(dcNuki()) << "Authentication process finished, but have not valid pairing translaction id";
return;
}
if (m_pairingInfo->transactionId() != pairingTransactionId) {
qCWarning(dcNuki()) << "Authentication process finished, but have not valid pairing translaction id";
return;
}
m_pairingInfo->finish(success ? Thing::ThingErrorNoError : Thing::ThingErrorHardwareFailure);
m_pairingInfo = nullptr;
}

View File

@ -76,10 +76,6 @@ private slots:
void onBluetoothEnabledChanged(const bool &enabled);
void onBluetoothDiscoveryFinished(ThingDiscoveryInfo *info);
void onAsyncSetupNukiAvailableChanged(bool available);
void onNukiAuthenticationProcessFinished(const PairingTransactionId &pairingTransactionId, bool success);
};
#endif // INTEGRATIONPLUGINNUKI_H

View File

@ -37,13 +37,6 @@
#include <QDataStream>
#include <QTimer>
static QBluetoothUuid initializationServiceUuid = QBluetoothUuid(QUuid("a92ee000-5501-11e4-916c-0800200c9a66"));
static QBluetoothUuid pairingServiceUuid = QBluetoothUuid(QUuid("a92ee100-5501-11e4-916c-0800200c9a66"));
static QBluetoothUuid pairingDataCharacteristicUuid = QBluetoothUuid(QUuid("a92ee101-5501-11e4-916c-0800200c9a66"));
static QBluetoothUuid keyturnerServiceUuid = QBluetoothUuid(QUuid("a92ee200-5501-11e4-916c-0800200c9a66"));
static QBluetoothUuid keyturnerDataCharacteristicUuid = QBluetoothUuid(QUuid("a92ee201-5501-11e4-916c-0800200c9a66"));
static QBluetoothUuid keyturnerUserDataCharacteristicUuid = QBluetoothUuid(QUuid("a92ee202-5501-11e4-916c-0800200c9a66"));
Nuki::Nuki(Thing *thing, BluetoothDevice *bluetoothDevice, QObject *parent) :
QObject(parent),
m_thing(thing),
@ -150,9 +143,9 @@ void Nuki::printServices()
foreach (BluetoothGattService *service, m_bluetoothDevice->services()) {
qCDebug(dcNuki()) << service;
foreach (BluetoothGattCharacteristic *characteristic, service->characteristics()) {
qCDebug(dcNuki()) << " " << characteristic->chararcteristicName() << characteristic->uuid().toString();
qCDebug(dcNuki()) << " " << characteristic;
foreach (BluetoothGattDescriptor *descriptor, characteristic->descriptors()) {
qCDebug(dcNuki()) << " " << descriptor->name() << descriptor->uuid().toString();
qCDebug(dcNuki()) << " " << descriptor;
}
}
}
@ -160,6 +153,7 @@ void Nuki::printServices()
void Nuki::readDeviceInformationCharacteristics()
{
qCDebug(dcNuki()) << "Start reading device information";
m_initUuidsToRead.append(QBluetoothUuid::SerialNumberString);
m_initUuidsToRead.append(QBluetoothUuid::HardwareRevisionString);
m_initUuidsToRead.append(QBluetoothUuid::FirmwareRevisionString);
@ -202,6 +196,17 @@ void Nuki::executeCurrentAction()
}
}
bool Nuki::enableNotificationsIndications(BluetoothGattCharacteristic *characteristic)
{
qCDebug(dcNuki()) << "Enable notifications on" << characteristic;
if (!characteristic->startNotifications()) {
qCDebug(dcNuki()) << "Failed to start notifications on" << characteristic;
return false;
}
return true;
}
void Nuki::onBluetoothDeviceStateChanged(const BluetoothDevice::State &state)
{
qCDebug(dcNuki()) << m_bluetoothDevice << "state changed --> " << state;
@ -412,12 +417,12 @@ bool Nuki::init()
return false;
}
if (!m_bluetoothDevice->hasService(pairingServiceUuid)) {
if (!m_bluetoothDevice->hasService(pairingServiceUuid())) {
qCWarning(dcNuki()) << "Could not find pairing service on device" << m_bluetoothDevice;
return false;
}
if (!m_bluetoothDevice->hasService(keyturnerServiceUuid)) {
if (!m_bluetoothDevice->hasService(keyturnerServiceUuid())) {
qCWarning(dcNuki()) << "Could not find key turner service on device" << m_bluetoothDevice;
return false;
}
@ -428,34 +433,41 @@ bool Nuki::init()
connect(m_deviceInformationService, &BluetoothGattService::characteristicReadFinished, this, &Nuki::onDeviceInfoCharacteristicReadFinished);
// Keyturner service
m_keyturnerService = m_bluetoothDevice->getService(keyturnerServiceUuid);
if (!m_keyturnerService->hasCharacteristic(keyturnerUserDataCharacteristicUuid)) {
m_keyturnerService = m_bluetoothDevice->getService(keyturnerServiceUuid());
if (!m_keyturnerService->hasCharacteristic(keyturnerUserDataCharacteristicUuid())) {
qCWarning(dcNuki()) << "Could not find user data characteristc on device" << m_bluetoothDevice;
return false;
}
if (!m_keyturnerService->hasCharacteristic(keyturnerDataCharacteristicUuid)) {
// Set key turner characteristics for data and user data
if (!m_keyturnerService->hasCharacteristic(keyturnerDataCharacteristicUuid())) {
qCWarning(dcNuki()) << "Could not find data characteristc on device" << m_bluetoothDevice;
return false;
}
m_keyturnerUserDataCharacteristic = m_keyturnerService->getCharacteristic(keyturnerUserDataCharacteristicUuid);
if (!m_keyturnerUserDataCharacteristic->startNotifications()) {
qCWarning(dcNuki()) << "Could not enable notifications for user data characteristic.";
return false;
}
m_keyturnerDataCharacteristic = m_keyturnerService->getCharacteristic(keyturnerDataCharacteristicUuid);
if (!m_keyturnerDataCharacteristic->startNotifications()) {
qCWarning(dcNuki()) << "Could not enable notifications for data characteristic.";
// Enable notifications/indications
m_keyturnerUserDataCharacteristic = m_keyturnerService->getCharacteristic(keyturnerUserDataCharacteristicUuid());
if (!enableNotificationsIndications(m_keyturnerUserDataCharacteristic)) {
qCWarning(dcNuki()) << "Could not enable notifications/indications for user data characteristic.";
return false;
}
m_keyturnerDataCharacteristic = m_keyturnerService->getCharacteristic(keyturnerDataCharacteristicUuid());
if (!enableNotificationsIndications(m_keyturnerDataCharacteristic)) {
qCWarning(dcNuki()) << "Could not enable notifications/indications for key turner data characteristic.";
return false;
}
// Pairing service
m_pairingService = m_bluetoothDevice->getService(pairingServiceUuid);
if (!m_pairingService->hasCharacteristic(pairingDataCharacteristicUuid)) {
m_pairingService = m_bluetoothDevice->getService(pairingServiceUuid());
if (!m_pairingService->hasCharacteristic(pairingDataCharacteristicUuid())) {
qCWarning(dcNuki()) << "Could not find pairing data characteristc on device" << m_bluetoothDevice;
return false;
}
m_pairingDataCharacteristic = m_pairingService->getCharacteristic(pairingDataCharacteristicUuid);
if (!m_pairingDataCharacteristic->startNotifications()) {
m_pairingDataCharacteristic = m_pairingService->getCharacteristic(pairingDataCharacteristicUuid());
if (!enableNotificationsIndications(m_pairingDataCharacteristic)) {
qCWarning(dcNuki()) << "Could not enable notifications for pairing characteristic.";
return false;
}

View File

@ -50,7 +50,6 @@
class Nuki : public QObject
{
Q_OBJECT
public:
@ -78,6 +77,12 @@ public:
void clearSettings();
static QBluetoothUuid initializationServiceUuid() { return QBluetoothUuid(QUuid("a92ee000-5501-11e4-916c-0800200c9a66")); }
static QBluetoothUuid pairingServiceUuid() { return QBluetoothUuid(QUuid("a92ee100-5501-11e4-916c-0800200c9a66")); }
static QBluetoothUuid pairingDataCharacteristicUuid() { return QBluetoothUuid(QUuid("a92ee101-5501-11e4-916c-0800200c9a66")); }
static QBluetoothUuid keyturnerServiceUuid() { return QBluetoothUuid(QUuid("a92ee200-5501-11e4-916c-0800200c9a66")); }
static QBluetoothUuid keyturnerDataCharacteristicUuid() { return QBluetoothUuid(QUuid("a92ee201-5501-11e4-916c-0800200c9a66")); }
static QBluetoothUuid keyturnerUserDataCharacteristicUuid() { return QBluetoothUuid(QUuid("a92ee202-5501-11e4-916c-0800200c9a66")); }
private:
Thing *m_thing = nullptr;
@ -115,6 +120,7 @@ private:
void readDeviceInformationCharacteristics();
void executeCurrentAction();
bool enableNotificationsIndications(BluetoothGattCharacteristic *characteristic);
signals:
void availableChanged(bool available);

View File

@ -58,6 +58,7 @@ NukiAuthenticator::NukiAuthenticator(const QBluetoothHostInfo &hostInfo, Bluetoo
// Check if we have authentication data for this thing and set initial state
loadData();
if (isValid()) {
qCDebug(dcNuki()) << "Found valid authroization data for" << hostInfo.address().toString();
setState(AuthenticationStateAuthenticated);
} else {
setState(AuthenticationStateUnauthenticated);
@ -81,7 +82,7 @@ bool NukiAuthenticator::isValid() const
return !m_privateKey.isEmpty() &&
!m_publicKey.isEmpty() &&
!m_publicKeyNuki.isEmpty() &&
!m_authorizationId == 0 &&
m_authorizationId != 0 &&
!m_authorizationIdRawData.isEmpty() &&
!m_uuid.isEmpty();
}
@ -208,7 +209,6 @@ void NukiAuthenticator::setState(NukiAuthenticator::AuthenticationState state)
switch (m_state) {
case AuthenticationStateUnauthenticated:
resetExpectedData();
break;
case AuthenticationStateAuthenticated:
qCDebug(dcNuki()) << "Device" << m_hostInfo.address().toString() << "authenticated.";
@ -218,47 +218,37 @@ void NukiAuthenticator::setState(NukiAuthenticator::AuthenticationState state)
if (m_debug) qCDebug(dcNuki()) << " Authorization ID:" << NukiUtils::convertByteArrayToHexStringCompact(m_authorizationIdRawData) << m_authorizationId;
break;
case AuthenticationStateRequestPublicKey:
resetExpectedData(NukiUtils::CommandPublicKey, 2);
requestPublicKey();
break;
case AuthenticationStateGenerateKeyPair:
resetExpectedData();
generateKeyPair();
setState(AuthenticationStateSendPublicKey);
break;
case AuthenticationStateSendPublicKey:
resetExpectedData(NukiUtils::CommandChallenge, 2);
sendPublicKey();
setState(AuthenticationStateReadChallenge);
break;
case AuthenticationStateReadChallenge:
resetExpectedData(NukiUtils::CommandChallenge, 2);
break;
case AuthenticationStateAutorization:
sendAuthorizationAuthenticator();
setState(AuthenticationStateReadSecondChallenge);
break;
case AuthenticationStateReadSecondChallenge:
resetExpectedData(NukiUtils::CommandChallenge, 2);
break;
case AuthenticationStateAuthenticateData:
resetExpectedData();
sendAuthenticateData();
setState(AuthenticationStateAuthorizationId);
break;
case AuthenticationStateAuthorizationId:
resetExpectedData(NukiUtils::CommandAuthorizationId, 5);
break;
case AuthenticationStateAuthorizationIdConfirm:
resetExpectedData();
sendAuthoizationIdConfirm();
setState(AuthenticationStateStatus);
break;
case AuthenticationStateStatus:
resetExpectedData(NukiUtils::CommandStatus);
break;
case AuthenticationStateError:
resetExpectedData();
emit errorOccured(m_error);
emit authenticationProcessFinished(false);
break;
@ -268,14 +258,6 @@ void NukiAuthenticator::setState(NukiAuthenticator::AuthenticationState state)
}
}
void NukiAuthenticator::resetExpectedData(NukiUtils::Command command, int expectedCount)
{
m_currentReceivingCommand = command;
m_currentReceivingCurrentCount = 0;
m_currentReceivingExpectedCount = expectedCount;
m_currentReceivingData.clear();
}
bool NukiAuthenticator::createAuthenticator(const QByteArray content)
{
// Create shared key
@ -469,7 +451,6 @@ void NukiAuthenticator::loadData()
void NukiAuthenticator::onPairingDataCharacteristicChanged(const QByteArray &value)
{
if (m_debug) qCDebug(dcNuki()) << "Authenticator data received: <--" << NukiUtils::convertByteArrayToHexStringCompact(value);
// Process pairing characteristic data
QByteArray data = QByteArray(value);
@ -478,12 +459,9 @@ void NukiAuthenticator::onPairingDataCharacteristicChanged(const QByteArray &val
quint16 command;
stream >> command;
// Check if we are collecting data for multi part notification
if (m_currentReceivingCurrentCount > 0) {
command = m_currentReceivingCommand;
}
m_currentReceivingData = value;
if (m_debug) qCDebug(dcNuki()) << static_cast<NukiUtils::Command>(command);
if (m_debug) qCDebug(dcNuki()) << "Authenticator data received: <--" << static_cast<NukiUtils::Command>(command) << "|" << NukiUtils::convertByteArrayToHexStringCompact(value);
switch (command) {
case NukiUtils::CommandErrorReport:
@ -500,81 +478,70 @@ void NukiAuthenticator::onPairingDataCharacteristicChanged(const QByteArray &val
setState(AuthenticationStateError);
break;
case NukiUtils::CommandPublicKey:
m_currentReceivingCurrentCount++;
m_currentReceivingData.append(value);
if (m_currentReceivingCurrentCount == m_currentReceivingExpectedCount) {
if (!NukiUtils::validateMessageCrc(m_currentReceivingData)) {
qCWarning(dcNuki()) << "Invalid CRC CCITT value for public key message.";
// FIXME: check what to do if crc is invalid
}
qCDebug(dcNuki()) << "Authenticator: Nuki public key message received" << (m_debug ? NukiUtils::convertByteArrayToHexStringCompact(m_currentReceivingData) : "");
m_publicKeyNuki = m_currentReceivingData.mid(2, 32);
if (m_debug) qCDebug(dcNuki()) << "Authenticator: --> Nuki public key:" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKeyNuki);
setState(AuthenticationStateGenerateKeyPair);
if (!NukiUtils::validateMessageCrc(m_currentReceivingData)) {
qCWarning(dcNuki()) << "Invalid CRC CCITT value for public key message.";
// FIXME: check what to do if crc is invalid
}
qCDebug(dcNuki()) << "Authenticator: Nuki public key message received" << (m_debug ? NukiUtils::convertByteArrayToHexStringCompact(m_currentReceivingData) : "");
m_publicKeyNuki = m_currentReceivingData.mid(2, 32);
if (m_debug) qCDebug(dcNuki()) << "Authenticator: --> Nuki public key:" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKeyNuki);
setState(AuthenticationStateGenerateKeyPair);
break;
case NukiUtils::CommandChallenge:
m_currentReceivingCurrentCount++;
m_currentReceivingData.append(value);
if (m_currentReceivingCurrentCount == m_currentReceivingExpectedCount) {
qCDebug(dcNuki()) << "Authenticator: Nuki challenge message received" << (m_debug ? NukiUtils::convertByteArrayToHexStringCompact(m_currentReceivingData) : "");
if (!NukiUtils::validateMessageCrc(m_currentReceivingData)) {
qCWarning(dcNuki()) << "Invalid CRC CCITT value for challenge message.";
// FIXME: check what to do if crc is invalid
}
qCDebug(dcNuki()) << "Authenticator: Nuki challenge message received" << (m_debug ? NukiUtils::convertByteArrayToHexStringCompact(m_currentReceivingData) : "");
if (!NukiUtils::validateMessageCrc(m_currentReceivingData)) {
qCWarning(dcNuki()) << "Invalid CRC CCITT value for challenge message.";
// FIXME: check what to do if crc is invalid
}
m_nonceNuki = m_currentReceivingData.mid(2, 32);
if (m_debug) qCDebug(dcNuki()) << "Authenticator: --> Nuki nonce:" << NukiUtils::convertByteArrayToHexStringCompact(m_nonceNuki);
// Check if this was from the first challenge read or the second
if (m_state == AuthenticationStateReadChallenge) {
setState(AuthenticationStateAutorization);
} else if (m_state == AuthenticationStateReadSecondChallenge) {
setState(AuthenticationStateAuthenticateData);
} else {
qCWarning(dcNuki()) << "Received a challenge without expecting one.";
setState(AuthenticationStateError);
}
m_nonceNuki = m_currentReceivingData.mid(2, 32);
if (m_debug) qCDebug(dcNuki()) << "Authenticator: --> Nuki nonce:" << NukiUtils::convertByteArrayToHexStringCompact(m_nonceNuki);
// Check if this was from the first challenge read or the second
if (m_state == AuthenticationStateReadChallenge) {
setState(AuthenticationStateAutorization);
} else if (m_state == AuthenticationStateReadSecondChallenge) {
setState(AuthenticationStateAuthenticateData);
} else {
qCWarning(dcNuki()) << "Received a challenge without expecting one.";
setState(AuthenticationStateError);
}
break;
case NukiUtils::CommandAuthorizationId:
m_currentReceivingCurrentCount++;
m_currentReceivingData.append(value);
if (m_currentReceivingCurrentCount == m_currentReceivingExpectedCount) {
qCDebug(dcNuki()) << "Authenticator: Nuki authorization ID message received" << (m_debug ? NukiUtils::convertByteArrayToHexStringCompact(m_currentReceivingData) : "");
case NukiUtils::CommandAuthorizationId: {
qCDebug(dcNuki()) << "Authenticator: Nuki authorization ID message received" << (m_debug ? NukiUtils::convertByteArrayToHexStringCompact(m_currentReceivingData) : "");
if (!NukiUtils::validateMessageCrc(m_currentReceivingData)) {
qCWarning(dcNuki()) << "Invalid CRC CCITT value for challenge message.";
// FIXME: check what to do if crc is invalid
}
// Parse data
QByteArray message = m_currentReceivingData.mid(2, m_currentReceivingData.count() - 4);
QByteArray authenticator = message.left(32);
Q_ASSERT_X(authenticator.count() == 32, "data length", "Nuki nonce has not the correct length.");
// Read authorization ID
m_authorizationIdRawData = message.mid(32, 4);
QDataStream stream(&m_authorizationIdRawData, QIODevice::ReadOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream >> m_authorizationId;
m_uuid = message.mid(36, 16);
Q_ASSERT_X(m_uuid.count() == 16, "data length", "UUIS has not the correct length.");
m_nonceNuki = message.mid(52, 32);
Q_ASSERT_X(m_nonceNuki.count() == 32, "data length", "Nuki nonce has not the correct length.");
if (m_debug) qCDebug(dcNuki()) << " Full message :" << NukiUtils::convertByteArrayToHexStringCompact(message);
if (m_debug) qCDebug(dcNuki()) << " Authenticator :" << NukiUtils::convertByteArrayToHexStringCompact(authenticator);
if (m_debug) qCDebug(dcNuki()) << " Authorization ID:" << NukiUtils::convertByteArrayToHexStringCompact(m_authorizationIdRawData) << m_authorizationId;
if (m_debug) qCDebug(dcNuki()) << " UUID data :" << NukiUtils::convertByteArrayToHexStringCompact(m_uuid);
if (m_debug) qCDebug(dcNuki()) << " Nuki nonce :" << NukiUtils::convertByteArrayToHexStringCompact(m_nonceNuki);
setState(AuthenticationStateAuthorizationIdConfirm);
if (!NukiUtils::validateMessageCrc(m_currentReceivingData)) {
qCWarning(dcNuki()) << "Invalid CRC CCITT value for challenge message.";
// FIXME: check what to do if crc is invalid
}
// Parse data
QByteArray message = m_currentReceivingData.mid(2, m_currentReceivingData.count() - 4);
QByteArray authenticator = message.left(32);
Q_ASSERT_X(authenticator.count() == 32, "data length", "Nuki nonce has not the correct length.");
// Read authorization ID
m_authorizationIdRawData = message.mid(32, 4);
QDataStream stream(&m_authorizationIdRawData, QIODevice::ReadOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream >> m_authorizationId;
m_uuid = message.mid(36, 16);
Q_ASSERT_X(m_uuid.count() == 16, "data length", "UUID has not the correct length.");
m_nonceNuki = message.mid(52, 32);
Q_ASSERT_X(m_nonceNuki.count() == 32, "data length", "Nuki nonce has not the correct length.");
if (m_debug) qCDebug(dcNuki()) << " Full message :" << NukiUtils::convertByteArrayToHexStringCompact(message);
if (m_debug) qCDebug(dcNuki()) << " Authenticator :" << NukiUtils::convertByteArrayToHexStringCompact(authenticator);
if (m_debug) qCDebug(dcNuki()) << " Authorization ID:" << NukiUtils::convertByteArrayToHexStringCompact(m_authorizationIdRawData) << m_authorizationId;
if (m_debug) qCDebug(dcNuki()) << " UUID data :" << NukiUtils::convertByteArrayToHexStringCompact(m_uuid);
if (m_debug) qCDebug(dcNuki()) << " Nuki nonce :" << NukiUtils::convertByteArrayToHexStringCompact(m_nonceNuki);
setState(AuthenticationStateAuthorizationIdConfirm);
break;
}
case NukiUtils::CommandStatus: {
quint8 status;
stream >> status;
@ -604,7 +571,6 @@ void NukiAuthenticator::onPairingDataCharacteristicChanged(const QByteArray &val
}
default:
qCWarning(dcNuki()) << "Authenticator: Unhandled command identifier for parining charateristic" << NukiUtils::convertUint16ToHexString(command);
resetExpectedData();
break;
}
}

View File

@ -110,7 +110,6 @@ private:
// State machine
void setState(AuthenticationState state);
void resetExpectedData(NukiUtils::Command command = NukiUtils::CommandRequestData, int expectedCount = 1);
// Helper methods
bool createAuthenticator(const QByteArray content);

View File

@ -88,6 +88,23 @@ bool NukiController::readLockState()
return true;
}
bool NukiController::readConfiguration()
{
if (m_state != NukiControllerStateIdle) {
// TODO: maybe queue commands
qCWarning(dcNuki()) << "Controller: Could not read lock state, Nuki is currenty busy";
return false;
}
if (!m_nukiAuthenticator->isValid()) {
qCWarning(dcNuki()) << "Invalid authenticator. Please authenticate the thing first.";
return false;
}
setState(NukiControllerStateReadingConfiguration);
return true;
}
bool NukiController::lock()
{
if (m_state != NukiControllerStateIdle) {
@ -154,6 +171,15 @@ void NukiController::setState(NukiController::NukiControllerState state)
case NukiControllerStateReadingLockStates:
sendReadLockStateRequest();
break;
case NukiControllerStateReadingConfigurationRequestChallange:
sendRequestChallengeRequest();
break;
case NukiControllerStateReadingConfigurationExecute:
sendReadConfigurationRequest();
setState(NukiControllerStateReadingConfiguration);
break;
case NukiControllerStateReadingConfiguration:
break;
case NukiControllerStateLockActionRequestChallange:
sendRequestChallengeRequest();
break;
@ -235,6 +261,12 @@ void NukiController::processNukiStatesData(const QByteArray &data)
emit nukiStatesChanged();
}
void NukiController::processNukiConfigData(const QByteArray &data)
{
qCDebug(dcNuki()) << "Processing config data from nuki" << data;
}
void NukiController::processNukiErrorReport(const QByteArray &data)
{
qint8 errorCode;
@ -280,6 +312,21 @@ void NukiController::processUserDataNotification(const QByteArray nonce, quint32
if (command == NukiUtils::CommandNukiStates) {
processNukiStatesData(payload);
emit readNukiStatesFinished(true);
// TODO: read configuration
setState(NukiControllerStateIdle);
return;
}
break;
case NukiControllerStateReadingConfigurationRequestChallange:
if (command == NukiUtils::CommandChallenge) {
m_nukiNonce = payload;
setState(NukiControllerStateReadingConfigurationExecute);
return;
}
break;
case NukiControllerStateReadingConfiguration:
if (command == NukiUtils::CommandConfig) {
processNukiConfigData(payload);
setState(NukiControllerStateIdle);
return;
}
@ -424,6 +471,45 @@ void NukiController::sendReadLockStateRequest()
m_userDataCharacteristic->writeCharacteristic(message);
}
void NukiController::sendReadConfigurationRequest()
{
qCDebug(dcNuki()) << "Controller: Reading configurations";
// Create data for encryption
QByteArray payload;
QDataStream stream(&payload, QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << static_cast<quint16>(NukiUtils::CommandRequestConfig);
for (int i = 0; i < m_nukiNonce.length(); i++) {
stream << static_cast<quint8>(m_nukiNonce.at(i));
}
// Create unencrypted PDATA
QByteArray unencryptedMessage = NukiUtils::createRequestMessageForUnencryptedForEncryption(m_nukiAuthenticator->authorizationId(), NukiUtils::CommandRequestData, payload);
// Encrypt PDATA
QByteArray nonce = m_nukiAuthenticator->generateNonce(crypto_box_NONCEBYTES);
QByteArray encryptedMessage = m_nukiAuthenticator->encryptData(unencryptedMessage, nonce);
// Create ADATA
QByteArray header;
header.append(nonce);
header.append(m_nukiAuthenticator->authorizationIdRawData());
header.append(NukiUtils::converUint16ToByteArrayLittleEndian(static_cast<quint16>(encryptedMessage.length())));
// Message ADATA + PDATA
QByteArray message;
message.append(header);
message.append(encryptedMessage);
// Send data
qCDebug(dcNuki()) << "Controller: Sending get config request";
if (m_debug) qCDebug(dcNuki()) << " Nonce :" << NukiUtils::convertByteArrayToHexStringCompact(nonce);
if (m_debug) qCDebug(dcNuki()) << " Header :" << NukiUtils::convertByteArrayToHexStringCompact(header);
if (m_debug) qCDebug(dcNuki()) << "Controller: -->" << NukiUtils::convertByteArrayToHexStringCompact(message);
m_userDataCharacteristic->writeCharacteristic(message);
}
void NukiController::sendRequestChallengeRequest()
{
qCDebug(dcNuki()) << "Controller: Request challenge";
@ -508,47 +594,27 @@ void NukiController::onUserDataCharacteristicChanged(const QByteArray &value)
{
if (m_debug) qCDebug(dcNuki()) << "Controller: Data received: <--" << NukiUtils::convertByteArrayToHexStringCompact(value);
if (m_messageBufferCounter <= 0) {
// New data arrived
m_messageBuffer.append(value);
m_messageBufferCounter++;
} else {
// We are currently collecting
m_messageBuffer.append(value);
m_messageBufferCounter++;
m_messageBuffer = value;
// In the second buffer message is the complete message length
if (m_messageBufferCounter == 2) {
if (m_messageBuffer.count() < 30) {
qCWarning(dcNuki()) << "Controller: Cannot understand message. Rejecting.";
resetMessageBuffer();
return;
}
// Parse message length
// ADATA: 24 byte nonce, 4 byte autorization, 2 byte encrypted message length
m_messageBufferAData = m_messageBuffer.left(30);
m_messageBufferPData = m_messageBuffer.right(m_messageBuffer.count() - 30);
m_messageBufferNonce = m_messageBufferAData.left(24);
QByteArray messageInformation = m_messageBufferAData.right(6);
QDataStream stream(&messageInformation, QIODevice::ReadOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream >> m_messageBufferIdentifier >> m_messageBufferLength;
if (m_messageBufferPData.count() == m_messageBufferLength) {
processUserDataNotification(m_messageBufferNonce, m_messageBufferIdentifier, m_messageBufferPData);
resetMessageBuffer();
}
} else {
// We already know the message length and are still collecting p data
m_messageBufferPData.append(value);
if (m_messageBufferPData.count() == m_messageBufferLength) {
// Message finished
processUserDataNotification(m_messageBufferNonce, m_messageBufferIdentifier, m_messageBufferPData);
resetMessageBuffer();
}
}
if (m_messageBuffer.count() < 30) {
qCWarning(dcNuki()) << "Controller: Cannot understand message. Rejecting.";
resetMessageBuffer();
return;
}
// Parse message length
// ADATA: 24 byte nonce, 4 byte autorization, 2 byte encrypted message length
m_messageBufferAData = m_messageBuffer.left(30);
m_messageBufferPData = m_messageBuffer.right(m_messageBuffer.count() - 30);
m_messageBufferNonce = m_messageBufferAData.left(24);
QByteArray messageInformation = m_messageBufferAData.right(6);
QDataStream stream(&messageInformation, QIODevice::ReadOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream >> m_messageBufferIdentifier >> m_messageBufferLength;
if (m_messageBufferPData.count() == m_messageBufferLength) {
processUserDataNotification(m_messageBufferNonce, m_messageBufferIdentifier, m_messageBufferPData);
resetMessageBuffer();
}
}

View File

@ -49,6 +49,11 @@ public:
// Read state
NukiControllerStateReadingLockStates,
// Read configuration
NukiControllerStateReadingConfigurationRequestChallange,
NukiControllerStateReadingConfigurationExecute,
NukiControllerStateReadingConfiguration,
// Lock action
NukiControllerStateLockActionRequestChallange,
NukiControllerStateLockActionExecute,
@ -80,6 +85,7 @@ public:
// Actions
bool readLockState();
bool readConfiguration();
bool lock();
bool unlock();
bool unlatch();
@ -119,11 +125,13 @@ private:
// Data processors
void processNukiStatesData(const QByteArray &data);
void processNukiConfigData(const QByteArray &data);
void processNukiErrorReport(const QByteArray &data);
void processUserDataNotification(const QByteArray nonce, quint32 authorizationIdentifier, const QByteArray &privateData);
// State action methods
void sendReadLockStateRequest();
void sendReadConfigurationRequest();
void sendRequestChallengeRequest();
void sendLockActionRequest(NukiUtils::LockAction lockAction, quint8 flag = 0);

View File

@ -143,9 +143,9 @@ public:
CommandOpeningsClosingsSummary = 0x0010,
CommandBatteryReport = 0x0011,
CommandErrorReport = 0x0012,
CommandSetConG = 0x0013,
CommandRequestConG = 0x0014,
CommandConG = 0x0015,
CommandSetConfig = 0x0013,
CommandRequestConfig = 0x0014,
CommandConfig = 0x0015,
CommandSetSecurityPIN = 0x0019,
CommandRequestCalibration = 0x001A,
CommandRequestReboot = 0x001D,
@ -190,7 +190,7 @@ public:
// Message helper
static QByteArray createRequestMessageForUnencrypted(NukiUtils::Command command, const QByteArray &payload);
static QByteArray createRequestMessageForUnencryptedForEncryption(quint32 authenticationId, NukiUtils::Command command, const QByteArray &payload);
static QByteArray createRequestMessageForUnencryptedForEncryption(quint32 authenticationId, NukiUtils::Command command, const QByteArray &payload = QByteArray());
};