/*! * * 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) 24-Apr-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() { quitThread(); // validated } /*! * \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())); moveToThread(_thread); _thread->start(); } /*! * \brief CloudSyncController::quitThread * \details Moves this object to main thread to be handled by QCloudSync * And to be destroyed there. */ void CloudSyncController::quitThread() { if ( ! _thread ) return; // runs in thread moveToThread(qApp->thread()); // validated } /*! * \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.trimmed().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 id = message.id; // keep the original recevied message id after we are done using the id for loggging. 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; LOG_APPED_CS(QString("%1,%2").arg(id).arg(message.params.join(','))); 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_TDOpModeData : { // ---------------------------------------------------------------------------------------- // 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_AdjustSerialTDRsp : case GuiActionType::ID_AdjustSerialDDRsp : // 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_AdjustSerialTDRsp ) { if ( vData.count() ) { _deviceInfoHD = vData[eDeviceInfo_Ix].toString(); } else { _deviceInfoHD = ""; } } if ( vAction == GuiActionType::ID_AdjustSerialDDRsp ) { 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(); } if ( _deviceInfoDG.isEmpty() ) { _deviceInfoDG = "DGNotPresent"; } 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; }