diff --git a/nuki/bluez/bluetoothgattcharacteristic.cpp b/nuki/bluez/bluetoothgattcharacteristic.cpp index 038f2485..422bd246 100644 --- a/nuki/bluez/bluetoothgattcharacteristic.cpp +++ b/nuki/bluez/bluetoothgattcharacteristic.cpp @@ -72,6 +72,17 @@ QList 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"; diff --git a/nuki/bluez/bluetoothgattcharacteristic.h b/nuki/bluez/bluetoothgattcharacteristic.h index d935a61c..23c3a5ac 100644 --- a/nuki/bluez/bluetoothgattcharacteristic.h +++ b/nuki/bluez/bluetoothgattcharacteristic.h @@ -82,7 +82,7 @@ public: Properties properties() const; QByteArray value() const; QList descriptors() const; - + BluetoothGattDescriptor *getDescriptor(const QBluetoothUuid &desciptorUuid) const; private: explicit BluetoothGattCharacteristic(const QDBusObjectPath &path, const QVariantMap &properties, QObject *parent = 0); diff --git a/nuki/bluez/bluetoothgattdescriptor.cpp b/nuki/bluez/bluetoothgattdescriptor.cpp index 69a6dc05..c50c1410 100644 --- a/nuki/bluez/bluetoothgattdescriptor.cpp +++ b/nuki/bluez/bluetoothgattdescriptor.cpp @@ -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 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); } diff --git a/nuki/bluez/bluetoothmanager.cpp b/nuki/bluez/bluetoothmanager.cpp index 7facc9d8..01d21df3 100644 --- a/nuki/bluez/bluetoothmanager.cpp +++ b/nuki/bluez/bluetoothmanager.cpp @@ -181,6 +181,7 @@ void BluetoothManager::processInterfaceList(const QDBusObjectPath &objectPath, c QDBusObjectPath serviceObjectPath = qvariant_cast(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(properties.value("Characteristic")); BluetoothGattCharacteristic *characteristic = findCharacteristic(characterisitcObjectPath); if (characteristic) + qCDebug(dcBluez()) << "Add descriptor" << characterisitcObjectPath.path() << properties << characteristic; characteristic->addDescriptorInternally(objectPath, properties); } diff --git a/nuki/integrationpluginnuki.cpp b/nuki/integrationpluginnuki.cpp index 203ddb34..d922a739 100644 --- a/nuki/integrationpluginnuki.cpp +++ b/nuki/integrationpluginnuki.cpp @@ -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; -} diff --git a/nuki/integrationpluginnuki.h b/nuki/integrationpluginnuki.h index 27e6a719..08dcf05d 100644 --- a/nuki/integrationpluginnuki.h +++ b/nuki/integrationpluginnuki.h @@ -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 diff --git a/nuki/nuki.cpp b/nuki/nuki.cpp index 7328ed85..6c7d7390 100644 --- a/nuki/nuki.cpp +++ b/nuki/nuki.cpp @@ -37,13 +37,6 @@ #include #include -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; } diff --git a/nuki/nuki.h b/nuki/nuki.h index 365f1e94..8d664260 100644 --- a/nuki/nuki.h +++ b/nuki/nuki.h @@ -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); diff --git a/nuki/nukiauthenticator.cpp b/nuki/nukiauthenticator.cpp index fed0ae8d..cbbd2a6f 100644 --- a/nuki/nukiauthenticator.cpp +++ b/nuki/nukiauthenticator.cpp @@ -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(command); + if (m_debug) qCDebug(dcNuki()) << "Authenticator data received: <--" << static_cast(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; } } diff --git a/nuki/nukiauthenticator.h b/nuki/nukiauthenticator.h index 679489bc..740d7e02 100644 --- a/nuki/nukiauthenticator.h +++ b/nuki/nukiauthenticator.h @@ -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); diff --git a/nuki/nukicontroller.cpp b/nuki/nukicontroller.cpp index 21ffde80..3bfd6265 100644 --- a/nuki/nukicontroller.cpp +++ b/nuki/nukicontroller.cpp @@ -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(NukiUtils::CommandRequestConfig); + for (int i = 0; i < m_nukiNonce.length(); i++) { + stream << static_cast(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(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(); + } + } diff --git a/nuki/nukicontroller.h b/nuki/nukicontroller.h index ebc4b37f..2414f60f 100644 --- a/nuki/nukicontroller.h +++ b/nuki/nukicontroller.h @@ -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); diff --git a/nuki/nukiutils.h b/nuki/nukiutils.h index 315c911e..1abf36c1 100644 --- a/nuki/nukiutils.h +++ b/nuki/nukiutils.h @@ -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()); };