Index: sources/cloudsync/CloudSyncController.cpp =================================================================== diff -u -r3b323bd6a1a03429c2321a889049de1c3b11302f -r176abff73696b975d62b0159265ca74d6f4fe54f --- sources/cloudsync/CloudSyncController.cpp (.../CloudSyncController.cpp) (revision 3b323bd6a1a03429c2321a889049de1c3b11302f) +++ sources/cloudsync/CloudSyncController.cpp (.../CloudSyncController.cpp) (revision 176abff73696b975d62b0159265ca74d6f4fe54f) @@ -1,1329 +1,1362 @@ -/*! - * - * Copyright (c) 2021-2024 Diality Inc. - All Rights Reserved. - * \copyright - * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN - * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. - * - * \file CloudSyncController.cpp - * \author (last) Behrouz NematiPour - * \date (last) 13-Mar-2024 - * \author (original) Behrouz NematiPour - * \date (original) 14-Oct-2021 - * - */ -#include "CloudSyncController.h" - -// Qt -#include - -// Project -#include "ApplicationController.h" -#include "DeviceController.h" -#include "FileHandler.h" -#include "TreatmentLog.h" -#include "crc.h" - -SINGLETON_DISABLE(CloudSyncController) - -/*! - * \brief CloudSyncController::CloudSyncController - * \details Constructor - * \param parent - QObject parent owner object. - * Qt handles the children destruction by their parent objects life-cycle. - */ -CloudSyncController::CloudSyncController(QObject *parent) : QObject(parent) { - testWatchBuffDate(); - startTimer(_interval); -} - -/*! - * \brief CloudSyncController initializer - */ -bool CloudSyncController::init() -{ - if ( _init ) return false; - _init = true; - - initConnections(); - LOG_DEBUG(tr("%1 Initialized").arg(metaObject()->className())); - - return true; -} - -/*! - * \brief CloudSyncController::init - * \details Initialized the Class by calling the init() method first - * And initializes the thread vThread by calling initThread - * on success init(). - * \param vThread - the thread - * \return returns the return value of the init() method - */ -bool CloudSyncController::init(QThread &vThread) -{ - if ( ! init() ) return false; - initThread(vThread); - return true; -} - -/*! - * \brief CloudSyncController::quit - * \details quits the class - * Calls quitThread - */ -void CloudSyncController::quit() -{ - // disabled coco begin validated: CloudSync termination is not correctly done in coco!!! - // it has been tested and works perfectly fine in normal run. - quitThread(); // validated -} -// disabled coco end - -/*! - * \brief CloudSyncController::doRegister - * \details Send the register device command. - */ -void CloudSyncController::doRegister() -{ - sendDeviceRegister(); -} - -/*! - * \brief CloudSyncController::initConnections - * \details Initializes the required signal/slot connection between this class and other objects - * to be able to communicate. - */ -void CloudSyncController::initConnections() -{ - if ( ! gDisableCloudSyncFailStop ) { - SINGLETON_DISABLE_CONNECT(didPOSTCloudSync) - } - - connect(&_ApplicationController , SIGNAL(didPOSTCloudSync(bool)), - this , SLOT( onPOSTCloudSync(bool))); - connect(&_ApplicationController , SIGNAL(didActionReceive (GuiActionType , const QVariantList &)), - this , SLOT( onActionReceive (GuiActionType , const QVariantList &))); - - connect(&_DeviceController , SIGNAL(didCryptSetupMount(bool)), - this , SLOT( onCryptSetupMount(bool))); - connect(&_DeviceController , SIGNAL(didPendingLog (const QString &, const QString &)), - this , SLOT( onPendingLog (const QString &, const QString &))); - connect(&_Logger , SIGNAL(didRetentionLogCS (quint8)), - this , SLOT( onRetentionLog (quint8))); - - connect(&_DeviceController , SIGNAL(didWatchFileChange (const QString &)), - this , SLOT( onWatchFileChange (const QString &))); - connect(&_DeviceController , SIGNAL(didFactoryReset (bool)), - this , SLOT( onFactoryReset (bool))); - connect(&_DeviceController , SIGNAL(didDecommissioning (bool)), - this , SLOT( onDecommissioning (bool))); - connect(&_TreatmentLog , SIGNAL(didPendingTxr (const QString &)), - this , SLOT( onPendingTxr (const QString &))); - connect(this , SIGNAL(didInitComplete ()), - this , SLOT( onInitComplete ()),Qt::QueuedConnection); // it has to be queued connection, don't remove it. -} - -/*! - * \brief CloudSyncController::initThread - * \details Moves this object into the thread vThread. - * And checks that this method is called from main thread. - * Also connects quitThread to CloudSync aboutToQuit. - * \param vThread - the thread - */ -void CloudSyncController::initThread(QThread &vThread) -{ - // runs in main thread - Q_ASSERT_X(QThread::currentThread() == qApp->thread(), __func__, "The Class initialization must be done in Main Thread" ); - _thread = &vThread; - _thread->setObjectName(QString("%1_Thread").arg(metaObject()->className())); - connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(quit())); - _thread->start(); - moveToThread(_thread); -} - -/*! - * \brief CloudSyncController::quitThread - * \details Moves this object to main thread to be handled by QCloudSync - * And to be destroyed there. - */ -void CloudSyncController::quitThread() -{ - // disabled coco begin validated: CloudSync termination is not correctly done in coco!!! - // it has been tested and works perfectly fine in normal run. - - if ( ! _thread ) return; - - // runs in thread - moveToThread(qApp->thread()); // validated -} -// disabled coco end - -/*! - * \brief CloudSyncController::timerEvent - * \details The timer event handler which currently is triggered on each second to check for the date change, - * Which caused the watched file change and needs to updated the watched list. - * The check-in (watch dog) also needs to be here. - */ -void CloudSyncController::timerEvent(QTimerEvent *) -{ - TIME_CALL(sendCheckIn(), _checkinIntervalSend); // call every x times/second - will be called on first call. - TIME_CALL(testCheckIn(), _checkinIntervalTest); // call every x times/second - will be called on first call. - testWatchBuffDate(); - testDeviceInfoWait(); -} - -/*! - * \brief CloudSyncController::event - * \details The override method of QObject event to handle the ThreadChange. - * \param vEvent - the QObject event - * \return true if the event e was recognized and processed - */ -bool CloudSyncController::event(QEvent *vEvent) { - if (vEvent->type() == QEvent::ThreadChange) { - emit didInitComplete(); - } - // Make sure the rest of events are handled - return QObject::event(vEvent); -} - -/*! - * \brief CloudSyncController::onInitComplete - * \details The slot to be called when the CloudSync initialization is complete - * Does nothing for now - */ -void CloudSyncController::onInitComplete() { - /* Development testability: - For testing if -C (gDisableCloudSyncFailStop) is used call the testReady - which is called - - when the encrypted partition is mounted - - a request for credential save received - testReady will set ok = true if -C (gDisableCloudSyncFailStop) is used. - */ - if ( gDisableCloudSyncFailStop ) testReady(); -} - -/*! - * \brief CloudSyncController::onPOSTCloudSync - * \details This the handler for the ApplicationController::didPOSTCloudSync(bool) - * \param vPass - will be true if the POST test of CloudSync passed and it is running. - */ -void CloudSyncController::onPOSTCloudSync(bool vPass) -{ - _postPass = vPass; -} - -/*! - * \brief CloudSyncController::onCryptSetupMount - * \details This the handler for the DeviceController::onCryptSetupMount() - */ -void CloudSyncController::onCryptSetupMount(bool vPass) -{ - if ( vPass ) { - testReady(); - } -} - -/*! - * \brief CloudSyncController::onFactoryReset - * \details this slot will be called when the DeviceController is done with the Factory Reset - * to let the UI request CS to do the Factory Reset and clean up all the Logs. - * \param vPass - Device controller factory reset was successful. - */ -void CloudSyncController::onFactoryReset(bool vPass) -{ - if ( vPass ) { - csFactoryReset(); - } -} - -/*! - * \brief CloudSyncController::onDecommissioning - * \details this slot will be called when the DeviceController is done with the Decommissioning - * to let the UI request CS to do the Decommissioning and clean up all the Tokens. - * \param vPass - Device controller Decommissioning was successful. - */ -void CloudSyncController::onDecommissioning(bool vPass) -{ - if ( vPass ) { - csDecommissioning(); - } -} - -/*! - * \brief CloudSyncController::onWatchFileChange - * \details This slot will be called when the Device Controller identifies any changes in the watched files. - * \param vFile - watched file - * \note The DeviceController will emit the signal on any watched file update, it's up to the CloudSyncController to filter the result. - */ -void CloudSyncController::onWatchFileChange(const QString &vFile) -{ - // TODO: It may improve the performance of the code to make it more specific. - // Meaning, Device controller has only one file watcher and it will call all the slots of all the classes watching for a file change. - // then ignore what is not important, but it may effect, the call queue and the threading performance. - // Later investigation. - if ( vFile != _date_out_File ) return; // ignore unwanted file updates. - QString content; - Storage::FileHandler::read(vFile, content); - sendUIResponse(content); -} - -/*! - * \brief CloudSyncController::addCSBuffWatch - * \details Adds a watcher on the CloudSync Application output file. - * \sa _out_File - */ -bool CloudSyncController::addCSBuffWatch() -{ - // this makeFolder function call investigated for permissions - // during setup the folder if does not exists will be created (root) - // the lockdown.sh script will set the permissions for it. - // on power cycle to normal operation the folder with the correct exists. - // *** NOTE: the makeFolder returns true if the folder already exists. *** - bool ok = Storage::FileHandler::makeFolder(_location); - QVariantList args {}; - Errors_Enum error = eError_Unknown; - if ( ! ok ) { error = eError_LogFolder; args = {{ _location }}; ok = false; goto lErr; } - _date_out_File = _location + // The location - _dateFormatted + _dateSeparator + _out_File; // The file name - // watching for the cloud sync output file buffer. - if ( ! QFileInfo(_date_out_File).exists() ) { - // if the file does not exists, send a check-in to the CS and wait for the response. - // by CS sending the response it will create the out file and next time this function with start watching the file. - // since this class has a one second timer set, next call is next second - // TODO: during this less that 1s UI will not see messages from CS, since the file was not there to watch. - sendCheckIn(); - // send device state to make the CloudSync send back a message to create the out buff with its user to own the file - return ok; - } - _DeviceController.doAddWatch(_date_out_File, false); - _isWatching = true; // when the watch is added then the flag sets until next time the date changes. - // since the buff files will be deleted on each power cycle, when the out buf is created it means the CloudSync is running. - // we emit the ApplicationController to check the post.log for the CloudSync status check. - return ok; - -lErr: - toLog(error, args); - return ok; -} - -/*! - * \brief CloudSyncController::interpret - * \details Checks the received buffer to make sure it has all the required parameters - * \param vIndex - * \return true if the buffer is correct. - */ -bool CloudSyncController::interpret(const QString &vContent, Message &vMessage) -{ - bool ok = true; - QVariantList args {}; - Errors_Enum error = eError_Unknown; - - QStringList lines; - QString buffer; - - int index = -1; - quint32 count = 0; - - Message message; - QStringList items; - quint32 id; - - if ( vContent.isEmpty() ) { error = eError_OutFileEmpty; ok = false; goto lErr; } - - lines = vContent.split('\n',Qt::SkipEmptyParts); - buffer = lines.last(); - // DEBUG: LOG_DEBUG(QString("CS [%1]").arg(buffer)); - - items = buffer.split(_separator); - count = items.count(); - - // check the required message length - if ( count < eMessage_Count ) { error = eError_HeaderCount ; ok = false; goto lErr; } - index = eMessage_Timestamp ; message.timestamp = items[index].toUInt(&ok); if (!ok) { error = eError_Timestamp ; ok = false; goto lErr; } - index = eMessage_Sequence ; message.sequence = items[index].toUInt(&ok); if (!ok) { error = eError_Sequence ; ok = false; goto lErr; } - index = eMessage_CRC ; message.crc = items[index].toUInt(&ok); if (!ok) { error = eError_CSCRC ; ok = false; goto lErr; } - index = eMessage_MessageID ; message.id = items[index].toInt (&ok); if (!ok) { error = eError_MessageID ; ok = false; goto lErr; } - index = eMessage_ParamCount; message.paramCount = items[index].toUInt(&ok); if (!ok) { error = eError_ParamCount ; ok = false; goto lErr; } - - // check the parameters count - if ( count - eMessage_Count != message.paramCount ) { error = eError_ParamMismatch ; ok = false; goto lErr; } - - // check missing parameters - id = CS2UI(message.id); - if ( ! paramCount.contains(id)) paramCount[id] = 0; - if ( message.paramCount < paramCount[id] ) { error = eError_ParamMissing ; ok = false; goto lErr; } - - // convert the message id and check its validity - message.id = CS2UI(static_cast(message.id)); - if ( eMessageID_Start > message.id || message.id > eMessageID_Count ) { error = eError_InvalidID ; ok = false; goto lErr; } - - // getting the parameters - for ( quint32 i = eMessage_Count; i < eMessage_Count + message.paramCount; i++ ) { - message.params.append( items[i] ); - } - - vMessage = message; - return true; - -lErr: - // building the error info for each error - switch (error) { - case eError_Unknown : args = { }; break; - case eError_OutFileEmpty : args = { }; break; - case eError_HeaderCount : args = { count , eMessage_Count }; break; - case eError_Timestamp : args = { items[index].trimmed() }; break; - case eError_Sequence : args = { items[index].trimmed() }; break; - case eError_CSCRC : args = { items[index].trimmed() }; break; - case eError_MessageID : args = { items[index].trimmed() }; break; - case eError_InvalidID : args = { items[index].trimmed() }; break; - case eError_ParamCount : args = { items[index].trimmed() }; break; - case eError_ParamMismatch : args = { count - eMessage_Count , message.paramCount }; break; - case eError_ParamMissing : args = { paramCount[message.id] , message.paramCount }; break; - // Interpreter function only handles the message parsing errors. So default can be used. - default : break; - } - toLog(error, args); - return false; -} - -/*! - * \brief CloudSyncController::toText - * \details returns the error message of the error id. - * \param vErrorID - Error id - * \param vMessage - error message - * \return the error message or empty string if the error is 0 (no error), or Unknown Error if not found - */ -QString CloudSyncController::toText(CloudSyncController::Errors_Enum vErrorID) -{ - QString text = tr( "CS Unknown Error" ) ; - if ( vErrorID == 0 ) return text; - switch (vErrorID) { - case eError_Unknown : /* "CS Unknown Error" */ ; break; - // CS Errors - case eError_Registration : text = tr( "CS The device registration failed" ) ; break; - case eError_DeviceState : text = tr( "CS Invalid Device State" ) ; break; - case eError_TxReport : text = tr( "CS The treatment report delivery failed" ) ; break; - case eError_UICRC : text = tr( "CS Invalid UI Message CRC" ) ; break; - case eError_DeviceValidation : text = tr( "CS DRT device registration failed" ) ; break; - case eError_PatientAssociation : text = tr( "CS Patient association failed" ) ; break; - case eError_GetNewTokenCert : text = tr( "CS New token certification failed" ) ; break; - case eError_VerifyToken : text = tr( "CS Token verification failed" ) ; break; - case eError_ValidateDevice : text = tr( "CS Cloud device validation failed" ) ; break; - case eError_PatientIdExists : text = tr( "CS Patient ID does not exists" ) ; break; - case eError_TemporaryPatient : text = tr( "CS Temporary patient ID creation failed" ) ; break; - case eError_SaveCredentials : text = tr( "CS Save credentials failed" ) ; break; - case eError_UnknownDeviceState : text = tr( "CS Unknown device state" ) ; break; - // UI Errors - case eError_OutFileEmpty : text = tr( "CS Out buffer empty" ) ; break; - case eError_HeaderCount : text = tr( "CS Incorrect header" ) ; break; - case eError_Timestamp : text = tr( "CS Incorrect timestamp" ) ; break; - case eError_Sequence : text = tr( "CS Incorrect sequence" ) ; break; - case eError_CSCRC : text = tr( "CS Incorrect CRC" ) ; break; - case eError_MessageID : text = tr( "CS Incorrect ID" ) ; break; - case eError_ParamCount : text = tr( "CS Incorrect parameter count" ) ; break; - case eError_InvalidID : text = tr( "CS Invalid ID" ) ; break; - case eError_ParamMismatch : text = tr( "CS Mismatch parameter count" ) ; break; - case eError_ParamMissing : text = tr( "CS Missing parameter" ) ; break; - case eError_NoHistory : text = tr( "CS No history available" ) ; break; - case eError_Duplicate : text = tr( "CS Duplicate data" ) ; break; - case eError_LogFolder : text = tr( "CS The log folder cannot be created." ) ; break; - case eError_LogFileInp : text = tr( "CS Error writing to the input file." ) ; break; - case eError_CredentialFile : text = tr( "CS The credentials file does not exist." ) ; break; - case eError_CredentialPath : text = tr( "CS The credentials folder is incorrect." ) ; break; - case eError_CredentialCount : text = tr( "CS No credential file name provided." ) ; break; - case eError_CredentialEmpty : text = tr( "CS The credentials folder is empty." ) ; break; - case eError_TxCodeNoParam : text = tr( "CS No Treatment Code provided." ) ; break; - case eError_TxCodeEmpty : text = tr( "CS The provided Treatment Code is empty." ) ; break; - case eError_LogNameNoParam : text = tr( "CS No Log Name provided." ) ; break; - case eError_LogNameEmpty : text = tr( "CS The provided Log Name is empty." ) ; break; - case eError_NotRegistered : text = tr( "CS Not Sent, Device not registered." ) ; break; - case eError_LogRetentionNoParam : text = tr( "CS No Log Retention summary provided." ) ; break; - - } - return text; -} - -/*! - * \brief CloudSyncController::toInfo - * \details Provides extra information, related to each error, to be concatenated later to the error description. - * \param vErrorID - the error id - * \param vInfoItems - the information items to be used in creating the info. - * \return the extra info for the error as string - */ -QString CloudSyncController::toInfo(CloudSyncController::Errors_Enum vErrorID, const QVariantList &vInfoItems) -{ - // IMPORTANT: Please be careful here used QVariantList::value to act as a loose list. - // It means it is designed to not throw out of bound error and just use "~" as a missing info argument. - auto item = [=](uint i) { return vInfoItems.value(i,"~").toString(); }; - - QString info = QString( "[%1]" ).arg( vErrorID ) ; - switch (vErrorID) { - case eError_Unknown : ; break; - // CS Errors - case eError_Registration : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_DeviceState : ; break; - case eError_TxReport : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_UICRC : /* info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) */ ; break; - case eError_DeviceValidation : ; break; - case eError_PatientAssociation : ; break; - case eError_GetNewTokenCert : ; break; - case eError_VerifyToken : ; break; - case eError_ValidateDevice : ; break; - case eError_PatientIdExists : ; break; - case eError_TemporaryPatient : ; break; - case eError_SaveCredentials : ; break; - case eError_UnknownDeviceState : ; break; - // UI Errors - case eError_OutFileEmpty : ; break; - case eError_HeaderCount : info = QString( "[%1:%2/%3]" ).arg( vErrorID ).arg( item(0) ).arg( item(1) ) ; break; - case eError_Timestamp : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_Sequence : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_CSCRC : /* info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) */ ; break; - case eError_MessageID : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_InvalidID : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_ParamCount : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_ParamMismatch : info = QString( "[%1:%2/%3]" ).arg( vErrorID ).arg( item(0) ).arg( item(1) ) ; break; - case eError_ParamMissing : info = QString( "[%1:%2/%3]" ).arg( vErrorID ).arg( item(0) ).arg( item(1) ) ; break; - case eError_NoHistory : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_Duplicate : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_LogFolder : ; break; - case eError_LogFileInp : ; break; - case eError_CredentialFile : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_CredentialPath : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_CredentialCount : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_CredentialEmpty : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_TxCodeNoParam : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_TxCodeEmpty : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_LogNameNoParam : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_LogNameEmpty : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_NotRegistered : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_LogRetentionNoParam : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - } - return info; -} - -/*! - * \brief CloudSyncController::errorOut - * \details the function to send out the error message to the cloudsync inp buffer. - * \param vErrorID - the error id from CloudSyncController::Errors_Enum in range of 950 to 999 - * \param vInfoItems - - */ -void CloudSyncController::errorOut(CloudSyncController::Errors_Enum vErrorID, const QVariantList &vInfoItems) { - QString msg = QString::number(UI2CS(eMessageID_Error)); - QString len = QString::number(1 + vInfoItems.count()); - QString prm = QString::number(vErrorID); - QStringList buf = { msg, len, prm }; - for (const auto &item: vInfoItems) { - buf += item.toString(); - } - sendUIBuff(buf); -} - -/*! - * \brief CloudSyncController::toLog - * \details logs the error in debug mode (service log) - * \param vErrorID - error id - * \param vInfoItems - extra information - */ -void CloudSyncController::toLog(CloudSyncController::Errors_Enum vErrorID, const QVariantList &vInfoItems) -{ - errorOut(vErrorID, vInfoItems); - LOG_DEBUG(toText(vErrorID) + " " + toInfo(vErrorID, vInfoItems)); -} - -/*! - * \brief CloudSyncController::sendUIResponse - * \details the function to be called after reading the CloudSync out file to interpret the content of the file. - * \param vContent - the content to be interpreted. - * \return true if the content has a meaning for the interpreter, false otherwise. - */ -bool CloudSyncController::sendUIResponse(const QString &vContent) -{ - // IMPORTANT: - // please note the following in development testing it has been found out that. - // Do not use gedit. - // if the out.buf is edited by the "gedit" application the watch will be broken. - // The "nano" application works fine. - // A simple python3 code to open file and write to file works as well and that's sufficient for UI<=>CS communication. - // TODO: messages have to have a sequence. - // if the seq is duplicate it will be ignored. - // seq, id, payload - // DEBUG: qDebug() << vContent; - - bool ok = true; - Message message; - if ( ! interpret(vContent, message) ) return false; // The error handling internal to interpret method. - ok = sendMessage(message); - - return ok; -} - -/*! - * \brief CloudSyncController::testWatchBuffDate - * \details Checks the date and updated the watched file in case the date changed. - */ -void CloudSyncController::testWatchBuffDate() -{ - _datetime = QDateTime::currentDateTime().toUTC(); - _secSinceEpoch = _datetime.toSecsSinceEpoch(); - _timeFormatted = _datetime.toString(_timeFormat); - QString dateFormatted = _datetime.toString(_dateFormat); - if (_dateFormatted != dateFormatted || ! _isWatching ) { - _dateFormatted = dateFormatted; - _isWatching = false; // first time the date changes and CSctrl needs a new watch this flag need to be reset until the watch is added. - // TODO: do we need to remove previous watch? - addCSBuffWatch(); // bool out is not used here and error handling is internal to the addCSBuffWatch - } -} - -/*! - * \brief CloudSyncController::makeUIBuff - * \details make formatted UI buffer to send out. - * \param vMessage - The message id with destination added. 2k, 1K - * \return the formatted buffer - */ -QStringList CloudSyncController::makeUIBuff(const qint32 vMessageID, const QStringList &vPrm) -{ - int count = vPrm.count(); - QStringList prm = count ? vPrm : _uiHistory[ vMessageID ]; - QString msg = QString::number(vMessageID); - QString len = QString::number(prm.count()); - - QStringList dataList; - dataList += msg; - dataList += len; - dataList += prm; - - return dataList; -} - -/*! - * \brief CloudSyncController::isDuplicate - * \param vMessage - current action/messageID received. - * \param vData - current data to compare with the history - * \return true if duplicate - */ -bool CloudSyncController::isDuplicate(const qint32 vMessageID, const QStringList &vData) -{ - return _uiHistory.contains(vMessageID) && _uiHistory[vMessageID] == vData; -} - -/*! - * \brief CloudSyncController::writeInpFile - * \details Writes to the CS Input buffer - * \param vBuffer - the string out buffer. - * \return true if successful - */ -bool CloudSyncController::writeInpFile(const QString &vInpBuff) -{ - bool ok = true; - QVariantList args ; - Errors_Enum error = eError_Unknown; - - _date_inp_File = _location + // The location - _dateFormatted + _dateSeparator + _inp_File; // The file name - - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - // writing the message into the buffer. - LOG_APPED_UI( vInpBuff ); - if(!QFile::exists(_date_inp_File)){ - Storage::FileHandler::makeFolder(_location); - } - if ( ! Storage::FileHandler::write(_date_inp_File, vInpBuff + "\n") ) { error = eError_LogFileInp; args = { _date_inp_File }; ok = false; goto lErr; } - return ok; - -lErr: - // don't send the error back to the CS - // it calls this same function if the error is in this function - // and creates a loop - LOG_DEBUG(toText(error) + " " + toInfo(error, args)); - return ok; -} - -/*! - * \brief CloudSyncController::sendUIBuff - * \details Sends the UI Buffer to the UI input file. - * \param vData - the data to be sent out. - * \return true on successful writing to the file buffer. - * \sa _inp_File - */ -bool CloudSyncController::sendUIBuff(const QStringList &vDataList) -{ - bool ok = true; - QString inpBuff = "%1,%2,%3,%4"; - - inpBuff = inpBuff - .arg( _secSinceEpoch ) - .arg( Types::safeIncrement(_seq)) - .arg( generateCRC(_secSinceEpoch, _seq, vDataList)) - .arg( vDataList.join(_separator)); - - ok = writeInpFile(inpBuff); - return ok; -} - -/*! - * \brief CloudSyncController::saveUIHistory - * \details stores the action data in history, if vData is provided. - * \param vAction - the request id, which in current design is same as the Message comes to the UI. - * \param vData - The message data - * \return true if a message history is available, false otherwise. - */ -bool CloudSyncController::saveUIHistory(const qint32 vAction, const QVariantList &vData) -{ - bool ok = true; - QVariantList args ; - Errors_Enum error = eError_Unknown; - - qint32 messageID = UI2CS(static_cast(vAction)); - QStringList data; - - if ( vData.isEmpty() ) { error = eError_NoHistory; args = { messageID }; ok = false; goto lErr; } - - data = Format::fromVariantList(vData); - - if ( isDuplicate(messageID, data) ) { error = eError_Duplicate; args = { messageID }; ok = false; return ok; } // TODO: goto lErr; } don't log it just ignore and return false. - - // store the last message data - _uiHistory[messageID] = data; - - return ok; - -lErr: - toLog(error, args); - return ok; -} - -/*! - * \brief CloudSyncController::sendUIHistory - * \details sends the saved history of the message as a response to the cloud sync upon request. - * \param vAction - the request id, which in current design is same as the Message comes to the UI. - * \return true if a message history is available, false otherwise. - */ -bool CloudSyncController::sendUIHistory(const qint32 vAction) -{ - bool ok = true; - QVariantList args ; - Errors_Enum error = eError_Unknown; - - // only sends messages if device has been registered - qint32 messageID = UI2CS(static_cast(vAction)); - if ( ! isRegistered() && vAction != eMessageID_DeviceRegister ) { error = eError_NotRegistered ; args = { messageID }; ok = false; goto lErr; } - if ( ! _uiHistory.contains( messageID ) ) { error = eError_NoHistory ; args = { messageID }; ok = false; goto lErr; } - - sendUIBuff(makeUIBuff(messageID)); - - return ok; - -lErr: - toLog(error, args); - return ok; -} - -/*! - * \brief CloudSyncController::onActionReceive - * \details The slot which will be called when a CANBus message is received, and will be sent to CloudSync if related. - * \param vAction - The message action - * \param vData - The message data - */ -void CloudSyncController::onActionReceive(GuiActionType vAction, const QVariantList &vData) -{ - // TODO: This section is the translation/mapping section - // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv - // preparing the message - // Same parameters will be sent for these messages for now, so data is not changed. - - switch (vAction) { - case GuiActionType::ID_HDOperationModeData : { - // ---------------------------------------------------------------------------------------- - // DEBUG : disable the HD HW message on the device on CloudSync integration testing. - // currently it is always on fault and resets CloudSync inp log - QVariantList varData = vData; - QStringList strData = Format::fromVariantList(vData); - if ( strData.length() == 2 ) { - if ( strData[0] == "0" ) { - if ( strData[1] == "1") { varData = {0,0}; } // use Fault, wait4Tx instead of 0,0 to send fault. to able to simulate and bypass HD. - else { return; } - } - } - // ---------------------------------------------------------------------------------------- - - if (saveDeviceState(varData)) // if not empty, nor duplicate, and saved - sendDeviceState(); - } - break; - - case GuiActionType::ID_AdjustSerialHDRsp : - case GuiActionType::ID_AdjustSerialDGRsp : - // this message is not complete in one step and saves only the received info and waits for the other if complete will send. - saveDeviceInfo(vAction, vData); - break; - - default: - break; - } -} - -void CloudSyncController::sendPendingTxr(const QString &vFileName) -{ - bool ok = true; Q_UNUSED(ok) - QVariantList args ; - Errors_Enum error = eError_Unknown; - qint32 messageID = UI2CS(static_cast( eMessageID_TxReport )); - // DEBUG ok = false; // using the ok bool which is true as the debug flag to bypass the registration on debug testing. - if ( ok && ! isRegistered() ) { error = eError_NotRegistered ; args = { vFileName }; ok = false; goto lErr; } - sendUIBuff(makeUIBuff( messageID , { vFileName } )); - - return; -lErr: - toLog(error, args); -} - -void CloudSyncController::onPendingTxr(const QString &vFileName) -{ - sendPendingTxr(vFileName); -} - -/*! - * \brief CloudSyncController::onPendingLog - * \details The signal handler to call the function to send the CloudSync a message to upload the pending file. - * \param vFileName - The pending file name - * \param vChecksum - The sha256sum of the file content - */ -void CloudSyncController::onPendingLog(const QString &vFileName, const QString vChecksum) -{ - sendPendingLog(vFileName, vChecksum); -} - -/*! - * \brief CloudSyncController::sendPendingLog - * \details The function to send CloudSync a message to uplaod the pending log file. - * \param vFileName - The pending file name - * \param vChecksum - The sha256sum of the file content - */ -void CloudSyncController::sendPendingLog(const QString &vFileName, const QString vChecksum) -{ - bool ok = true; Q_UNUSED(ok) - QVariantList args ; - Errors_Enum error = eError_Unknown; - qint32 messageID = UI2CS(static_cast( eMessageID_SendLogUpload )); - // DEBUG ok = false; // using the ok bool which is true as the debug flag to bypass the registration on debug testing. - if ( ok && ! isRegistered() ) { error = eError_NotRegistered ; args = { vFileName }; ok = false; goto lErr; } - sendUIBuff(makeUIBuff( messageID , { vFileName, vChecksum } )); - - return; -lErr: - toLog(error, args); -} - -/*! - * \brief CloudSyncController::rcvdPendingLog - * \details reads the received Log Name from CloudSync app and notifies with a signal. - * \param vMessage : message containing the uploaded Log name. - * \return true on successful extracting the Log Name. - */ -bool CloudSyncController::rcvdPendingLog(const Message &vMessage) -{ - bool ok = true; - QString mLogName; - // although it has been checked in the interpreter, we won't risk the crash and check the list empty. - if ( vMessage.params.isEmpty() ) { toLog(eError_LogNameNoParam , {}); ok = false; goto lOut; } - mLogName = vMessage.params[0].trimmed(); - if ( mLogName.isEmpty() ) { toLog(eError_LogNameEmpty , {}); ok = false; goto lOut; } - //DEBUG qDebug() << " ---------- " << mLogName; - emit didLogUpload( mLogName ); - - lOut: - return ok; -} - -/// -------------------------------------------------- Retention Log -/*! - * \brief CloudSyncController::onRetentionLog - * \details The slot tha handles the received even to request for the log retention - * \param vMaxUsePercent - the maximum size of the SD-Card (log storage device) cloudsync can get for the logs - */ -void CloudSyncController::onRetentionLog(quint8 vMaxUsePercent) -{ - sendRetentionLog(vMaxUsePercent); -} - -void CloudSyncController::sendRetentionLog(quint8 vMaxUsePercent) -{ - bool ok = true; Q_UNUSED(ok) - QVariantList args ; - Errors_Enum error = eError_Unknown; - qint32 messageID = UI2CS(static_cast( eMessageID_SendLogRetention )); - // DEBUG ok = false; // using the ok bool which is true as the debug flag to bypass the registration on debug testing. - if ( ok && ! isRegistered() ) { error = eError_NotRegistered ; args = { eMessageID_SendLogRetention, vMaxUsePercent }; ok = false; goto lErr; } - sendUIBuff(makeUIBuff( messageID , { QString::number(vMaxUsePercent) } )); - - return; -lErr: - toLog(error, args); -} - -bool CloudSyncController::rcvdRetentionLog(const Message &vMessage) -{ - bool ok = true; - QString tmp; - quint16 mLogsCount; // number of deleted logs - quint32 mLogsSize ; // total current logs size - // although it has been checked in the interpreter, we won't risk the crash and check the list empty. - if ( vMessage.params.isEmpty() ) { toLog(eError_LogRetentionNoParam , {eMessageID_SendLogRetention }); ok = false; goto lOut; } - if ( vMessage.params.count () < paramCount[eMessageID_SendLogRetention]) { toLog(eError_ParamCount , {eMessageID_SendLogRetention }); ok = false; goto lOut; } - - tmp = vMessage.params[0]; - mLogsCount = tmp.toUInt(&ok); - if ( ! ok ) { toLog(eError_ParamMismatch , {eMessageID_SendLogRetention, 0}); ok = false; goto lOut; } - - tmp = vMessage.params[1]; - mLogsSize = tmp.toUInt(&ok); - if ( ! ok ) { toLog(eError_ParamMismatch , {eMessageID_SendLogRetention, 1}); ok = false; goto lOut; } - - //DEBUG qDebug() << " ---------- " << mLogsCount << mLogsSize; - emit didLogRetention( mLogsCount, mLogsSize ); - - LOG_APPED_CS(QString("Log retention deleted %1 files of total %2 MB.").arg(mLogsCount).arg(mLogsSize)); - - lOut: - return ok; -} -/// -------------------------------------------------- - -/*! - * \brief CloudSyncController::sendMessage - * \details Makes and Sends the appropriate message for the vAction. - * Some messages are sent out upon request form the last received on the history and will not be asked from FW. - * \return true if there is a history for that message and no error happened. - */ -bool CloudSyncController::sendMessage(const Message &vMessage) { - bool ok = false; - // we are using the file system for the UI<=>CS communication, - // and the file events are multiple during the write to the file. - // if a write to the _out.buf trigeres the message to be sent more than once, - // ignore the same message, with the same sequence number. - static quint32 sequence ; - if ( sequence == vMessage.sequence ) return ok; - sequence = vMessage.sequence ; - - //DEBUG qDebug() << Q_FUNC_INFO << vMessage.id; - // this function is used in sendUIResponse, therefore the message IDs which are responses should be implemented here. - switch (vMessage.id) { - case eMessageID_DeviceRegister : /* No Request/Response */ break; - case eMessageID_CheckIn : ok = takeCheckIn ( ); break; - case eMessageID_DeviceInfo : ok = sendDeviceInfo ( ); break; - case eMessageID_CredentialsSave : ok = sendCredentialsSave( vMessage ); break; - case eMessageID_UIFactoryReset : ok = sendFactoryReset ( ); break; - case eMessageID_DeviceState : ok = sendDeviceState ( ); break; - - case eMessageID_TxCodeDisplay : ok = sendTxCodeDisplay ( vMessage ); break; - case eMessageID_SendLogUpload : ok = rcvdPendingLog ( vMessage ); break; - case eMessageID_SendLogRetention : ok = rcvdRetentionLog ( vMessage ); break; - - case eMessageID_TxReport : /* No Req/Rsp, it is event based */ break; // This message doesn't have the response since there is no request. UI will send when the data ready by HD. - - case eMessageID_Error : ok = errorHandler ( vMessage ); break; - - } - return ok; -} - -/*! - * \brief CloudSyncController::saveDeviceState - * \details Saves the device state in the history for later request - * \param vData - the device information - * \return - */ -bool CloudSyncController::saveDeviceState(const QVariantList &vData) -{ - bool ok = false; - ok = saveUIHistory(eMessageID_DeviceState, vData); - return ok; -} - -/*! - * \brief CloudSyncController::sendDeviceState - * \return Sends the last received device state from history to CloudSync - */ -bool CloudSyncController::sendDeviceState() -{ - bool ok = false; - ok = sendUIHistory(eMessageID_DeviceState); - return ok; -} - -/*! - * \brief CloudSyncController::csFactoryReset - * \details This function is requesting CloudSync to remove all the logs - * in the logs folder (/media/sd-card/cloudsync/log/) - * during Factory Reset - * It is a request from UI to CS since UI Linux user (denali) should not have access - * to remove the CloudSync logs. - */ -bool CloudSyncController::csFactoryReset() -{ - - bool ok = true; - return ok; // NOT IMPLEMENTED - - QVariantList args ; - Errors_Enum error = eError_Unknown; - qint32 messageID = UI2CS(static_cast( eMessageID_CSFactoryReset )); - - if ( ! isRegistered() ) { error = eError_NotRegistered ; args = {}; ok = false; goto lErr; } - sendUIBuff(makeUIBuff( messageID , { } )); - - return ok; -lErr: - toLog(error, args); - return ok; -} - -/*! - * \brief CloudSyncController::Decommissioning - * \details This function is requesting CloudSync to remove all the credentials and configurations - * in the settings partition (/var/configurations/CloudSync) - * during decommissioning - * which then it means the device needs to be re-registered to able to communicate with cloud. - * It is a request from UI to CS since UI Linux user (denali) should not have access - * to remove the CloudSync credentials. - */ -bool CloudSyncController::csDecommissioning() -{ - bool ok = true; - QVariantList args ; - Errors_Enum error = eError_Unknown; - qint32 messageID = UI2CS(static_cast( eMessageID_CSDecommissioning )); - - if ( ! isRegistered() ) { error = eError_NotRegistered ; args = {}; ok = false; goto lErr; } - sendUIBuff(makeUIBuff( messageID , { } )); - - return ok; -lErr: - toLog(error, args); - return ok; -} - -/*! - * \brief CloudSyncController::factoryReset - * \details does the reset factory - * \return true on successful reset - */ -bool CloudSyncController::uiFactoryReset() -{ - // reset factory has not been implemented yet. - bool ok = true; - // TODO: call the UI Software Reset Factory here when it has implemented. - LOG_DEBUG("CloudSync Reset factory request has not been implemented yet."); - return ok; -} - -/*! - * \brief CloudSyncController::sendFactoryReset - * \details sends the factory reset response to CloudSync - * \return true on successful send. - */ -bool CloudSyncController::sendFactoryReset() -{ - enum { eSucceed, eFailed }; - bool ok = false; - ok = uiFactoryReset(); - // if ( ! ok ) { } /* Not defined */ - qint32 messageID = UI2CS(eMessageID_UIFactoryReset); - // TODO : Store registration success in here - ok = sendUIBuff({ QString("%1").arg( messageID ) ,"1", QString("%1").arg( ok ? eSucceed : eFailed ) }); - return ok; -} - -/*! - * \brief CloudSyncController::sendCredentialsSave - * \details validates the credentials files which their sent location. - * \param vMessage - the message received from CloudSync - * \return true on successfully validate the files. - */ -bool CloudSyncController::sendCredentialsSave( const Message &vMessage) -{ - bool ok = true; - QString destination = QString(Storage::CloudSync_Base_Path_Name) + Storage::CloudSync_Credentials_Folder_Name; - - // file existence has been separated from copy remove to avoid partial copy. - if ( ! vMessage.params.count() ) { toLog(eError_CredentialCount , { }); ok = false; goto lOut; } - for ( auto fileName : vMessage.params ) { - QFileInfo fileinfo(fileName); - if ( ! fileinfo.exists() ) { toLog(eError_CredentialFile , {fileName }); ok = false; goto lOut; } - if ( fileinfo.absolutePath() != destination ) { toLog(eError_CredentialPath , {fileName }); ok = false; goto lOut; } - } - // no need to call for isRegistered() function, we are testing for the count, location, and existence. - -lOut: - if ( ok ) sendCredentialsResponse(); - emit didRegisterDone(ok); - testReady(); - return ok; -} - -/*! - * \brief CloudSyncController::sendCredentialsResponse - * \return send the confirmation of the credential save on the device from UI to CS to let CS know when to delete them. - */ -bool CloudSyncController::sendCredentialsResponse() -{ - enum { eSucceed, eFailed }; - bool ok = false; - qint32 messageID = UI2CS(eMessageID_CredentialsSave); - ok = sendUIBuff({ QString("%1").arg( messageID ) ,"1", QString("%1").arg( ok ? eSucceed : eFailed ) }); - return ok; -} - -/*! - * \brief CloudSyncController::sendCheckIn - * \details Send a check-in message and expects the same check-in message from CS - * \return - */ -bool CloudSyncController::sendCheckIn() -{ - _checkinRcvd = false; - qint32 messageID = UI2CS(eMessageID_CheckIn); - return sendUIBuff( { QString("%1").arg( messageID ) ,"0" } ); -} - -/*! - * \brief CloudSyncController::testCheckIn - * \details - * \return - */ -void CloudSyncController::testCheckIn() -{ - if ( ! _checkinRcvd ) { - LOG_APPED_UI(QString("CloudSync check-in failed")); - } -} - -/*! - * \brief CloudSyncController::takeCheckIn - * \details if the check-in received this method will be called - * \return true - - */ -bool CloudSyncController::takeCheckIn() -{ - _checkinRcvd = true; - emit didCheckInReceive(); - return true; -} - -/*! - * \brief CloudSyncController::sendTxCodeDisplay - * \details reads the received Tx Code from CloudSync app and notifies with a signal. - * \param vMessage : message containing the Tx Code. - * \return true on successful extracting the Tc Code. - */ -bool CloudSyncController::sendTxCodeDisplay(const CloudSyncController::Message &vMessage) -{ - bool ok = true; - QString mTxCode; - - // although it has been checked in the interpreter, we won't risk the crash and check the list empty. - if ( vMessage.params.isEmpty() ) { toLog(eError_TxCodeNoParam , {}); ok = false; goto lOut; } - - mTxCode = vMessage.params[0].trimmed(); - if ( mTxCode.isEmpty() ) { toLog(eError_TxCodeEmpty , {}); ok = false; goto lOut; } - - //DEBUG qDebug() << " ---------- " << mTxCode; - emit didTxCodeReceive ( mTxCode ); - -lOut: - return ok; -} - -/*! - * \brief CloudSyncController::errorHandler - * \details nothing for now. - * \param vMessage - the received message - * \return always true for now. - */ -bool CloudSyncController::errorHandler(const Message &vMessage) -{ - qDebug() << "ERR: " << vMessage.params; - return true; -} - -/*! - * \brief CloudSyncController::sendDeviceRegister - * \details sends the device registration request - * \return true on successful send. - */ -bool CloudSyncController::sendDeviceRegister() -{ - bool ok = false; - ok = sendUIHistory(eMessageID_DeviceRegister); - return ok; -} - -/*! - * \brief CloudSyncController::isRegistered - * \details checks if the device is registered by looking for existing credentials. - * \return true if device has been registered. - */ -bool CloudSyncController::isRegistered() -{ -#ifdef BUILD_FOR_DESKTOP - return true; -#else - QString source = QString(Storage::CloudSync_Base_Path_Name) + Storage::CloudSync_Credentials_Folder_Name; - QFileInfoList fileInfos = QDir(source).entryInfoList(QDir::NoDotAndDotDot|QDir::Files); - return !fileInfos.isEmpty(); -#endif -} - -/*! - * \brief CloudSyncController::testReady - * \details Checks if the CloudSync POST was passed and the device registration is complete. - */ -void CloudSyncController::testReady() -{ - bool ok = _postPass && isRegistered(); -#ifdef BUILD_FOR_DESKTOP - ok = true; -#endif - emit didCloudSyncStatus( ok - || gDisableCloudSyncFailStop // Development testability - ); -} - -/*! - * \brief CloudSyncController::saveDeviceInfo - * \details keeps the received device information and set a flag to wait for the next message. - * \param vAction - the action enum which identifies information source of HD or DG. - * \param vData - the device information - * \param vTimedOut - if true the missing device information will be filled by zero and will be sent anyway. - * \return - */ -bool CloudSyncController::saveDeviceInfo(GuiActionType vAction, const QVariantList &vData) -{ - bool ok = false; - - // the messages are coming from different sources and the order of receiving could not be guessed. - // so for each of these two we check and fill the designated variable - // if both values received - initDeviceInfoWait(); - - if ( vAction == GuiActionType::ID_AdjustSerialHDRsp ) { - if ( vData.count() ) { - _deviceInfoHD = vData[eDeviceInfo_Ix].toString(); - } else { - _deviceInfoHD = ""; - } - } - if ( vAction == GuiActionType::ID_AdjustSerialDGRsp ) { - if ( vData.count() ) { - _deviceInfoDG = vData[eDeviceInfo_Ix].toString(); - } else { - _deviceInfoDG = ""; - } - } - - if ( !_deviceInfoHD.isEmpty() && !_deviceInfoDG.isEmpty() ) { - saveDeviceInfoTimeOut(); - ok = true; - } - return ok; -} - -/*! - * \brief CloudSyncController::saveDeviceInfoTimeOut - * \details send the Device information regardless of the information filled or not. - * \return true on successful save and send. - */ -bool CloudSyncController::saveDeviceInfoTimeOut() -{ - bool ok = false; - if ( _deviceInfoUI.isEmpty() ) { _deviceInfoUI = qApp->applicationVersion(); } - saveUIHistory(eMessageID_DeviceInfo , { _deviceInfoHD, _deviceInfoDG, _deviceInfoUI } ); - saveUIHistory(eMessageID_DeviceRegister , { _deviceInfoHD, _deviceInfoDG, _deviceInfoUI } ); // the device registration request format is the same as Device info with different message id. - - sendDeviceInfo (); // this one may need to be removed and only will be sent on the request - - stopDeviceInfoWait(); - - return ok; -} - -/*! - * \brief CloudSyncController::sendDeviceInfo - * \details sends the devices information if both the HD and DG information are ready otherwise will fill the missing with 0 if the vTimeout is set to true. - * \return - */ -bool CloudSyncController::sendDeviceInfo() -{ - bool ok = false; - ok = sendUIHistory(eMessageID_DeviceInfo); - return ok; -} - -/*! - * \brief CloudSyncController::testDeviceInfoWait - * \details tests the wait conditions if there is a wait, decrements until times out and sends the device info. - */ -void CloudSyncController::testDeviceInfoWait() -{ - if (_deviceInfoStop ) return; - // DEBUG: qDebug() << _deviceInfoWait; - if (_deviceInfoWait ){ - _deviceInfoWait -- ; - } - else { - saveDeviceInfoTimeOut(); - } -} - -/*! - * \brief CloudSyncController::stopDeviceInfoWait - * \details stops waiting for the device info. - */ -void CloudSyncController::stopDeviceInfoWait() { - _deviceInfoStop = true; - // NOTE: if it is accepted to use the last received info just comment these 3 cleanup lines. - // so the last message will update the history only and it will be sent out. - _deviceInfoHD = ""; - _deviceInfoDG = ""; - _deviceInfoUI = ""; -} - -/*! - * \brief CloudSyncController::initDeviceInfoWait - * \details if currently not in wait, then set the wait to the secs of wait. - */ -void CloudSyncController::initDeviceInfoWait() { - if ( _deviceInfoStop ) { // if timer is stopped - _deviceInfoWait = _deviceInfoSecs; // enable the timer. - _deviceInfoStop = false; - } -} - -/*! - * \brief CloudSyncController::generateCRC - * \details The crc algorithm is to get - * all the message sections including, epoch, seq, and the parameters, - * without any separator - * concatenate them together like a one long string - * and run the crc on them - * \example 1674193951,4,,1001,3,HD1234567890123,DG1234567890123,DEN-14517-UI-BN-S88.01192111.1 - * str: 1674193951410013HD1234567890123DG1234567890123DEN-14517-UI-BN-S88.01192111.1 - * out: 145 - * \note the parameters values will keep untouched, meaning, if a parameter has space it will not be removed. - * \param vSecSinceEpoch - * \param vSeq - the message sequence - * \param vData - the message payload in list of strings - * \return - quint8 crc code - */ -quint8 CloudSyncController::generateCRC(quint64 vSecSinceEpoch, quint64 vSeq, const QStringList &vDataList) -{ - quint8 crc = 0; - QByteArray buf; - buf += QByteArray::number(vSecSinceEpoch); - buf += QByteArray::number(vSeq); - buf += vDataList.join("").toLatin1(); - crc = crc8(buf); - return crc; -} +/*! + * + * Copyright (c) 2021-2024 Diality Inc. - All Rights Reserved. + * \copyright + * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN + * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. + * + * \file CloudSyncController.cpp + * \author (last) Behrouz NematiPour + * \date (last) 13-Mar-2024 + * \author (original) Behrouz NematiPour + * \date (original) 14-Oct-2021 + * + */ +#include "CloudSyncController.h" + +// Qt +#include + +// Project +#include "ApplicationController.h" +#include "DeviceController.h" +#include "FileHandler.h" +#include "TreatmentLog.h" +#include "crc.h" + +SINGLETON_DISABLE(CloudSyncController) + +/*! + * \brief CloudSyncController::CloudSyncController + * \details Constructor + * \param parent - QObject parent owner object. + * Qt handles the children destruction by their parent objects life-cycle. + */ +CloudSyncController::CloudSyncController(QObject *parent) : QObject(parent) { + testWatchBuffDate(); + startTimer(_interval); +} + +/*! + * \brief CloudSyncController initializer + */ +bool CloudSyncController::init() +{ + if ( _init ) return false; + _init = true; + + initConnections(); + LOG_DEBUG(tr("%1 Initialized").arg(metaObject()->className())); + + return true; +} + +/*! + * \brief CloudSyncController::init + * \details Initialized the Class by calling the init() method first + * And initializes the thread vThread by calling initThread + * on success init(). + * \param vThread - the thread + * \return returns the return value of the init() method + */ +bool CloudSyncController::init(QThread &vThread) +{ + if ( ! init() ) return false; + initThread(vThread); + return true; +} + +/*! + * \brief CloudSyncController::quit + * \details quits the class + * Calls quitThread + */ +void CloudSyncController::quit() +{ + // disabled coco begin validated: CloudSync termination is not correctly done in coco!!! + // it has been tested and works perfectly fine in normal run. + quitThread(); // validated +} +// disabled coco end + +/*! + * \brief CloudSyncController::doRegister + * \details Send the register device command. + */ +void CloudSyncController::doRegister() +{ + sendDeviceRegister(); +} + +/*! + * \brief CloudSyncController::initConnections + * \details Initializes the required signal/slot connection between this class and other objects + * to be able to communicate. + */ +void CloudSyncController::initConnections() +{ + if ( ! gDisableCloudSyncFailStop ) { + SINGLETON_DISABLE_CONNECT(didPOSTCloudSync) + } + + connect(&_ApplicationController , SIGNAL(didPOSTCloudSync(bool)), + this , SLOT( onPOSTCloudSync(bool))); + connect(&_ApplicationController , SIGNAL(didActionReceive (GuiActionType , const QVariantList &)), + this , SLOT( onActionReceive (GuiActionType , const QVariantList &))); + + connect(&_DeviceController , SIGNAL(didCryptSetupMount(bool)), + this , SLOT( onCryptSetupMount(bool))); + connect(&_DeviceController , SIGNAL(didPendingLog (const QString &, const QString &)), + this , SLOT( onPendingLog (const QString &, const QString &))); + connect(&_Logger , SIGNAL(didRetentionLogCS (quint8)), + this , SLOT( onRetentionLog (quint8))); + + connect(&_DeviceController , SIGNAL(didWatchFileChange (const QString &)), + this , SLOT( onWatchFileChange (const QString &))); + connect(&_DeviceController , SIGNAL(didFactoryReset (bool)), + this , SLOT( onFactoryReset (bool))); + connect(&_DeviceController , SIGNAL(didDecommissioning (bool)), + this , SLOT( onDecommissioning (bool))); + connect(&_TreatmentLog , SIGNAL(didPendingTxr (const QString &)), + this , SLOT( onPendingTxr (const QString &))); + connect(this , SIGNAL(didInitComplete ()), + this , SLOT( onInitComplete ()),Qt::QueuedConnection); // it has to be queued connection, don't remove it. +} + +/*! + * \brief CloudSyncController::initThread + * \details Moves this object into the thread vThread. + * And checks that this method is called from main thread. + * Also connects quitThread to CloudSync aboutToQuit. + * \param vThread - the thread + */ +void CloudSyncController::initThread(QThread &vThread) +{ + // runs in main thread + Q_ASSERT_X(QThread::currentThread() == qApp->thread(), __func__, "The Class initialization must be done in Main Thread" ); + _thread = &vThread; + _thread->setObjectName(QString("%1_Thread").arg(metaObject()->className())); + connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(quit())); + _thread->start(); + moveToThread(_thread); +} + +/*! + * \brief CloudSyncController::quitThread + * \details Moves this object to main thread to be handled by QCloudSync + * And to be destroyed there. + */ +void CloudSyncController::quitThread() +{ + // disabled coco begin validated: CloudSync termination is not correctly done in coco!!! + // it has been tested and works perfectly fine in normal run. + + if ( ! _thread ) return; + + // runs in thread + moveToThread(qApp->thread()); // validated +} +// disabled coco end + +/*! + * \brief CloudSyncController::timerEvent + * \details The timer event handler which currently is triggered on each second to check for the date change, + * Which caused the watched file change and needs to updated the watched list. + * The check-in (watch dog) also needs to be here. + */ +void CloudSyncController::timerEvent(QTimerEvent *) +{ + TIME_CALL(sendCheckIn(), _checkinIntervalSend); // call every x times/second - will be called on first call. + TIME_CALL(testCheckIn(), _checkinIntervalTest); // call every x times/second - will be called on first call. + testWatchBuffDate(); + testDeviceInfoWait(); +} + +/*! + * \brief CloudSyncController::event + * \details The override method of QObject event to handle the ThreadChange. + * \param vEvent - the QObject event + * \return true if the event e was recognized and processed + */ +bool CloudSyncController::event(QEvent *vEvent) { + if (vEvent->type() == QEvent::ThreadChange) { + emit didInitComplete(); + } + // Make sure the rest of events are handled + return QObject::event(vEvent); +} + +/*! + * \brief CloudSyncController::onInitComplete + * \details The slot to be called when the CloudSync initialization is complete + * Does nothing for now + */ +void CloudSyncController::onInitComplete() { + /* Development testability: + For testing if -C (gDisableCloudSyncFailStop) is used call the testReady + which is called + - when the encrypted partition is mounted + - a request for credential save received + testReady will set ok = true if -C (gDisableCloudSyncFailStop) is used. + */ + if ( gDisableCloudSyncFailStop ) testReady(); +} + +/*! + * \brief CloudSyncController::onPOSTCloudSync + * \details This the handler for the ApplicationController::didPOSTCloudSync(bool) + * \param vPass - will be true if the POST test of CloudSync passed and it is running. + */ +void CloudSyncController::onPOSTCloudSync(bool vPass) +{ + _postPass = vPass; +} + +/*! + * \brief CloudSyncController::onCryptSetupMount + * \details This the handler for the DeviceController::onCryptSetupMount() + */ +void CloudSyncController::onCryptSetupMount(bool vPass) +{ + if ( vPass ) { + testReady(); + } +} + +/*! + * \brief CloudSyncController::onFactoryReset + * \details this slot will be called when the DeviceController is done with the Factory Reset + * to let the UI request CS to do the Factory Reset and clean up all the Logs. + * \param vPass - Device controller factory reset was successful. + */ +void CloudSyncController::onFactoryReset(bool vPass) +{ + if ( vPass ) { + csFactoryReset(); + } +} + +/*! + * \brief CloudSyncController::onDecommissioning + * \details this slot will be called when the DeviceController is done with the Decommissioning + * to let the UI request CS to do the Decommissioning and clean up all the Tokens. + * \param vPass - Device controller Decommissioning was successful. + */ +void CloudSyncController::onDecommissioning(bool vPass) +{ + if ( vPass ) { + csDecommissioning(); + } +} + +/*! + * \brief CloudSyncController::onWatchFileChange + * \details This slot will be called when the Device Controller identifies any changes in the watched files. + * \param vFile - watched file + * \note The DeviceController will emit the signal on any watched file update, it's up to the CloudSyncController to filter the result. + */ +void CloudSyncController::onWatchFileChange(const QString &vFile) +{ + // TODO: It may improve the performance of the code to make it more specific. + // Meaning, Device controller has only one file watcher and it will call all the slots of all the classes watching for a file change. + // then ignore what is not important, but it may effect, the call queue and the threading performance. + // Later investigation. + if ( vFile != _date_out_File ) return; // ignore unwanted file updates. + QString content; + Storage::FileHandler::read(vFile, content); + sendUIResponse(content); +} + +/*! + * \brief CloudSyncController::addCSBuffWatch + * \details Adds a watcher on the CloudSync Application output file. + * \sa _out_File + */ +bool CloudSyncController::addCSBuffWatch() +{ + bool ok = true; + QVariantList args {}; + Errors_Enum error = eError_Unknown; + QString dateFormatted; + + // -------------------------------------------------- check the folder + // this makeFolder function call investigated for permissions + // during setup the folder if does not exists will be created (root) + // the lockdown.sh script will set the permissions for it. + // on power cycle to normal operation the folder with the correct exists. + // *** NOTE: the makeFolder returns true if the folder already exists. *** + ok = Storage::FileHandler::makeFolder(_location); + if ( ! ok ) { error = eError_LogFolder; args = {{ _location }}; ok = false; goto lErr; } + + // -------------------------------------------------- Check the out file + // check the date time for the file to watch + dateFormatted = _datetime.toString(_dateFormat); + if (_dateFormatted != dateFormatted ) { + _dateFormatted = dateFormatted; + _isWatching = false; // first time the date changes and CSctrl needs a new watch this flag need to be reset until the watch is added. + } + _date_out_File = _location + _dateFormatted + _dateSeparator + _out_File; + // watching/wait for the cloud sync output file buffer. + // if the file does not exists, send a check-in to the CS and wait for the response. + // by CS sending the response it will create the out file and next time this function with start watching the file. + // since this class has a one second timer set, next call is next second + // TODO: during this less that 1s UI will not see messages from CS, since the file was not there to watch. + // send device state to make the CloudSync send back a message to create the out buff with its user to own the file + ok = QFileInfo(_date_out_File).exists(); + if ( ! ok ) { + _isWatching = false; + sendCheckIn(); + } + if ( ! ok ) { error = eError_OutFileExist; args = {{ _location }}; ok = false; goto lErr; } + + // -------------------------------------------------- return if already watching + if ( _isWatching ) { goto lOut; } + + // -------------------------------------------------- add the watch + _DeviceController.doAddWatch(_date_out_File, false); + _isWatching = true; // when the watch is added then the flag sets until next time the date changes. + // since the buff files will be deleted on each power cycle, when the out buf is created it means the CloudSync is running. + // we emit the ApplicationController to check the post.log for the CloudSync status check. + goto lOut; + +lErr: + toLog(error, args); + +lOut: + return ok; +} + +/*! + * \brief CloudSyncController::interpret + * \details Checks the received buffer to make sure it has all the required parameters + * \param vIndex + * \return true if the buffer is correct. + */ +bool CloudSyncController::interpret(const QString &vContent, Message &vMessage) +{ + bool ok = true; + QVariantList args {}; + Errors_Enum error = eError_Unknown; + + QStringList lines; + QString buffer; + + int index = -1; + quint32 count = 0; + + Message message; + QStringList items; + quint32 id; + + if ( vContent.isEmpty() ) { error = eError_OutFileEmpty; ok = false; goto lErr; } + + lines = vContent.split('\n',Qt::SkipEmptyParts); + buffer = lines.last(); + // DEBUG: LOG_DEBUG(QString("CS [%1]").arg(buffer)); + + items = buffer.split(_separator); + count = items.count(); + + // check the required message length + if ( count < eMessage_Count ) { error = eError_HeaderCount ; ok = false; goto lErr; } + index = eMessage_Timestamp ; message.timestamp = items[index].toUInt(&ok); if (!ok) { error = eError_Timestamp ; ok = false; goto lErr; } + index = eMessage_Sequence ; message.sequence = items[index].toUInt(&ok); if (!ok) { error = eError_Sequence ; ok = false; goto lErr; } + index = eMessage_CRC ; message.crc = items[index].toUInt(&ok); if (!ok) { error = eError_CSCRC ; ok = false; goto lErr; } + index = eMessage_MessageID ; message.id = items[index].toInt (&ok); if (!ok) { error = eError_MessageID ; ok = false; goto lErr; } + index = eMessage_ParamCount; message.paramCount = items[index].toUInt(&ok); if (!ok) { error = eError_ParamCount ; ok = false; goto lErr; } + + // check the parameters count + if ( count - eMessage_Count != message.paramCount ) { error = eError_ParamMismatch ; ok = false; goto lErr; } + + // check missing parameters + id = CS2UI(message.id); + if ( ! paramCount.contains(id)) paramCount[id] = 0; + if ( message.paramCount < paramCount[id] ) { error = eError_ParamMissing ; ok = false; goto lErr; } + + // convert the message id and check its validity + message.id = CS2UI(static_cast(message.id)); + if ( eMessageID_Start > message.id || message.id > eMessageID_Count ) { error = eError_InvalidID ; ok = false; goto lErr; } + + // getting the parameters + for ( quint32 i = eMessage_Count; i < eMessage_Count + message.paramCount; i++ ) { + message.params.append( items[i] ); + } + + vMessage = message; + return true; + +lErr: + // building the error info for each error + switch (error) { + case eError_Unknown : args = { }; break; + case eError_OutFileExist : args = { }; break; + case eError_OutFileEmpty : args = { }; break; + case eError_HeaderCount : args = { count , eMessage_Count }; break; + case eError_Timestamp : args = { items[index].trimmed() }; break; + case eError_Sequence : args = { items[index].trimmed() }; break; + case eError_CSCRC : args = { items[index].trimmed() }; break; + case eError_MessageID : args = { items[index].trimmed() }; break; + case eError_InvalidID : args = { items[index].trimmed() }; break; + case eError_ParamCount : args = { items[index].trimmed() }; break; + case eError_ParamMismatch : args = { count - eMessage_Count , message.paramCount }; break; + case eError_ParamMissing : args = { paramCount[message.id] , message.paramCount }; break; + // Interpreter function only handles the message parsing errors. So default can be used. + default : break; + } + toLog(error, args); + return false; +} + +/*! + * \brief CloudSyncController::toText + * \details returns the error message of the error id. + * \param vErrorID - Error id + * \param vMessage - error message + * \return the error message or empty string if the error is 0 (no error), or Unknown Error if not found + */ +QString CloudSyncController::toText(CloudSyncController::Errors_Enum vErrorID) +{ + QString text = tr( "CS Unknown Error" ) ; + if ( vErrorID == 0 ) return text; + switch (vErrorID) { + case eError_Unknown : /* "CS Unknown Error" */ ; break; + // CS Errors + case eError_Registration : text = tr( "CS The device registration failed" ) ; break; + case eError_DeviceState : text = tr( "CS Invalid Device State" ) ; break; + case eError_TxReport : text = tr( "CS The treatment report delivery failed" ) ; break; + case eError_CheckIn : text = tr( "CS The device check-in failed" ) ; break; + case eError_Decommission : text = tr( "CS The device decommissioning failed" ) ; break; + case eError_UICRC : text = tr( "CS Invalid UI Message CRC" ) ; break; + case eError_DeviceValidation : text = tr( "CS DRT device registration failed" ) ; break; + case eError_PatientAssociation : text = tr( "CS Patient association failed" ) ; break; + case eError_GetNewTokenCert : text = tr( "CS New token certification failed" ) ; break; + case eError_VerifyToken : text = tr( "CS Token verification failed" ) ; break; + case eError_ValidateDevice : text = tr( "CS Cloud device validation failed" ) ; break; + case eError_PatientIdExists : text = tr( "CS Patient ID does not exists" ) ; break; + case eError_TemporaryPatient : text = tr( "CS Temporary patient ID creation failed" ) ; break; + case eError_SaveCredentials : text = tr( "CS Save credentials failed" ) ; break; + case eError_UnknownDeviceState : text = tr( "CS Unknown device state" ) ; break; + case eError_ConfigSave : text = tr( "CS Config file save failed" ) ; break; + case eError_DeviceLog : text = tr( "CS Log upload failed" ) ; break; + case eError_Logging : text = tr( "CS Log upload failed" ) ; break; + case eError_FactoryReset : text = tr( "CS The device factory reset failed" ) ; break; + case eError_Retention : text = tr( "CS The log retention failed" ) ; break; + // UI Errors + case eError_OutFileExist : text = tr( "CS Out buffer file does not exist" ) ; break; + case eError_OutFileEmpty : text = tr( "CS Out buffer empty" ) ; break; + case eError_HeaderCount : text = tr( "CS Incorrect header" ) ; break; + case eError_Timestamp : text = tr( "CS Incorrect timestamp" ) ; break; + case eError_Sequence : text = tr( "CS Incorrect sequence" ) ; break; + case eError_CSCRC : text = tr( "CS Incorrect CRC" ) ; break; + case eError_MessageID : text = tr( "CS Incorrect ID" ) ; break; + case eError_ParamCount : text = tr( "CS Incorrect parameter count" ) ; break; + case eError_InvalidID : text = tr( "CS Invalid ID" ) ; break; + case eError_ParamMismatch : text = tr( "CS Mismatch parameter count" ) ; break; + case eError_ParamMissing : text = tr( "CS Missing parameter" ) ; break; + case eError_NoHistory : text = tr( "CS No history available" ) ; break; + case eError_Duplicate : text = tr( "CS Duplicate data" ) ; break; + case eError_LogFolder : text = tr( "CS The log folder cannot be created." ) ; break; + case eError_LogFileInp : text = tr( "CS Error writing to the input file." ) ; break; + case eError_CredentialFile : text = tr( "CS The credentials file does not exist." ) ; break; + case eError_CredentialPath : text = tr( "CS The credentials folder is incorrect." ) ; break; + case eError_CredentialCount : text = tr( "CS No credential file name provided." ) ; break; + case eError_CredentialEmpty : text = tr( "CS The credentials folder is empty." ) ; break; + case eError_TxCodeNoParam : text = tr( "CS No Treatment Code provided." ) ; break; + case eError_TxCodeEmpty : text = tr( "CS The provided Treatment Code is empty." ) ; break; + case eError_LogNameNoParam : text = tr( "CS No Log Name provided." ) ; break; + case eError_LogNameEmpty : text = tr( "CS The provided Log Name is empty." ) ; break; + case eError_NotRegistered : text = tr( "CS Not Sent, Device not registered." ) ; break; + case eError_LogRetentionNoParam : text = tr( "CS No Log Retention summary provided." ) ; break; + + } + return text; +} + +/*! + * \brief CloudSyncController::toInfo + * \details Provides extra information, related to each error, to be concatenated later to the error description. + * \param vErrorID - the error id + * \param vInfoItems - the information items to be used in creating the info. + * \return the extra info for the error as string + */ +QString CloudSyncController::toInfo(CloudSyncController::Errors_Enum vErrorID, const QVariantList &vInfoItems) +{ + // IMPORTANT: Please be careful here used QVariantList::value to act as a loose list. + // It means it is designed to not throw out of bound error and just use "~" as a missing info argument. + auto item = [=](uint i) { return vInfoItems.value(i,"~").toString(); }; + + QString info = QString( "[%1]" ).arg( vErrorID ) ; + switch (vErrorID) { + case eError_Unknown : ; break; + // CS Errors + case eError_Registration : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_DeviceState : ; break; + case eError_TxReport : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_CheckIn : ; break; + case eError_Decommission : ; break; + case eError_UICRC : /* info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) */ ; break; + case eError_DeviceValidation : ; break; + case eError_PatientAssociation : ; break; + case eError_GetNewTokenCert : ; break; + case eError_VerifyToken : ; break; + case eError_ValidateDevice : ; break; + case eError_PatientIdExists : ; break; + case eError_TemporaryPatient : ; break; + case eError_SaveCredentials : ; break; + case eError_UnknownDeviceState : ; break; + case eError_ConfigSave : ; break; + case eError_DeviceLog : ; break; + case eError_Logging : ; break; + case eError_FactoryReset : ; break; + case eError_Retention : ; break; + // UI Errors + case eError_OutFileExist : ; break; + case eError_OutFileEmpty : ; break; + case eError_HeaderCount : info = QString( "[%1:%2/%3]" ).arg( vErrorID ).arg( item(0) ).arg( item(1) ) ; break; + case eError_Timestamp : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_Sequence : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_CSCRC : /* info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) */ ; break; + case eError_MessageID : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_InvalidID : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_ParamCount : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_ParamMismatch : info = QString( "[%1:%2/%3]" ).arg( vErrorID ).arg( item(0) ).arg( item(1) ) ; break; + case eError_ParamMissing : info = QString( "[%1:%2/%3]" ).arg( vErrorID ).arg( item(0) ).arg( item(1) ) ; break; + case eError_NoHistory : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_Duplicate : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_LogFolder : ; break; + case eError_LogFileInp : ; break; + case eError_CredentialFile : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_CredentialPath : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_CredentialCount : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_CredentialEmpty : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_TxCodeNoParam : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_TxCodeEmpty : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_LogNameNoParam : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_LogNameEmpty : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_NotRegistered : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_LogRetentionNoParam : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + } + return info; +} + +/*! + * \brief CloudSyncController::errorOut + * \details the function to send out the error message to the cloudsync inp buffer. + * \param vErrorID - the error id from CloudSyncController::Errors_Enum in range of 950 to 999 + * \param vInfoItems - + */ +void CloudSyncController::errorOut(CloudSyncController::Errors_Enum vErrorID, const QVariantList &vInfoItems) { + QString msg = QString::number(UI2CS(eMessageID_Error)); + QString len = QString::number(1 + vInfoItems.count()); + QString prm = QString::number(vErrorID); + QStringList buf = { msg, len, prm }; + for (const auto &item: vInfoItems) { + buf += item.toString(); + } + sendUIBuff(buf); +} + +/*! + * \brief CloudSyncController::toLog + * \details logs the error in debug mode (service log) + * \param vErrorID - error id + * \param vInfoItems - extra information + */ +void CloudSyncController::toLog(CloudSyncController::Errors_Enum vErrorID, const QVariantList &vInfoItems) +{ + errorOut(vErrorID, vInfoItems); + LOG_DEBUG(toText(vErrorID) + " " + toInfo(vErrorID, vInfoItems)); +} + +/*! + * \brief CloudSyncController::sendUIResponse + * \details the function to be called after reading the CloudSync out file to interpret the content of the file. + * \param vContent - the content to be interpreted. + * \return true if the content has a meaning for the interpreter, false otherwise. + */ +bool CloudSyncController::sendUIResponse(const QString &vContent) +{ + // IMPORTANT: + // please note the following in development testing it has been found out that. + // Do not use gedit. + // if the out.buf is edited by the "gedit" application the watch will be broken. + // The "nano" application works fine. + // A simple python3 code to open file and write to file works as well and that's sufficient for UI<=>CS communication. + // TODO: messages have to have a sequence. + // if the seq is duplicate it will be ignored. + // seq, id, payload + // DEBUG: qDebug() << vContent; + + bool ok = true; + Message message; + if ( ! interpret(vContent, message) ) return false; // The error handling internal to interpret method. + ok = sendMessage(message); + + return ok; +} + +/*! + * \brief CloudSyncController::testWatchBuffDate + * \details Checks the date and updated the watched file in case the date changed. + */ +void CloudSyncController::testWatchBuffDate() +{ + // prepare date values for addCSBufWatch + _datetime = QDateTime::currentDateTime().toUTC(); + _secSinceEpoch = _datetime.toSecsSinceEpoch(); + _timeFormatted = _datetime.toString(_timeFormat); + + addCSBuffWatch(); // bool out is not used here and error handling is internal to the addCSBuffWatch +} + +/*! + * \brief CloudSyncController::makeUIBuff + * \details make formatted UI buffer to send out. + * \param vMessage - The message id with destination added. 2k, 1K + * \return the formatted buffer + */ +QStringList CloudSyncController::makeUIBuff(const qint32 vMessageID, const QStringList &vPrm) +{ + int count = vPrm.count(); + QStringList prm = count ? vPrm : _uiHistory[ vMessageID ]; + QString msg = QString::number(vMessageID); + QString len = QString::number(prm.count()); + + QStringList dataList; + dataList += msg; + dataList += len; + dataList += prm; + + return dataList; +} + +/*! + * \brief CloudSyncController::isDuplicate + * \param vMessage - current action/messageID received. + * \param vData - current data to compare with the history + * \return true if duplicate + */ +bool CloudSyncController::isDuplicate(const qint32 vMessageID, const QStringList &vData) +{ + return _uiHistory.contains(vMessageID) && _uiHistory[vMessageID] == vData; +} + +/*! + * \brief CloudSyncController::writeInpFile + * \details Writes to the CS Input buffer + * \param vBuffer - the string out buffer. + * \return true if successful + */ +bool CloudSyncController::writeInpFile(const QString &vInpBuff) +{ + bool ok = true; + QVariantList args ; + Errors_Enum error = eError_Unknown; + + _date_inp_File = _location + // The location + _dateFormatted + _dateSeparator + _inp_File; // The file name + + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // writing the message into the buffer. + LOG_APPED_UI( vInpBuff ); + if(!QFile::exists(_date_inp_File)){ + Storage::FileHandler::makeFolder(_location); + } + if ( ! Storage::FileHandler::write(_date_inp_File, vInpBuff + "\n") ) { error = eError_LogFileInp; args = { _date_inp_File }; ok = false; goto lErr; } + return ok; + +lErr: + // don't send the error back to the CS + // it calls this same function if the error is in this function + // and creates a loop + LOG_DEBUG(toText(error) + " " + toInfo(error, args)); + return ok; +} + +/*! + * \brief CloudSyncController::sendUIBuff + * \details Sends the UI Buffer to the UI input file. + * \param vData - the data to be sent out. + * \return true on successful writing to the file buffer. + * \sa _inp_File + */ +bool CloudSyncController::sendUIBuff(const QStringList &vDataList) +{ + bool ok = true; + QString inpBuff = "%1,%2,%3,%4"; + + inpBuff = inpBuff + .arg( _secSinceEpoch ) + .arg( Types::safeIncrement(_seq)) + .arg( generateCRC(_secSinceEpoch, _seq, vDataList)) + .arg( vDataList.join(_separator)); + + ok = writeInpFile(inpBuff); + return ok; +} + +/*! + * \brief CloudSyncController::saveUIHistory + * \details stores the action data in history, if vData is provided. + * \param vAction - the request id, which in current design is same as the Message comes to the UI. + * \param vData - The message data + * \return true if a message history is available, false otherwise. + */ +bool CloudSyncController::saveUIHistory(const qint32 vAction, const QVariantList &vData) +{ + bool ok = true; + QVariantList args ; + Errors_Enum error = eError_Unknown; + + qint32 messageID = UI2CS(static_cast(vAction)); + QStringList data; + + if ( vData.isEmpty() ) { error = eError_NoHistory; args = { messageID }; ok = false; goto lErr; } + + data = Format::fromVariantList(vData); + + if ( isDuplicate(messageID, data) ) { error = eError_Duplicate; args = { messageID }; ok = false; return ok; } // TODO: goto lErr; } don't log it just ignore and return false. + + // store the last message data + _uiHistory[messageID] = data; + + return ok; + +lErr: + toLog(error, args); + return ok; +} + +/*! + * \brief CloudSyncController::sendUIHistory + * \details sends the saved history of the message as a response to the cloud sync upon request. + * \param vAction - the request id, which in current design is same as the Message comes to the UI. + * \return true if a message history is available, false otherwise. + */ +bool CloudSyncController::sendUIHistory(const qint32 vAction) +{ + bool ok = true; + QVariantList args ; + Errors_Enum error = eError_Unknown; + + // only sends messages if device has been registered + qint32 messageID = UI2CS(static_cast(vAction)); + if ( ! isRegistered() && vAction != eMessageID_DeviceRegister ) { error = eError_NotRegistered ; args = { messageID }; ok = false; goto lErr; } + if ( ! _uiHistory.contains( messageID ) ) { error = eError_NoHistory ; args = { messageID }; ok = false; goto lErr; } + + sendUIBuff(makeUIBuff(messageID)); + + return ok; + +lErr: + toLog(error, args); + return ok; +} + +/*! + * \brief CloudSyncController::onActionReceive + * \details The slot which will be called when a CANBus message is received, and will be sent to CloudSync if related. + * \param vAction - The message action + * \param vData - The message data + */ +void CloudSyncController::onActionReceive(GuiActionType vAction, const QVariantList &vData) +{ + // TODO: This section is the translation/mapping section + // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + // preparing the message + // Same parameters will be sent for these messages for now, so data is not changed. + + switch (vAction) { + case GuiActionType::ID_HDOperationModeData : { + // ---------------------------------------------------------------------------------------- + // DEBUG : disable the HD HW message on the device on CloudSync integration testing. + // currently it is always on fault and resets CloudSync inp log + QVariantList varData = vData; + QStringList strData = Format::fromVariantList(vData); + if ( strData.length() == 2 ) { + if ( strData[0] == "0" ) { + if ( strData[1] == "1") { varData = {0,0}; } // use Fault, wait4Tx instead of 0,0 to send fault. to able to simulate and bypass HD. + else { return; } + } + } + // ---------------------------------------------------------------------------------------- + + if (saveDeviceState(varData)) // if not empty, nor duplicate, and saved + sendDeviceState(); + } + break; + + case GuiActionType::ID_AdjustSerialHDRsp : + case GuiActionType::ID_AdjustSerialDGRsp : + // this message is not complete in one step and saves only the received info and waits for the other if complete will send. + saveDeviceInfo(vAction, vData); + break; + + default: + break; + } +} + +void CloudSyncController::sendPendingTxr(const QString &vFileName) +{ + bool ok = true; Q_UNUSED(ok) + QVariantList args ; + Errors_Enum error = eError_Unknown; + qint32 messageID = UI2CS(static_cast( eMessageID_TxReport )); + // DEBUG ok = false; // using the ok bool which is true as the debug flag to bypass the registration on debug testing. + if ( ok && ! isRegistered() ) { error = eError_NotRegistered ; args = { vFileName }; ok = false; goto lErr; } + sendUIBuff(makeUIBuff( messageID , { vFileName } )); + + return; +lErr: + toLog(error, args); +} + +void CloudSyncController::onPendingTxr(const QString &vFileName) +{ + sendPendingTxr(vFileName); +} + +/*! + * \brief CloudSyncController::onPendingLog + * \details The signal handler to call the function to send the CloudSync a message to upload the pending file. + * \param vFileName - The pending file name + * \param vChecksum - The sha256sum of the file content + */ +void CloudSyncController::onPendingLog(const QString &vFileName, const QString vChecksum) +{ + sendPendingLog(vFileName, vChecksum); +} + +/*! + * \brief CloudSyncController::sendPendingLog + * \details The function to send CloudSync a message to uplaod the pending log file. + * \param vFileName - The pending file name + * \param vChecksum - The sha256sum of the file content + */ +void CloudSyncController::sendPendingLog(const QString &vFileName, const QString vChecksum) +{ + bool ok = true; Q_UNUSED(ok) + QVariantList args ; + Errors_Enum error = eError_Unknown; + qint32 messageID = UI2CS(static_cast( eMessageID_SendLogUpload )); + // DEBUG ok = false; // using the ok bool which is true as the debug flag to bypass the registration on debug testing. + if ( ok && ! isRegistered() ) { error = eError_NotRegistered ; args = { vFileName }; ok = false; goto lErr; } + sendUIBuff(makeUIBuff( messageID , { vFileName, vChecksum } )); + + return; +lErr: + toLog(error, args); +} + +/*! + * \brief CloudSyncController::rcvdPendingLog + * \details reads the received Log Name from CloudSync app and notifies with a signal. + * \param vMessage : message containing the uploaded Log name. + * \return true on successful extracting the Log Name. + */ +bool CloudSyncController::rcvdPendingLog(const Message &vMessage) +{ + bool ok = true; + QString mLogName; + // although it has been checked in the interpreter, we won't risk the crash and check the list empty. + if ( vMessage.params.isEmpty() ) { toLog(eError_LogNameNoParam , {}); ok = false; goto lOut; } + mLogName = vMessage.params[0].trimmed(); + if ( mLogName.isEmpty() ) { toLog(eError_LogNameEmpty , {}); ok = false; goto lOut; } + //DEBUG qDebug() << " ---------- " << mLogName; + emit didLogUpload( mLogName ); + + lOut: + return ok; +} + +/// -------------------------------------------------- Retention Log +/*! + * \brief CloudSyncController::onRetentionLog + * \details The slot tha handles the received even to request for the log retention + * \param vMaxUsePercent - the maximum size of the SD-Card (log storage device) cloudsync can get for the logs + */ +void CloudSyncController::onRetentionLog(quint8 vMaxUsePercent) +{ + sendRetentionLog(vMaxUsePercent); +} + +void CloudSyncController::sendRetentionLog(quint8 vMaxUsePercent) +{ + bool ok = true; Q_UNUSED(ok) + QVariantList args ; + Errors_Enum error = eError_Unknown; + qint32 messageID = UI2CS(static_cast( eMessageID_SendLogRetention )); + // DEBUG ok = false; // using the ok bool which is true as the debug flag to bypass the registration on debug testing. + if ( ok && ! isRegistered() ) { error = eError_NotRegistered ; args = { eMessageID_SendLogRetention, vMaxUsePercent }; ok = false; goto lErr; } + sendUIBuff(makeUIBuff( messageID , { QString::number(vMaxUsePercent) } )); + + return; +lErr: + toLog(error, args); +} + +bool CloudSyncController::rcvdRetentionLog(const Message &vMessage) +{ + bool ok = true; + QString tmp; + quint16 mLogsCount; // number of deleted logs + quint32 mLogsSize ; // total current logs size + // although it has been checked in the interpreter, we won't risk the crash and check the list empty. + if ( vMessage.params.isEmpty() ) { toLog(eError_LogRetentionNoParam , {eMessageID_SendLogRetention }); ok = false; goto lOut; } + if ( vMessage.params.count () < paramCount[eMessageID_SendLogRetention]) { toLog(eError_ParamCount , {eMessageID_SendLogRetention }); ok = false; goto lOut; } + + tmp = vMessage.params[0]; + mLogsCount = tmp.toUInt(&ok); + if ( ! ok ) { toLog(eError_ParamMismatch , {eMessageID_SendLogRetention, 0}); ok = false; goto lOut; } + + tmp = vMessage.params[1]; + mLogsSize = tmp.toUInt(&ok); + if ( ! ok ) { toLog(eError_ParamMismatch , {eMessageID_SendLogRetention, 1}); ok = false; goto lOut; } + + //DEBUG qDebug() << " ---------- " << mLogsCount << mLogsSize; + emit didLogRetention( mLogsCount, mLogsSize ); + + LOG_APPED_CS(QString("Log retention deleted %1 files of total %2 MB.").arg(mLogsCount).arg(mLogsSize)); + + lOut: + return ok; +} +/// -------------------------------------------------- + +/*! + * \brief CloudSyncController::sendMessage + * \details Makes and Sends the appropriate message for the vAction. + * Some messages are sent out upon request form the last received on the history and will not be asked from FW. + * \return true if there is a history for that message and no error happened. + */ +bool CloudSyncController::sendMessage(const Message &vMessage) { + bool ok = false; + // we are using the file system for the UI<=>CS communication, + // and the file events are multiple during the write to the file. + // if a write to the _out.buf trigeres the message to be sent more than once, + // ignore the same message, with the same sequence number. + static quint32 sequence ; + if ( sequence == vMessage.sequence ) return ok; + sequence = vMessage.sequence ; + + //DEBUG qDebug() << Q_FUNC_INFO << vMessage.id; + // this function is used in sendUIResponse, therefore the message IDs which are responses should be implemented here. + switch (vMessage.id) { + case eMessageID_DeviceRegister : /* No Request/Response */ break; + case eMessageID_CheckIn : ok = takeCheckIn ( ); break; + case eMessageID_DeviceInfo : ok = sendDeviceInfo ( ); break; + case eMessageID_CredentialsSave : ok = sendCredentialsSave( vMessage ); break; + case eMessageID_UIFactoryReset : ok = sendFactoryReset ( ); break; + case eMessageID_DeviceState : ok = sendDeviceState ( ); break; + + case eMessageID_TxCodeDisplay : ok = sendTxCodeDisplay ( vMessage ); break; + case eMessageID_SendLogUpload : ok = rcvdPendingLog ( vMessage ); break; + case eMessageID_SendLogRetention : ok = rcvdRetentionLog ( vMessage ); break; + + case eMessageID_TxReport : /* No Req/Rsp, it is event based */ break; // This message doesn't have the response since there is no request. UI will send when the data ready by HD. + + case eMessageID_Error : ok = errorHandler ( vMessage ); break; + + } + return ok; +} + +/*! + * \brief CloudSyncController::saveDeviceState + * \details Saves the device state in the history for later request + * \param vData - the device information + * \return + */ +bool CloudSyncController::saveDeviceState(const QVariantList &vData) +{ + bool ok = false; + ok = saveUIHistory(eMessageID_DeviceState, vData); + return ok; +} + +/*! + * \brief CloudSyncController::sendDeviceState + * \return Sends the last received device state from history to CloudSync + */ +bool CloudSyncController::sendDeviceState() +{ + bool ok = false; + ok = sendUIHistory(eMessageID_DeviceState); + return ok; +} + +/*! + * \brief CloudSyncController::csFactoryReset + * \details This function is requesting CloudSync to remove all the logs + * in the logs folder (/media/sd-card/cloudsync/log/) + * during Factory Reset + * It is a request from UI to CS since UI Linux user (denali) should not have access + * to remove the CloudSync logs. + */ +bool CloudSyncController::csFactoryReset() +{ + + bool ok = true; + return ok; // NOT IMPLEMENTED + + QVariantList args ; + Errors_Enum error = eError_Unknown; + qint32 messageID = UI2CS(static_cast( eMessageID_CSFactoryReset )); + + if ( ! isRegistered() ) { error = eError_NotRegistered ; args = {}; ok = false; goto lErr; } + sendUIBuff(makeUIBuff( messageID , { } )); + + return ok; +lErr: + toLog(error, args); + return ok; +} + +/*! + * \brief CloudSyncController::Decommissioning + * \details This function is requesting CloudSync to remove all the credentials and configurations + * in the settings partition (/var/configurations/CloudSync) + * during decommissioning + * which then it means the device needs to be re-registered to able to communicate with cloud. + * It is a request from UI to CS since UI Linux user (denali) should not have access + * to remove the CloudSync credentials. + */ +bool CloudSyncController::csDecommissioning() +{ + bool ok = true; + QVariantList args ; + Errors_Enum error = eError_Unknown; + qint32 messageID = UI2CS(static_cast( eMessageID_CSDecommissioning )); + + if ( ! isRegistered() ) { error = eError_NotRegistered ; args = {}; ok = false; goto lErr; } + sendUIBuff(makeUIBuff( messageID , { } )); + + return ok; +lErr: + toLog(error, args); + return ok; +} + +/*! + * \brief CloudSyncController::factoryReset + * \details does the reset factory + * \return true on successful reset + */ +bool CloudSyncController::uiFactoryReset() +{ + // reset factory has not been implemented yet. + bool ok = true; + // TODO: call the UI Software Reset Factory here when it has implemented. + LOG_DEBUG("CloudSync Reset factory request has not been implemented yet."); + return ok; +} + +/*! + * \brief CloudSyncController::sendFactoryReset + * \details sends the factory reset response to CloudSync + * \return true on successful send. + */ +bool CloudSyncController::sendFactoryReset() +{ + enum { eSucceed, eFailed }; + bool ok = false; + ok = uiFactoryReset(); + // if ( ! ok ) { } /* Not defined */ + qint32 messageID = UI2CS(eMessageID_UIFactoryReset); + // TODO : Store registration success in here + ok = sendUIBuff({ QString("%1").arg( messageID ) ,"1", QString("%1").arg( ok ? eSucceed : eFailed ) }); + return ok; +} + +/*! + * \brief CloudSyncController::sendCredentialsSave + * \details validates the credentials files which their sent location. + * \param vMessage - the message received from CloudSync + * \return true on successfully validate the files. + */ +bool CloudSyncController::sendCredentialsSave( const Message &vMessage) +{ + bool ok = true; + QString destination = QString(Storage::CloudSync_Base_Path_Name) + Storage::CloudSync_Credentials_Folder_Name; + + // file existence has been separated from copy remove to avoid partial copy. + if ( ! vMessage.params.count() ) { toLog(eError_CredentialCount , { }); ok = false; goto lOut; } + for ( auto fileName : vMessage.params ) { + QFileInfo fileinfo(fileName); + if ( ! fileinfo.exists() ) { toLog(eError_CredentialFile , {fileName }); ok = false; goto lOut; } + if ( fileinfo.absolutePath() != destination ) { toLog(eError_CredentialPath , {fileName }); ok = false; goto lOut; } + } + // no need to call for isRegistered() function, we are testing for the count, location, and existence. + +lOut: + if ( ok ) sendCredentialsResponse(); + emit didRegisterDone(ok); + testReady(); + return ok; +} + +/*! + * \brief CloudSyncController::sendCredentialsResponse + * \return send the confirmation of the credential save on the device from UI to CS to let CS know when to delete them. + */ +bool CloudSyncController::sendCredentialsResponse() +{ + enum { eSucceed, eFailed }; + bool ok = false; + qint32 messageID = UI2CS(eMessageID_CredentialsSave); + ok = sendUIBuff({ QString("%1").arg( messageID ) ,"1", QString("%1").arg( ok ? eSucceed : eFailed ) }); + return ok; +} + +/*! + * \brief CloudSyncController::sendCheckIn + * \details Send a check-in message and expects the same check-in message from CS + * \return + */ +bool CloudSyncController::sendCheckIn() +{ + _checkinRcvd = false; + qint32 messageID = UI2CS(eMessageID_CheckIn); + return sendUIBuff( { QString("%1").arg( messageID ) ,"0" } ); +} + +/*! + * \brief CloudSyncController::testCheckIn + * \details + * \return + */ +void CloudSyncController::testCheckIn() +{ + if ( ! _checkinRcvd ) { + LOG_APPED_UI(QString("CloudSync check-in failed")); + } +} + +/*! + * \brief CloudSyncController::takeCheckIn + * \details if the check-in received this method will be called + * \return true - + */ +bool CloudSyncController::takeCheckIn() +{ + _checkinRcvd = true; + emit didCheckInReceive(); + return true; +} + +/*! + * \brief CloudSyncController::sendTxCodeDisplay + * \details reads the received Tx Code from CloudSync app and notifies with a signal. + * \param vMessage : message containing the Tx Code. + * \return true on successful extracting the Tc Code. + */ +bool CloudSyncController::sendTxCodeDisplay(const CloudSyncController::Message &vMessage) +{ + bool ok = true; + QString mTxCode; + + // although it has been checked in the interpreter, we won't risk the crash and check the list empty. + if ( vMessage.params.isEmpty() ) { toLog(eError_TxCodeNoParam , {}); ok = false; goto lOut; } + + mTxCode = vMessage.params[0].trimmed(); + if ( mTxCode.isEmpty() ) { toLog(eError_TxCodeEmpty , {}); ok = false; goto lOut; } + + //DEBUG qDebug() << " ---------- " << mTxCode; + emit didTxCodeReceive ( mTxCode ); + +lOut: + return ok; +} + +/*! + * \brief CloudSyncController::errorHandler + * \details nothing for now. + * \param vMessage - the received message + * \return always true for now. + */ +bool CloudSyncController::errorHandler(const Message &vMessage) +{ + qDebug() << "ERR: " << vMessage.params; + return true; +} + +/*! + * \brief CloudSyncController::sendDeviceRegister + * \details sends the device registration request + * \return true on successful send. + */ +bool CloudSyncController::sendDeviceRegister() +{ + bool ok = false; + ok = sendUIHistory(eMessageID_DeviceRegister); + return ok; +} + +/*! + * \brief CloudSyncController::isRegistered + * \details checks if the device is registered by looking for existing credentials. + * \return true if device has been registered. + */ +bool CloudSyncController::isRegistered() +{ +#ifdef BUILD_FOR_DESKTOP + return true; +#else + QString source = QString(Storage::CloudSync_Base_Path_Name) + Storage::CloudSync_Credentials_Folder_Name; + QFileInfoList fileInfos = QDir(source).entryInfoList(QDir::NoDotAndDotDot|QDir::Files); + return !fileInfos.isEmpty(); +#endif +} + +/*! + * \brief CloudSyncController::testReady + * \details Checks if the CloudSync POST was passed and the device registration is complete. + */ +void CloudSyncController::testReady() +{ + bool ok = _postPass && isRegistered(); +#ifdef BUILD_FOR_DESKTOP + ok = true; +#endif + emit didCloudSyncStatus( ok + || gDisableCloudSyncFailStop // Development testability + ); +} + +/*! + * \brief CloudSyncController::saveDeviceInfo + * \details keeps the received device information and set a flag to wait for the next message. + * \param vAction - the action enum which identifies information source of HD or DG. + * \param vData - the device information + * \param vTimedOut - if true the missing device information will be filled by zero and will be sent anyway. + * \return + */ +bool CloudSyncController::saveDeviceInfo(GuiActionType vAction, const QVariantList &vData) +{ + bool ok = false; + + // the messages are coming from different sources and the order of receiving could not be guessed. + // so for each of these two we check and fill the designated variable + // if both values received + initDeviceInfoWait(); + + if ( vAction == GuiActionType::ID_AdjustSerialHDRsp ) { + if ( vData.count() ) { + _deviceInfoHD = vData[eDeviceInfo_Ix].toString(); + } else { + _deviceInfoHD = ""; + } + } + if ( vAction == GuiActionType::ID_AdjustSerialDGRsp ) { + if ( vData.count() ) { + _deviceInfoDG = vData[eDeviceInfo_Ix].toString(); + } else { + _deviceInfoDG = ""; + } + } + + if ( !_deviceInfoHD.isEmpty() && !_deviceInfoDG.isEmpty() ) { + saveDeviceInfoTimeOut(); + ok = true; + } + return ok; +} + +/*! + * \brief CloudSyncController::saveDeviceInfoTimeOut + * \details send the Device information regardless of the information filled or not. + * \return true on successful save and send. + */ +bool CloudSyncController::saveDeviceInfoTimeOut() +{ + bool ok = false; + if ( _deviceInfoUI.isEmpty() ) { _deviceInfoUI = qApp->applicationVersion(); } + saveUIHistory(eMessageID_DeviceInfo , { _deviceInfoHD, _deviceInfoDG, _deviceInfoUI } ); + saveUIHistory(eMessageID_DeviceRegister , { _deviceInfoHD, _deviceInfoDG, _deviceInfoUI } ); // the device registration request format is the same as Device info with different message id. + + sendDeviceInfo (); // this one may need to be removed and only will be sent on the request + + stopDeviceInfoWait(); + + return ok; +} + +/*! + * \brief CloudSyncController::sendDeviceInfo + * \details sends the devices information if both the HD and DG information are ready otherwise will fill the missing with 0 if the vTimeout is set to true. + * \return + */ +bool CloudSyncController::sendDeviceInfo() +{ + bool ok = false; + ok = sendUIHistory(eMessageID_DeviceInfo); + return ok; +} + +/*! + * \brief CloudSyncController::testDeviceInfoWait + * \details tests the wait conditions if there is a wait, decrements until times out and sends the device info. + */ +void CloudSyncController::testDeviceInfoWait() +{ + if (_deviceInfoStop ) return; + // DEBUG: qDebug() << _deviceInfoWait; + if (_deviceInfoWait ){ + _deviceInfoWait -- ; + } + else { + saveDeviceInfoTimeOut(); + } +} + +/*! + * \brief CloudSyncController::stopDeviceInfoWait + * \details stops waiting for the device info. + */ +void CloudSyncController::stopDeviceInfoWait() { + _deviceInfoStop = true; + // NOTE: if it is accepted to use the last received info just comment these 3 cleanup lines. + // so the last message will update the history only and it will be sent out. + _deviceInfoHD = ""; + _deviceInfoDG = ""; + _deviceInfoUI = ""; +} + +/*! + * \brief CloudSyncController::initDeviceInfoWait + * \details if currently not in wait, then set the wait to the secs of wait. + */ +void CloudSyncController::initDeviceInfoWait() { + if ( _deviceInfoStop ) { // if timer is stopped + _deviceInfoWait = _deviceInfoSecs; // enable the timer. + _deviceInfoStop = false; + } +} + +/*! + * \brief CloudSyncController::generateCRC + * \details The crc algorithm is to get + * all the message sections including, epoch, seq, and the parameters, + * without any separator + * concatenate them together like a one long string + * and run the crc on them + * \example 1674193951,4,,1001,3,HD1234567890123,DG1234567890123,DEN-14517-UI-BN-S88.01192111.1 + * str: 1674193951410013HD1234567890123DG1234567890123DEN-14517-UI-BN-S88.01192111.1 + * out: 145 + * \note the parameters values will keep untouched, meaning, if a parameter has space it will not be removed. + * \param vSecSinceEpoch + * \param vSeq - the message sequence + * \param vData - the message payload in list of strings + * \return - quint8 crc code + */ +quint8 CloudSyncController::generateCRC(quint64 vSecSinceEpoch, quint64 vSeq, const QStringList &vDataList) +{ + quint8 crc = 0; + QByteArray buf; + buf += QByteArray::number(vSecSinceEpoch); + buf += QByteArray::number(vSeq); + buf += vDataList.join("").toLatin1(); + crc = crc8(buf); + return crc; +}