// SPDX-License-Identifier: GPL-3.0-or-later

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-plugins.
*
* nymea-plugins is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-plugins is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-plugins. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "nukiauthenticator.h"

#include "nukiutils.h"
#include "nymeasettings.h"
#include "extern-plugininfo.h"

extern "C" {
#include "sodium.h"
#include "sodium/crypto_box.h"
#include "sodium/crypto_secretbox.h"
}

#include <QtEndian>
#include <QSettings>
#include <QDataStream>

NukiAuthenticator::NukiAuthenticator(const QBluetoothHostInfo &hostInfo, BluetoothGattCharacteristic *pairingCharacteristic, QObject *parent) :
    QObject(parent),
    m_hostInfo(hostInfo),
    m_pairingCharacteristic(pairingCharacteristic)
{

#ifdef QT_DEBUG
    // Enable full debug messages containing sensible data for debug builds
    m_debug = true;
#endif

    // 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);
    }

    connect(m_pairingCharacteristic, &BluetoothGattCharacteristic::valueChanged, this, &NukiAuthenticator::onPairingDataCharacteristicChanged);
}

NukiUtils::ErrorCode NukiAuthenticator::error() const
{
    return m_error;
}

NukiAuthenticator::AuthenticationState NukiAuthenticator::state() const
{
    return m_state;
}

bool NukiAuthenticator::isValid() const
{
    return !m_privateKey.isEmpty() &&
            !m_publicKey.isEmpty() &&
            !m_publicKeyNuki.isEmpty() &&
            m_authorizationId != 0 &&
            !m_authorizationIdRawData.isEmpty() &&
            !m_uuid.isEmpty();
}

void NukiAuthenticator::clearSettings()
{
    QSettings setting(NymeaSettings::settingsPath() + "/plugin-nuki.conf", QSettings::IniFormat);
    setting.beginGroup(m_hostInfo.address().toString());
    setting.remove("");
    setting.endGroup();

    qCDebug(dcNuki()) << "Settings cleared for" << m_hostInfo.address().toString() << "in" << setting.fileName();
}

void NukiAuthenticator::startAuthenticationProcess()
{
    setState(AuthenticationStateRequestPublicKey);
}

quint32 NukiAuthenticator::authorizationId() const
{
    return m_authorizationId;
}

QByteArray NukiAuthenticator::authorizationIdRawData() const
{
    return m_authorizationIdRawData;
}

QByteArray NukiAuthenticator::encryptData(const QByteArray &data, const QByteArray &nonce)
{
    // Calculate shared key
    qCDebug(dcNuki()) << "Authenticator: Encrypt data";
    Q_ASSERT_X(nonce.length() == crypto_box_NONCEBYTES, "data length", "The nonce does not have the correct length.");

    /* Note: https://download.libsodium.org/doc/public-key_cryptography/authenticated_encryption.html
     *      unsigned char *c         The encrypted message (length of the data + crypto_box_MACBYTES)
     *      const unsigned char *m   The message to encrypt
     *      unsigned long long mlen  The length of the message to encrypt
     *      const unsigned char *n   The nonce (must also sent unencrypted)
     *      const unsigned char *pk  The public key of the Nuki
     *      const unsigned char *sk  The private key of nymea for this Nuki
     */

    unsigned char *encrypted = NULL;
    std::vector<unsigned char> realBuffer(crypto_box_MACBYTES + data.length());
    encrypted = &realBuffer[0];
    int result = crypto_box_easy(encrypted,
                                 reinterpret_cast<const unsigned char *>(data.data()),
                                 static_cast<unsigned long long>(data.length()),
                                 reinterpret_cast<const unsigned char *>(nonce.data()),
                                 reinterpret_cast<const unsigned char *>(m_publicKeyNuki.data()),
                                 reinterpret_cast<const unsigned char *>(m_privateKey.data()));

    if (result < 0) {
        qCWarning(dcNuki()) << "Could not encrypt data. Something went wrong";
        return QByteArray();
    }

    QByteArray encryptedData = QByteArray(reinterpret_cast<const char*>(encrypted), crypto_box_MACBYTES + data.length());

    if (m_debug) qCDebug(dcNuki()) << "    Private key     :" << NukiUtils::convertByteArrayToHexStringCompact(m_privateKey);
    if (m_debug) qCDebug(dcNuki()) << "    Public key      :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKey);
    if (m_debug) qCDebug(dcNuki()) << "    Nuki public key :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKeyNuki);
    if (m_debug) qCDebug(dcNuki()) << "    Unencrypted data:" << NukiUtils::convertByteArrayToHexStringCompact(data);
    if (m_debug) qCDebug(dcNuki()) << "    Encrypted data  :" << NukiUtils::convertByteArrayToHexStringCompact(encryptedData);

    return encryptedData;
}

QByteArray NukiAuthenticator::decryptData(const QByteArray &data, const QByteArray &nonce)
{
    qCDebug(dcNuki()) << "Authenticator: Decrypt data";
    Q_ASSERT_X(nonce.length() == crypto_box_NONCEBYTES, "data length", "The nonce does not have the correct length.");
    Q_ASSERT_X(static_cast<uint>(data.length()) >= crypto_box_MACBYTES, "data length", "The encrypted data is to short.");

    /* Note: https://download.libsodium.org/doc/public-key_cryptography/authenticated_encryption.html
     *      unsigned char *m         The decrypted message result
     *      const unsigned char *c   The message to decrypt / cyphertext (length of the encrypted data + crypto_box_MACBYTES)
     *      unsigned long long clen  The length of the message to decrypt
     *      const unsigned char *n   The nonce used while encryption (received in the unencrypted ADATA)
     *      const unsigned char *pk  The public key of the Nuki
     *      const unsigned char *sk  The private key of nymea for this Nuki
     */
    unsigned char *decrypted = NULL;
    std::vector<unsigned char> realBuffer(data.length() - crypto_box_MACBYTES);
    decrypted = &realBuffer[0];
    int result = crypto_box_open_easy(decrypted,
                                      reinterpret_cast<const unsigned char *>(data.data()),
                                      static_cast<unsigned long long>(data.length()),
                                      reinterpret_cast<const unsigned char *>(nonce.data()),
                                      reinterpret_cast<const unsigned char *>(m_publicKeyNuki.data()),
                                      reinterpret_cast<const unsigned char *>(m_privateKey.data()));

    if (result < 0) {
        qCWarning(dcNuki()) << "Could not decrypt data. Something went wrong";
        return QByteArray();
    }

    QByteArray decryptedData = QByteArray(reinterpret_cast<const char*>(decrypted), data.length() - crypto_box_MACBYTES);

    if (m_debug) qCDebug(dcNuki()) << "    Private key     :" << NukiUtils::convertByteArrayToHexStringCompact(m_privateKey);
    if (m_debug) qCDebug(dcNuki()) << "    Public key      :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKey);
    if (m_debug) qCDebug(dcNuki()) << "    Nuki public key :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKeyNuki);
    if (m_debug) qCDebug(dcNuki()) << "    Encrypted data  :" << NukiUtils::convertByteArrayToHexStringCompact(data);
    if (m_debug) qCDebug(dcNuki()) << "    Decrypted data  :" << NukiUtils::convertByteArrayToHexStringCompact(decryptedData);

    return decryptedData;
}

QByteArray NukiAuthenticator::generateNonce(int length) const
{
    unsigned char *nounce = NULL;
    std::vector<unsigned char> realBuffer(length);
    nounce = &realBuffer[0];
    randombytes_buf(nounce, length);
    return QByteArray(reinterpret_cast<const char *>(nounce), length);
}

void NukiAuthenticator::setState(NukiAuthenticator::AuthenticationState state)
{
    if (m_state == state)
        return;

    m_state = state;
    emit stateChanged(m_state);

    qCDebug(dcNuki()) << m_state;

    switch (m_state) {
    case AuthenticationStateUnauthenticated:
        break;
    case AuthenticationStateAuthenticated:
        qCDebug(dcNuki()) << "Device" << m_hostInfo.address().toString() << "authenticated.";
        if (m_debug) qCDebug(dcNuki()) << "    Private key     :" << NukiUtils::convertByteArrayToHexStringCompact(m_privateKey);
        if (m_debug) qCDebug(dcNuki()) << "    Public key      :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKey);
        if (m_debug) qCDebug(dcNuki()) << "    Nuki public key :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKeyNuki);
        if (m_debug) qCDebug(dcNuki()) << "    Authorization ID:" << NukiUtils::convertByteArrayToHexStringCompact(m_authorizationIdRawData) << m_authorizationId;
        break;
    case AuthenticationStateRequestPublicKey:
        requestPublicKey();
        break;
    case AuthenticationStateGenerateKeyPair:
        generateKeyPair();
        setState(AuthenticationStateSendPublicKey);
        break;
    case AuthenticationStateSendPublicKey:
        sendPublicKey();
        setState(AuthenticationStateReadChallenge);
        break;
    case AuthenticationStateReadChallenge:
        break;
    case AuthenticationStateAutorization:
        sendAuthorizationAuthenticator();
        setState(AuthenticationStateReadSecondChallenge);
        break;
    case AuthenticationStateReadSecondChallenge:
        break;
    case AuthenticationStateAuthenticateData:
        sendAuthenticateData();
        setState(AuthenticationStateAuthorizationId);
        break;
    case AuthenticationStateAuthorizationId:
        break;
    case AuthenticationStateAuthorizationIdConfirm:
        sendAuthoizationIdConfirm();
        setState(AuthenticationStateStatus);
        break;
    case AuthenticationStateStatus:
        break;
    case AuthenticationStateError:
        emit errorOccured(m_error);
        emit authenticationProcessFinished(false);
        break;
    default:
        qCWarning(dcNuki()) << "Authenticator: Unknown state.";
        break;
    }
}

bool NukiAuthenticator::createAuthenticator(const QByteArray content)
{
    // Create shared key
    qCDebug(dcNuki()) << "Authenticator: Calculate shared key";
    unsigned char sharedKey[crypto_box_BEFORENMBYTES];
    int result = crypto_box_beforenm(sharedKey, reinterpret_cast<const unsigned char *>(m_publicKeyNuki.data()), reinterpret_cast<const unsigned char *>(m_privateKey.data()));
    if (result < 0) {
        qCWarning(dcNuki()) << "Could not create shared key for autorization authenticator.";
        return false;
    }

    m_sharedKey = QByteArray(reinterpret_cast<const char*>(sharedKey), crypto_box_BEFORENMBYTES);
    Q_ASSERT_X(m_sharedKey.length() == 32, "data length", "The shared key does not have the correct length.");

    if (m_debug) qCDebug(dcNuki()) << "Authenticator: Calculate authenticator hash HMAC-SHA-256";
    if (m_debug) qCDebug(dcNuki()) << "    Shared key      :" << NukiUtils::convertByteArrayToHexStringCompact(m_sharedKey);
    if (m_debug) qCDebug(dcNuki()) << "    Nuki nonce      :" << NukiUtils::convertByteArrayToHexStringCompact(m_nonceNuki);

    // Calculate authenticator hash input for HMAC-SHA-256
    qCDebug(dcNuki()) << "Authenticator: Calculate authenticator data";
    unsigned char authenticator[crypto_auth_hmacsha256_BYTES];
    result = crypto_auth_hmacsha256(authenticator, reinterpret_cast<const unsigned char *>(content.data()), content.length(), reinterpret_cast<const unsigned char *>(m_sharedKey.data()));
    if (result < 0) {
        qCWarning(dcNuki()) << "Could not create authenticator hash for autorization authenticator.";
        return false;
    }

    m_authenticator = QByteArray(reinterpret_cast<const char*>(authenticator), crypto_auth_hmacsha256_BYTES);
    if (m_debug) qCDebug(dcNuki()) << "    Authenticator   :" << NukiUtils::convertByteArrayToHexStringCompact(m_authenticator);
    return true;
}

void NukiAuthenticator::requestPublicKey()
{
    qCDebug(dcNuki()) << "Authenticator: Request public key fom Nuki";

    QByteArray payload;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    QDataStream stream(&payload, QDataStream::WriteOnly);
#else
    QDataStream stream(&payload, QIODevice::WriteOnly);
#endif
    stream.setByteOrder(QDataStream::LittleEndian);
    stream << static_cast<quint16>(NukiUtils::CommandPublicKey);
    QByteArray data = NukiUtils::createRequestMessageForUnencrypted(NukiUtils::CommandRequestData, payload);
    if (m_debug) qCDebug(dcNuki()) << "-->" << NukiUtils::convertByteArrayToHexStringCompact(data);
    m_pairingCharacteristic->writeCharacteristic(data);
}

void NukiAuthenticator::sendPublicKey()
{
    qCDebug(dcNuki()) << "Authenticator: Send public key to Nuki";
    QByteArray data = NukiUtils::createRequestMessageForUnencrypted(NukiUtils::CommandPublicKey, m_publicKey);
    if (m_debug) qCDebug(dcNuki()) << "-->" << NukiUtils::convertByteArrayToHexStringCompact(data);
    m_pairingCharacteristic->writeCharacteristic(data);
}

void NukiAuthenticator::generateKeyPair()
{
    qCDebug(dcNuki()) << "Generate key pair";
    unsigned char publicKey[crypto_box_PUBLICKEYBYTES];
    unsigned char secretKey[crypto_box_SECRETKEYBYTES];
    crypto_box_keypair(publicKey, secretKey);
    m_publicKey = QByteArray(reinterpret_cast<const char *>(publicKey), crypto_box_PUBLICKEYBYTES);
    m_privateKey = QByteArray(reinterpret_cast<const char *>(secretKey), crypto_box_SECRETKEYBYTES);

    if (m_debug) qCDebug(dcNuki()) << "    Private key     :" << NukiUtils::convertByteArrayToHexStringCompact(m_privateKey);
    if (m_debug) qCDebug(dcNuki()) << "    Public key      :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKey);
    if (m_debug) qCDebug(dcNuki()) << "    Nuki public key :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKeyNuki);
}

void NukiAuthenticator::sendAuthorizationAuthenticator()
{
    QByteArray valueR;
    valueR.append(m_publicKey);
    valueR.append(m_publicKeyNuki);
    valueR.append(m_nonceNuki);

    // Create authenticator and store it into m_authenticator
    if (!createAuthenticator(valueR)) {
        qCWarning(dcNuki()) << "Could not create authenticator hash HMAC-SHA-256";
        setState(AuthenticationStateError);
    }

    // Send the authenticator
    qCDebug(dcNuki()) << "Authenticator: Send authorization authenticator to Nuki";
    QByteArray message = NukiUtils::createRequestMessageForUnencrypted(NukiUtils::CommandAuthorizationAuthenticator, m_authenticator);
    if (m_debug) qCDebug(dcNuki()) << "-->" << NukiUtils::convertByteArrayToHexStringCompact(message);
    m_pairingCharacteristic->writeCharacteristic(message);
}

void NukiAuthenticator::sendAuthenticateData()
{
    // Calculate new nounce
    m_nonce = generateNonce();

    QByteArray content;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    QDataStream stream(&content, QDataStream::WriteOnly);
#else
    QDataStream stream(&content, QIODevice::WriteOnly);
#endif
    // Note: 0x00 = App, 0x01 = Bridge, 0x02 = Fob
    stream << static_cast<quint8>(0x01);
    // Note: app id (42)
    stream << static_cast<quint32>(0x002A);

    // Note: the name of the bridge in 32 bytes [ 0 0 0 ... n y m e a ]
    QByteArray name = QByteArray(27, '\0').append(QByteArray("nymea"));
    Q_ASSERT_X(name.length() == 32, "data length", "Name has not the correct length.");

    QByteArray valueR = content;
    valueR.append(name);
    valueR.append(m_nonce);
    valueR.append(m_nonceNuki);

    if (m_debug) qCDebug(dcNuki()) << "    Name            :" << qUtf8Printable(name) << NukiUtils::convertByteArrayToHexStringCompact(name);
    if (m_debug) qCDebug(dcNuki()) << "    Nonce           :" << NukiUtils::convertByteArrayToHexStringCompact(m_nonce);

    // Create authenticator and store it into m_authenticator
    if (!createAuthenticator(valueR)) {
        qCWarning(dcNuki()) << "Could not create authenticator hash HMAC-SHA-256";
        setState(AuthenticationStateError);
    }

    // Prepare message to send to Nuki
    QByteArray data;
    data.append(m_authenticator);
    data.append(content);
    data.append(name);
    data.append(m_nonce);

    qCDebug(dcNuki()) << "Authenticator: Send authentication data to Nuki";
    QByteArray message = NukiUtils::createRequestMessageForUnencrypted(NukiUtils::CommandAuthorizationData, data);
    if (m_debug) qCDebug(dcNuki()) << "-->" << NukiUtils::convertByteArrayToHexStringCompact(message);
    m_pairingCharacteristic->writeCharacteristic(message);
}

void NukiAuthenticator::sendAuthoizationIdConfirm()
{
    qCDebug(dcNuki()) << "Authenticator: Create data for authentication ID confirm";
    QByteArray valueR;
    valueR.append(m_authorizationIdRawData);
    valueR.append(m_nonceNuki);

    // Create authenticator and store it into m_authenticator
    if (!createAuthenticator(valueR)) {
        qCWarning(dcNuki()) << "Could not create authenticator hash HMAC-SHA-256";
        setState(AuthenticationStateError);
    }

    // Calculate new nounce
    m_nonce = generateNonce();

    if (m_debug) qCDebug(dcNuki()) << "    Nonce           :" << NukiUtils::convertByteArrayToHexStringCompact(m_nonce);
    if (m_debug) qCDebug(dcNuki()) << "    Nuki Nonce      :" << NukiUtils::convertByteArrayToHexStringCompact(m_nonceNuki);
    if (m_debug) qCDebug(dcNuki()) << "    Authorization ID:" << NukiUtils::convertByteArrayToHexStringCompact(m_authorizationIdRawData) << m_authorizationId;

    // Prepare message to send to Nuki
    QByteArray data;
    data.append(m_authenticator);
    data.append(m_authorizationIdRawData);

    qCDebug(dcNuki()) << "Authenticator: Send authentication ID confirm to Nuki";
    QByteArray message = NukiUtils::createRequestMessageForUnencrypted(NukiUtils::CommandAuthorizationIdConfirmation, data);
    if (m_debug) qCDebug(dcNuki()) << "-->" << NukiUtils::convertByteArrayToHexStringCompact(message);
    m_pairingCharacteristic->writeCharacteristic(message);
}

void NukiAuthenticator::saveData()
{
    QSettings setting(NymeaSettings::settingsPath() + "/plugin-nuki.conf", QSettings::IniFormat);
    setting.beginGroup(m_hostInfo.address().toString());
    setting.setValue("privateKey", m_privateKey);
    setting.setValue("publicKey", m_publicKey);
    setting.setValue("publicKeyNuki", m_publicKeyNuki);
    setting.setValue("authenticationIdRawData", m_authorizationIdRawData);
    setting.setValue("authenticationId", m_authorizationId);
    setting.setValue("uuid", m_uuid);
    setting.endGroup();

    qCDebug(dcNuki()) << "Authenticator: Settings saved to" << setting.fileName();
}

void NukiAuthenticator::loadData()
{
    QSettings setting(NymeaSettings::settingsPath() + "/plugin-nuki.conf", QSettings::IniFormat);
    setting.beginGroup(m_hostInfo.address().toString());
    m_privateKey = setting.value("privateKey", QByteArray()).toByteArray();
    m_publicKey = setting.value("publicKey", QByteArray()).toByteArray();
    m_publicKeyNuki = setting.value("publicKeyNuki", QByteArray()).toByteArray();
    m_authorizationIdRawData = setting.value("authenticationIdRawData", QByteArray()).toByteArray();
    m_authorizationId = static_cast<quint32>(setting.value("authenticationId", 0).toInt());
    m_uuid = setting.value("uuid", QByteArray()).toByteArray();
    setting.endGroup();

    qCDebug(dcNuki()) << "Authenticator: Settings loaded from" << setting.fileName();
}

void NukiAuthenticator::onPairingDataCharacteristicChanged(const QByteArray &value)
{

    // Process pairing characteristic data
    QByteArray data = QByteArray(value);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    QDataStream stream(&data, QDataStream::ReadOnly);
#else
    QDataStream stream(&data, QIODevice::ReadOnly);
#endif
    stream.setByteOrder(QDataStream::LittleEndian);
    quint16 command;
    stream >> command;

    m_currentReceivingData = value;

    if (m_debug) qCDebug(dcNuki()) << "Authenticator data received: <--" << static_cast<NukiUtils::Command>(command) << "|" << NukiUtils::convertByteArrayToHexStringCompact(value);

    switch (command) {
    case NukiUtils::CommandErrorReport:
        quint8 error;
        quint16 commandIdentifier;
        quint16 crc;
        stream >> error >> commandIdentifier >> crc;
        if (!NukiUtils::validateMessageCrc(data)) {
            qCWarning(dcNuki()) << "Invalid message";
            // FIXME: check what to do if crc is invalid
        }
        m_error = static_cast<NukiUtils::ErrorCode>(error);
        qCWarning(dcNuki()) << "Authenticator: Error for command" << static_cast<NukiUtils::Command>(commandIdentifier) << m_error;
        setState(AuthenticationStateError);
        break;
    case NukiUtils::CommandPublicKey:
        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:
        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);
        }
        break;
    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.length() - 4);
        QByteArray authenticator = message.left(32);
        Q_ASSERT_X(authenticator.length() == 32, "data length", "Nuki nonce has not the correct length.");

        // Read authorization ID
        m_authorizationIdRawData = message.mid(32, 4);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
        QDataStream stream(&m_authorizationIdRawData, QDataStream::ReadOnly);
#else
        QDataStream stream(&m_authorizationIdRawData, QIODevice::ReadOnly);
#endif
        stream.setByteOrder(QDataStream::LittleEndian);
        stream >> m_authorizationId;

        m_uuid = message.mid(36, 16);
        Q_ASSERT_X(m_uuid.length() == 16, "data length", "UUID has not the correct length.");

        m_nonceNuki = message.mid(52, 32);
        Q_ASSERT_X(m_nonceNuki.length() == 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;

        if (!NukiUtils::validateMessageCrc(data)) {
            qCWarning(dcNuki()) << "Invalid message";
            // FIXME: check what to do if crc is invalid
        }

        NukiUtils::StatusCode statusCode = static_cast<NukiUtils::StatusCode>(status);
        if (m_debug) qCDebug(dcNuki()) << statusCode;
        switch (statusCode) {
        case NukiUtils::StatusCodeAccepted:
            qCWarning(dcNuki()) << "The command was accepted, but not completed.";
            setState(AuthenticationStateError);
            break;
        case NukiUtils::StatusCodeCompeted:
            qCDebug(dcNuki()) << "Nuki authentication process finished successfully!";
            saveData();
            setState(AuthenticationStateAuthenticated);
            emit authenticationProcessFinished(true);
            break;
        default:
            break;
        }
        break;
    }
    default:
        qCWarning(dcNuki()) << "Authenticator: Unhandled command identifier for parining charateristic" << NukiUtils::convertUint16ToHexString(command);
        break;
    }
}
