diff --git a/Svc/PrmDb/PrmDb.fpp b/Svc/PrmDb/PrmDb.fpp index eff26ecb4b..d6325c9147 100644 --- a/Svc/PrmDb/PrmDb.fpp +++ b/Svc/PrmDb/PrmDb.fpp @@ -7,6 +7,20 @@ module Svc { # Types # ---------------------------------------------------------------------- + @ Parameter DB type + enum PrmDbType { + DB_ACTIVE, + DB_STAGING + } + + @ State of parameter DB file load operations + enum PrmDbFileLoadState { + IDLE, + LOADING_FILE_UPDATES, + FILE_UPDATES_STAGED, + } + + @ Parameter read error enum PrmReadError { OPEN @@ -77,83 +91,13 @@ module Svc { # Commands # ---------------------------------------------------------------------- - @ Command to save parameter image to file. Uses file name passed to constructor - async command PRM_SAVE_FILE \ - opcode 0 + include "PrmDbCmdDict.fppi" # ---------------------------------------------------------------------- # Events # ---------------------------------------------------------------------- - @ Parameter ID not found in database. - event PrmIdNotFound( - Id: FwPrmIdType @< The parameter ID - ) \ - severity warning low \ - id 0 \ - format "Parameter ID 0x{x} not found" \ - throttle 5 - - @ Parameter ID updated in database - event PrmIdUpdated( - Id: FwPrmIdType @< The parameter ID - ) \ - severity activity high \ - id 1 \ - format "Parameter ID 0x{x} updated" - - @ Parameter database is full - event PrmDbFull( - Id: FwPrmIdType @< The parameter ID - ) \ - severity fatal \ - id 2 \ - format "Parameter DB full when adding ID 0x{x} " - - - @ Parameter ID added to database - event PrmIdAdded( - Id: FwPrmIdType @< The parameter ID - ) \ - severity activity high \ - id 3 \ - format "Parameter ID 0x{x} added" - - @ Failed to write parameter file - event PrmFileWriteError( - stage: PrmWriteError @< The write stage - $record: I32 @< The record that had the failure - error: I32 @< The error code - ) \ - severity warning high \ - id 4 \ - format "Parameter write failed in stage {} with record {} and error {}" - - @ Save of parameter file completed - event PrmFileSaveComplete( - records: U32 @< The number of records saved - ) \ - severity activity high \ - id 5 \ - format "Parameter file save completed. Wrote {} records." - - @ Failed to read parameter file - event PrmFileReadError( - stage: PrmReadError @< The read stage - $record: I32 @< The record that had the failure - error: I32 @< The error code - ) \ - severity warning high \ - id 6 \ - format "Parameter file read failed in stage {} with record {} and error {}" - - @ Load of parameter file completed - event PrmFileLoadComplete( - records: U32 @< The number of records loaded - ) \ - severity activity high \ - id 7 \ - format "Parameter file load completed. Read {} records." + include "PrmDbEventDict.fppi" } diff --git a/Svc/PrmDb/PrmDbCmdDict.fppi b/Svc/PrmDb/PrmDbCmdDict.fppi index 2a8801048a..15d188a579 100644 --- a/Svc/PrmDb/PrmDbCmdDict.fppi +++ b/Svc/PrmDb/PrmDbCmdDict.fppi @@ -1,3 +1,18 @@ +enum Merge : U8{ + MERGE, + RESET +} @ Command to save parameter image to file. Uses file name passed to constructor async command PRM_SAVE_FILE \ - opcode 0 + opcode 0x00 + +@ Loads a file from storage into the staging database. The file could have selective IDs and not the whole set. +async command PRM_LOAD_FILE( + fileName: string size FileNameStringSize @< The name of the on-board file to set parameters from + merge: Merge @< Whether to merge or fully reset the parameter database from the file contents + ) \ + opcode 0x01 + +@ Commits the backup database to become the prime (active) database +async command PRM_COMMIT_STAGED \ + opcode 0x02 diff --git a/Svc/PrmDb/PrmDbEventDict.fppi b/Svc/PrmDb/PrmDbEventDict.fppi new file mode 100644 index 0000000000..e0b362c6b8 --- /dev/null +++ b/Svc/PrmDb/PrmDbEventDict.fppi @@ -0,0 +1,109 @@ +enum PrmLoadAction : U8 { + SET_PARAMETER, + SAVE_FILE_COMMAND, + LOAD_FILE_COMMAND, + COMMIT_STAGED_COMMAND, +} + +@ Parameter ID not found in database. +event PrmIdNotFound( + Id: FwPrmIdType @< The parameter ID + ) \ + severity warning low \ + id 0 \ + format "Parameter ID 0x{x} not found" \ + throttle 5 + +@ Parameter ID updated in database +event PrmIdUpdated( + Id: FwPrmIdType @< The parameter ID + ) \ + severity activity high \ + id 1 \ + format "Parameter ID 0x{x} updated" + +@ Parameter database is full +event PrmDbFull( + Id: FwPrmIdType @< The parameter ID + ) \ + severity warning high \ + id 2 \ + format "Parameter DB full when adding ID 0x{x} " + + +@ Parameter ID added to database +event PrmIdAdded( + Id: FwPrmIdType @< The parameter ID + ) \ + severity activity high \ + id 3 \ + format "Parameter ID 0x{x} added" + +@ Failed to write parameter file +event PrmFileWriteError( + stage: PrmWriteError @< The write stage + $record: I32 @< The record that had the failure + error: I32 @< The error code + ) \ + severity warning high \ + id 4 \ + format "Parameter write failed in stage {} with record {} and error {}" + +@ Save of parameter file completed +event PrmFileSaveComplete( + records: U32 @< The number of records saved + ) \ + severity activity high \ + id 5 \ + format "Parameter file save completed. Wrote {} records." + +@ Failed to read parameter file +event PrmFileReadError( + stage: PrmReadError @< The read stage + $record: I32 @< The record that had the failure + error: I32 @< The error code + ) \ + severity warning high \ + id 6 \ + format "Parameter file read failed in stage {} with record {} and error {}" + +@ Load of parameter file completed +event PrmFileLoadComplete( + databaseString: string @< The database string + recordsTotal: U32 @< The number of records total + recordsAdded: U32 @< The number of new records added + recordsUpdated: U32 @< The number of records updated + ) \ + severity activity high \ + id 7 \ + format "Parameter file load completed. Database: {}, Records: {} ({} added and {} updated)." + +@ Committed staged parameter updates +event PrmDbCommitComplete() \ + severity activity high \ + id 8 \ + format "Parameter DB commit complete, staged updates are now active." + +@ All parameters Copied from one DB to another +event PrmDbCopyAllComplete( + databaseStringSrc: string @< The src database string + databaseStringDest: string @< The dest database string + ) \ + severity activity high \ + id 9 \ + format "All parameters copied. Source database: {}, Destination database: {}." + +@ Parameter file load failed, not staging any update +event PrmDbFileLoadFailed() \ + severity warning high \ + id 10 \ + format "Parameter file load failed. Clearing staging database and abandoning parameter file load." + +@ Invalid Action during parameter file load +event PrmDbFileLoadInvalidAction( + currentState: PrmDbFileLoadState @< The current state + attemptedAction: PrmLoadAction @< The invalid action attempted +) \ + severity warning low \ + id 11 \ + format "Invalid action during parameter file load. Current state: {}, Action (Invalid for current state): {}." diff --git a/Svc/PrmDb/PrmDbImpl.cpp b/Svc/PrmDb/PrmDbImpl.cpp index 7af03dfa95..a854898745 100644 --- a/Svc/PrmDb/PrmDbImpl.cpp +++ b/Svc/PrmDb/PrmDbImpl.cpp @@ -18,8 +18,6 @@ static_assert(std::numeric_limits::max() >= PRMDB_NUM_DB_ENTRIES, namespace Svc { -typedef PrmDb_PrmWriteError PrmWriteError; -typedef PrmDb_PrmReadError PrmReadError; // anonymous namespace for buffer declaration namespace { class WorkingBuffer : public Fw::SerializeBufferBase { @@ -36,22 +34,41 @@ class WorkingBuffer : public Fw::SerializeBufferBase { }; } // namespace -PrmDbImpl::PrmDbImpl(const char* name) : PrmDbComponentBase(name) { - this->clearDb(); +//! ---------------------------------------------------------------------- +//! Construction, initialization, and destruction +//! ---------------------------------------------------------------------- +PrmDbImpl::PrmDbImpl(const char* name) : PrmDbComponentBase(name), m_state(PrmDbFileLoadState::IDLE) { + this->m_activeDb = this->m_dbStore1; + this->m_stagingDb = this->m_dbStore2; + + this->clearDb(PrmDbType::DB_ACTIVE); + this->clearDb(PrmDbType::DB_STAGING); } +PrmDbImpl::~PrmDbImpl() {} + void PrmDbImpl::configure(const char* file) { FW_ASSERT(file != nullptr); this->m_fileName = file; } -void PrmDbImpl::clearDb() { - for (FwSizeType entry = 0; entry < PRMDB_NUM_DB_ENTRIES; entry++) { - this->m_db[entry].used = false; - this->m_db[entry].id = 0; - } +void PrmDbImpl::readParamFile() { + // Assumed to run at initialization time + // State should be IDLE upon entry + FW_ASSERT(static_cast(m_state == PrmDbFileLoadState::IDLE)); + + // Clear databases + this->clearDb(PrmDbType::DB_ACTIVE); + this->clearDb(PrmDbType::DB_STAGING); + + // Read parameter file to active database + (void)readParamFileImpl(this->m_fileName, PrmDbType::DB_ACTIVE); } +//! ---------------------------------------------------------------------- +//! Port & Command Handlers +//! ---------------------------------------------------------------------- + // If ports are no longer guarded, these accesses need to be protected from each other // If there are a lot of accesses, perhaps an interrupt lock could be used instead of guarded ports @@ -60,9 +77,9 @@ Fw::ParamValid PrmDbImpl::getPrm_handler(FwIndexType portNum, FwPrmIdType id, Fw Fw::ParamValid stat = Fw::ParamValid::INVALID; for (FwSizeType entry = 0; entry < PRMDB_NUM_DB_ENTRIES; entry++) { - if (this->m_db[entry].used) { - if (this->m_db[entry].id == id) { - val = this->m_db[entry].val; + if (this->m_activeDb[entry].used) { + if (this->m_activeDb[entry].id == id) { + val = this->m_activeDb[entry].val; stat = Fw::ParamValid::VALID; break; } @@ -78,47 +95,40 @@ Fw::ParamValid PrmDbImpl::getPrm_handler(FwIndexType portNum, FwPrmIdType id, Fw } void PrmDbImpl::setPrm_handler(FwIndexType portNum, FwPrmIdType id, Fw::ParamBuffer& val) { - this->lock(); - - // search for existing entry - - bool existingEntry = false; - bool noSlots = true; - - for (FwSizeType entry = 0; entry < PRMDB_NUM_DB_ENTRIES; entry++) { - if ((this->m_db[entry].used) && (id == this->m_db[entry].id)) { - this->m_db[entry].val = val; - existingEntry = true; - break; - } + // Reject parameter updates during non-idle file load states + if (m_state != PrmDbFileLoadState::IDLE) { + this->log_WARNING_LO_PrmDbFileLoadInvalidAction(m_state, PrmDb_PrmLoadAction::SET_PARAMETER); + return; } - // if there is no existing entry, add one - if (!existingEntry) { - for (FwSizeType entry = 0; entry < PRMDB_NUM_DB_ENTRIES; entry++) { - if (!(this->m_db[entry].used)) { - this->m_db[entry].val = val; - this->m_db[entry].id = id; - this->m_db[entry].used = true; - noSlots = false; - break; - } - } - } + // Update the parameter in the active database + PrmUpdateType update_status = updateAddPrmImpl(id, val, PrmDbType::DB_ACTIVE); - this->unLock(); - - if (existingEntry) { + // Issue relevant EVR + if (update_status == PARAM_UPDATED) { this->log_ACTIVITY_HI_PrmIdUpdated(id); - } else if (noSlots) { - this->log_FATAL_PrmDbFull(id); + } else if (update_status == NO_SLOTS) { + this->log_WARNING_HI_PrmDbFull(id); } else { this->log_ACTIVITY_HI_PrmIdAdded(id); } } +void PrmDbImpl::pingIn_handler(FwIndexType portNum, U32 key) { + // respond to ping + this->pingOut_out(0, key); +} + void PrmDbImpl::PRM_SAVE_FILE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) { + // Reject PRM_SAVE_FILE command during non-idle file load states + if (m_state != PrmDbFileLoadState::IDLE) { + this->log_WARNING_LO_PrmDbFileLoadInvalidAction(m_state, PrmDb_PrmLoadAction::SAVE_FILE_COMMAND); + this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::BUSY); + return; + } + FW_ASSERT(this->m_fileName.length() > 0); + Os::File paramFile; WorkingBuffer buff; @@ -130,13 +140,15 @@ void PrmDbImpl::PRM_SAVE_FILE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) { } this->lock(); + t_dbStruct* db = getDbPtr(PrmDbType::DB_ACTIVE); + FW_ASSERT(db != nullptr); // Traverse the parameter list, saving each entry U32 numRecords = 0; - for (FwSizeType entry = 0; entry < FW_NUM_ARRAY_ELEMENTS(this->m_db); entry++) { - if (this->m_db[entry].used) { + for (FwSizeType entry = 0; entry < PRMDB_NUM_DB_ENTRIES; entry++) { + if (db[entry].used) { // write delimiter static const U8 delim = PRMDB_ENTRY_DELIMITER; FwSizeType writeSize = static_cast(sizeof(delim)); @@ -155,7 +167,7 @@ void PrmDbImpl::PRM_SAVE_FILE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) { return; } // serialize record size = id field + data - U32 recordSize = static_cast(sizeof(FwPrmIdType) + this->m_db[entry].val.getBuffLength()); + U32 recordSize = static_cast(sizeof(FwPrmIdType) + db[entry].val.getBuffLength()); // reset buffer buff.resetSer(); @@ -185,7 +197,7 @@ void PrmDbImpl::PRM_SAVE_FILE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) { // serialize parameter id - serStat = buff.serializeFrom(this->m_db[entry].id); + serStat = buff.serializeFrom(db[entry].id); // should always work FW_ASSERT(Fw::FW_SERIALIZE_OK == serStat, static_cast(serStat)); @@ -208,8 +220,8 @@ void PrmDbImpl::PRM_SAVE_FILE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) { // write serialized parameter value - writeSize = static_cast(this->m_db[entry].val.getBuffLength()); - stat = paramFile.write(this->m_db[entry].val.getBuffAddr(), writeSize, Os::File::WaitType::WAIT); + writeSize = static_cast(db[entry].val.getBuffLength()); + stat = paramFile.write(db[entry].val.getBuffAddr(), writeSize, Os::File::WaitType::WAIT); if (stat != Os::File::OP_OK) { this->unLock(); this->log_WARNING_HI_PrmFileWriteError(PrmWriteError::PARAMETER_VALUE, static_cast(numRecords), @@ -217,7 +229,7 @@ void PrmDbImpl::PRM_SAVE_FILE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) { this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR); return; } - if (writeSize != static_cast(this->m_db[entry].val.getBuffLength())) { + if (writeSize != static_cast(db[entry].val.getBuffLength())) { this->unLock(); this->log_WARNING_HI_PrmFileWriteError(PrmWriteError::PARAMETER_VALUE_SIZE, static_cast(numRecords), static_cast(writeSize)); @@ -233,24 +245,95 @@ void PrmDbImpl::PRM_SAVE_FILE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) { this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); } -PrmDbImpl::~PrmDbImpl() {} +void PrmDbImpl::PRM_LOAD_FILE_cmdHandler(FwOpcodeType opCode, + U32 cmdSeq, + const Fw::CmdStringArg& fileName, + PrmDb_Merge merge) { + // Reject PRM_LOAD_FILE command during non-idle file load states + if (m_state != PrmDbFileLoadState::IDLE) { + this->log_WARNING_LO_PrmDbFileLoadInvalidAction(m_state, PrmDb_PrmLoadAction::LOAD_FILE_COMMAND); + this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::BUSY); + return; + } + + // Set state to loading + m_state = PrmDbFileLoadState::LOADING_FILE_UPDATES; + + // If reset is true, clear the staging database first + if (merge == PrmDb_Merge::MERGE) { + // Copy active to staging for merging + dbCopy(PrmDbType::DB_STAGING, PrmDbType::DB_ACTIVE); + } else { + // reset staging db, all file contents will be loaded but no old parameters will be retained + this->clearDb(PrmDbType::DB_STAGING); + } + + // Load the file into staging database + // The readParamFileImpl will emit the relevant EVR if the file load fails + // and also if it succeeds will emit EVRs with the number of records + PrmDbImpl::PrmLoadStatus success = PrmDbImpl::readParamFileImpl(fileName, PrmDbType::DB_STAGING); + + if (success == PrmLoadStatus::SUCCESS) { + this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); + m_state = PrmDbFileLoadState::FILE_UPDATES_STAGED; + } else { + this->log_WARNING_HI_PrmDbFileLoadFailed(); + // clear the staging DB and reset to an IDLE state in case of issues + this->clearDb(PrmDbType::DB_STAGING); + m_state = PrmDbFileLoadState::IDLE; + this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR); + } +} + +void PrmDbImpl::PRM_COMMIT_STAGED_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) { + // Verify we are in the correct state + if (m_state != PrmDbFileLoadState::FILE_UPDATES_STAGED) { + this->log_WARNING_LO_PrmDbFileLoadInvalidAction(m_state, PrmDb_PrmLoadAction::COMMIT_STAGED_COMMAND); + this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::VALIDATION_ERROR); + return; + } + + // Swap active and staging databases, safely w.r.t. prmGet + this->lock(); + t_dbStruct* temp = this->m_activeDb; + this->m_activeDb = this->m_stagingDb; + this->unLock(); + this->m_stagingDb = temp; + + // Clear the new staging database + this->clearDb(PrmDbType::DB_STAGING); + + // Set file load state to idle + m_state = PrmDbFileLoadState::IDLE; + + this->log_ACTIVITY_HI_PrmDbCommitComplete(); + this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); +} + +//! ---------------------------------------------------------------------- +//! Helpers for construction/destruction, init, & handlers +//! ---------------------------------------------------------------------- + +PrmDbImpl::PrmLoadStatus PrmDbImpl::readParamFileImpl(const Fw::StringBase& fileName, PrmDbType dbType) { + FW_ASSERT(dbType == PrmDbType::DB_ACTIVE or dbType == PrmDbType::DB_STAGING); + FW_ASSERT(fileName.length() > 0); + + Fw::String dbString = getDbString(dbType); -void PrmDbImpl::readParamFile() { - FW_ASSERT(this->m_fileName.length() > 0); // load file. FIXME: Put more robust file checking, such as a CRC. Os::File paramFile; - Os::File::Status stat = paramFile.open(this->m_fileName.toChar(), Os::File::OPEN_READ); + Os::File::Status stat = paramFile.open(fileName.toChar(), Os::File::OPEN_READ); if (stat != Os::File::OP_OK) { this->log_WARNING_HI_PrmFileReadError(PrmReadError::OPEN, 0, stat); - return; + return PrmLoadStatus::ERROR; } WorkingBuffer buff; - U32 recordNum = 0; - - this->clearDb(); + U32 recordNumTotal = 0; + U32 recordNumAdded = 0; + U32 recordNumUpdated = 0; for (FwSizeType entry = 0; entry < PRMDB_NUM_DB_ENTRIES; entry++) { U8 delimiter; @@ -265,35 +348,36 @@ void PrmDbImpl::readParamFile() { } if (fStat != Os::File::OP_OK) { - this->log_WARNING_HI_PrmFileReadError(PrmReadError::DELIMITER, static_cast(recordNum), fStat); - return; + this->log_WARNING_HI_PrmFileReadError(PrmReadError::DELIMITER, static_cast(recordNumTotal), fStat); + return PrmLoadStatus::ERROR; } if (sizeof(delimiter) != readSize) { - this->log_WARNING_HI_PrmFileReadError(PrmReadError::DELIMITER_SIZE, static_cast(recordNum), + this->log_WARNING_HI_PrmFileReadError(PrmReadError::DELIMITER_SIZE, static_cast(recordNumTotal), static_cast(readSize)); - return; + return PrmLoadStatus::ERROR; } if (PRMDB_ENTRY_DELIMITER != delimiter) { - this->log_WARNING_HI_PrmFileReadError(PrmReadError::DELIMITER_VALUE, static_cast(recordNum), + this->log_WARNING_HI_PrmFileReadError(PrmReadError::DELIMITER_VALUE, static_cast(recordNumTotal), delimiter); - return; + return PrmLoadStatus::ERROR; } U32 recordSize = 0; + // read record size readSize = sizeof(recordSize); fStat = paramFile.read(buff.getBuffAddr(), readSize, Os::File::WaitType::WAIT); if (fStat != Os::File::OP_OK) { - this->log_WARNING_HI_PrmFileReadError(PrmReadError::RECORD_SIZE, static_cast(recordNum), fStat); - return; + this->log_WARNING_HI_PrmFileReadError(PrmReadError::RECORD_SIZE, static_cast(recordNumTotal), fStat); + return PrmLoadStatus::ERROR; } if (sizeof(recordSize) != readSize) { - this->log_WARNING_HI_PrmFileReadError(PrmReadError::RECORD_SIZE_SIZE, static_cast(recordNum), + this->log_WARNING_HI_PrmFileReadError(PrmReadError::RECORD_SIZE_SIZE, static_cast(recordNumTotal), static_cast(readSize)); - return; + return PrmLoadStatus::ERROR; } // set serialized size to read size Fw::SerializeStatus desStat = buff.setBuffLen(static_cast(readSize)); @@ -308,9 +392,9 @@ void PrmDbImpl::readParamFile() { // sanity check value. It can't be larger than the maximum parameter buffer size + id // or smaller than the record id if ((recordSize > FW_PARAM_BUFFER_MAX_SIZE + sizeof(U32)) or (recordSize < sizeof(U32))) { - this->log_WARNING_HI_PrmFileReadError(PrmReadError::RECORD_SIZE_VALUE, static_cast(recordNum), + this->log_WARNING_HI_PrmFileReadError(PrmReadError::RECORD_SIZE_VALUE, static_cast(recordNumTotal), static_cast(recordSize)); - return; + return PrmLoadStatus::ERROR; } // read the parameter ID @@ -319,13 +403,13 @@ void PrmDbImpl::readParamFile() { fStat = paramFile.read(buff.getBuffAddr(), readSize, Os::File::WaitType::WAIT); if (fStat != Os::File::OP_OK) { - this->log_WARNING_HI_PrmFileReadError(PrmReadError::PARAMETER_ID, static_cast(recordNum), fStat); - return; + this->log_WARNING_HI_PrmFileReadError(PrmReadError::PARAMETER_ID, static_cast(recordNumTotal), fStat); + return PrmLoadStatus::ERROR; } if (sizeof(parameterId) != static_cast(readSize)) { - this->log_WARNING_HI_PrmFileReadError(PrmReadError::PARAMETER_ID_SIZE, static_cast(recordNum), + this->log_WARNING_HI_PrmFileReadError(PrmReadError::PARAMETER_ID_SIZE, static_cast(recordNumTotal), static_cast(readSize)); - return; + return PrmLoadStatus::ERROR; } // set serialized size to read parameter ID @@ -338,36 +422,128 @@ void PrmDbImpl::readParamFile() { desStat = buff.deserializeTo(parameterId); FW_ASSERT(Fw::FW_SERIALIZE_OK == desStat); - // copy parameter - this->m_db[entry].used = true; - this->m_db[entry].id = parameterId; + // copy parameter value from file into a temporary buffer + Fw::ParamBuffer tmpParamBuffer; // temporary param buffer to read parameter value from file readSize = recordSize - sizeof(parameterId); - - fStat = paramFile.read(this->m_db[entry].val.getBuffAddr(), readSize); + desStat = tmpParamBuffer.setBuffLen(static_cast(readSize)); + FW_ASSERT(Fw::FW_SERIALIZE_OK == desStat, static_cast(desStat)); // should never fail + fStat = paramFile.read(tmpParamBuffer.getBuffAddr(), readSize); if (fStat != Os::File::OP_OK) { - this->log_WARNING_HI_PrmFileReadError(PrmReadError::PARAMETER_VALUE, static_cast(recordNum), fStat); - return; + this->log_WARNING_HI_PrmFileReadError(PrmReadError::PARAMETER_VALUE, static_cast(recordNumTotal), + fStat); + return PrmLoadStatus::ERROR; } if (static_cast(readSize) != recordSize - sizeof(parameterId)) { - this->log_WARNING_HI_PrmFileReadError(PrmReadError::PARAMETER_VALUE_SIZE, static_cast(recordNum), + this->log_WARNING_HI_PrmFileReadError(PrmReadError::PARAMETER_VALUE_SIZE, static_cast(recordNumTotal), static_cast(readSize)); - return; + return PrmLoadStatus::ERROR; } - // set serialized size to read size - desStat = this->m_db[entry].val.setBuffLen(static_cast(readSize)); - // should never fail - FW_ASSERT(Fw::FW_SERIALIZE_OK == desStat, static_cast(desStat)); - recordNum++; + // Actually update or add parameter + PrmUpdateType updateStatus = updateAddPrmImpl(parameterId, tmpParamBuffer, dbType); + if (updateStatus == PARAM_ADDED) { + recordNumAdded++; + } else if (updateStatus == PARAM_UPDATED) { + recordNumUpdated++; + } + + if (updateStatus == NO_SLOTS) { + this->log_WARNING_HI_PrmDbFull(parameterId); + } + recordNumTotal++; } - this->log_ACTIVITY_HI_PrmFileLoadComplete(recordNum); + this->log_ACTIVITY_HI_PrmFileLoadComplete(dbString, recordNumTotal, recordNumAdded, recordNumUpdated); + return PrmLoadStatus::SUCCESS; } -void PrmDbImpl::pingIn_handler(FwIndexType portNum, U32 key) { - // respond to ping - this->pingOut_out(0, key); +PrmDbImpl::PrmUpdateType PrmDbImpl::updateAddPrmImpl(FwPrmIdType id, Fw::ParamBuffer& val, PrmDbType prmDbType) { + t_dbStruct* db = getDbPtr(prmDbType); + + PrmUpdateType updateStatus = NO_SLOTS; + + this->lock(); + // search for existing entry + bool existingEntry = false; + + for (FwSizeType entry = 0; entry < PRMDB_NUM_DB_ENTRIES; entry++) { + if ((db[entry].used) && (id == db[entry].id)) { + db[entry].val = val; + existingEntry = true; + updateStatus = PARAM_UPDATED; + break; + } + } + + // if there is no existing entry, add one + if (!existingEntry) { + for (FwSizeType entry = 0; entry < PRMDB_NUM_DB_ENTRIES; entry++) { + if (!(db[entry].used)) { + db[entry].val = val; + db[entry].id = id; + db[entry].used = true; + updateStatus = PARAM_ADDED; + break; + } + } + } + + this->unLock(); + return updateStatus; +} + +//! ---------------------------------------------------------------------- +//! Helpers for database management +//! ---------------------------------------------------------------------- + +void PrmDbImpl::clearDb(PrmDbType prmDbType) { + t_dbStruct* db = getDbPtr(prmDbType); + for (FwSizeType entry = 0; entry < PRMDB_NUM_DB_ENTRIES; entry++) { + db[entry].used = false; + db[entry].id = 0; + } +} + +bool PrmDbImpl::dbEqual() { + for (FwSizeType i = 0; i < PRMDB_NUM_DB_ENTRIES; i++) { + if (!(this->m_dbStore1[i] == this->m_dbStore2[i])) + return false; + } + return true; +} + +void PrmDbImpl::dbCopy(PrmDbType dest, PrmDbType src) { + for (FwSizeType i = 0; i < PRMDB_NUM_DB_ENTRIES; i++) { + dbCopySingle(dest, src, i); + } + this->log_ACTIVITY_HI_PrmDbCopyAllComplete(getDbString(src), getDbString(dest)); +} + +void PrmDbImpl::dbCopySingle(PrmDbType dest, PrmDbType src, FwSizeType index) { + t_dbStruct* srcPtr = getDbPtr(src); + t_dbStruct* destPtr = getDbPtr(dest); + + FW_ASSERT(index < PRMDB_NUM_DB_ENTRIES); + destPtr[index].used = srcPtr[index].used; + destPtr[index].id = srcPtr[index].id; + destPtr[index].val = srcPtr[index].val; +} + +PrmDbImpl::t_dbStruct* PrmDbImpl::getDbPtr(PrmDbType dbType) { + FW_ASSERT(dbType == PrmDbType::DB_ACTIVE or dbType == PrmDbType::DB_STAGING); + if (dbType == PrmDbType::DB_ACTIVE) { + return m_activeDb; + } + return m_stagingDb; +} + +Fw::String PrmDbImpl::getDbString(PrmDbType dbType) { + FW_ASSERT(dbType == PrmDbType::DB_ACTIVE or dbType == PrmDbType::DB_STAGING); + if (dbType == PrmDbType::DB_ACTIVE) { + return Fw::String("ACTIVE"); + } + return Fw::String("STAGING"); } } // namespace Svc diff --git a/Svc/PrmDb/PrmDbImpl.hpp b/Svc/PrmDb/PrmDbImpl.hpp index cc0dd7ddf6..1cedbf0c4e 100644 --- a/Svc/PrmDb/PrmDbImpl.hpp +++ b/Svc/PrmDb/PrmDbImpl.hpp @@ -16,10 +16,17 @@ #include #include #include +#include +#include #include namespace Svc { +typedef PrmDb_PrmWriteError PrmWriteError; +typedef PrmDb_PrmReadError PrmReadError; +typedef PrmDb_PrmDbType PrmDbType; +typedef PrmDb_PrmDbFileLoadState PrmDbFileLoadState; + //! \class PrmDbImpl //! \brief Component class for managing parameters //! @@ -28,14 +35,13 @@ namespace Svc { //! class PrmDbImpl final : public PrmDbComponentBase { - public: friend class PrmDbTester; + public: //! \brief PrmDb constructor //! //! The constructor for the PrmDbImpl class. - //! The constructor clears the database and stores - //! the file name for opening later. + //! The constructor clears the database and initiates the internal state. //! //! \param name component instance name PrmDbImpl(const char* name); @@ -58,17 +64,85 @@ class PrmDbImpl final : public PrmDbComponentBase { //! virtual ~PrmDbImpl(); + // Parameter update status for an individual parameter update or add + enum PrmUpdateType { + NO_SLOTS, //!< No slots available to add new parameter + PARAM_ADDED, //!< Parameter added to database + PARAM_UPDATED, //!< Parameter already in database, updated parameter + MAX_PARAM_UPDATE_TYPES + }; + + // Enum to return status of parameter file load + enum PrmLoadStatus { + SUCCESS, //!< File load successful + ERROR, //!< File load error + }; + protected: private: + Fw::String m_fileName; //!< filename for parameter storage + + PrmDbFileLoadState m_state; // Current file load state of the parameter database + + // Structure for a single parameter entry + struct t_dbStruct { + bool used; //!< whether slot is being used + FwPrmIdType id; //!< the id being stored in the slot + Fw::ParamBuffer val; //!< the serialized value of the parameter + + bool operator==(const t_dbStruct& other) const { + if (used != other.used) + return false; + if (id != other.id) + return false; + // Compare lengths first + if (val.getBuffLength() != other.val.getBuffLength()) + return false; + // Compare buffer contents + return std::memcmp(val.getBuffAddr(), other.val.getBuffAddr(), val.getBuffLength()) == 0; + } + }; + + // Pointers to the active and staging databases + // These point to the actual storage arrays below + // The active database is the ONLY one used for getting parameters + // The staging database is used for loading parameters from a file + // when commanded. Upon reading the file, the parameters are "staged" + // into the staging database, and then committed to the active database + // when a commit command is received. + t_dbStruct* m_activeDb; //!< Pointer to the active database + t_dbStruct* m_stagingDb; //!< Pointer to the staging database + + // Actual storage for the active and staging databases + t_dbStruct m_dbStore1[PRMDB_NUM_DB_ENTRIES]; + t_dbStruct m_dbStore2[PRMDB_NUM_DB_ENTRIES]; + + //! ---------------------------------------------------------------------- + //! Port & Command Handlers + //! ---------------------------------------------------------------------- + + //! \brief Read a parameter file and store parameter values into a database + //! + //! This method reads a parameter file and applies the values to the specified database + //! (i.e. active or staging). + //! + //! + //! \param fileName The name of the parameter file to read + //! \param dbType The type of database to read into (active or staging) + //! \return status success (True) / failure(False) + PrmLoadStatus readParamFileImpl(const Fw::StringBase& fileName, PrmDbType dbType); + //! \brief PrmDb parameter get handler //! //! This function retrieves a parameter value from the loaded set of stored parameters + //! It ALWAYS searches the active database, only //! //! \param portNum input port number. Should always be zero //! \param id identifier for parameter being used. //! \param val buffer where value is placed. //! \return status of retrieval. PARAM_VALID = successful read, PARAM_INVALID = unsuccessful read Fw::ParamValid getPrm_handler(FwIndexType portNum, FwPrmIdType id, Fw::ParamBuffer& val); + //! \brief PrmDb parameter set handler //! //! This function updates the value of the parameter stored in RAM. The PRM_SAVE_FILE @@ -79,6 +153,15 @@ class PrmDbImpl final : public PrmDbComponentBase { //! \param val buffer where value to be saved is stored. void setPrm_handler(FwIndexType portNum, FwPrmIdType id, Fw::ParamBuffer& val); + //! \brief PrmDb parameter add or update (set) helper + //! + //! This function does the underlying parameter update + //! + //! \param id identifier for parameter being used. + //! \param val buffer where value to be saved is stored. + //! \param dbType The type of database to read into (active or staging) + PrmDbImpl::PrmUpdateType updateAddPrmImpl(FwPrmIdType id, Fw::ParamBuffer& val, PrmDbType prmDbType); + //! \brief component ping handler //! //! The ping handler responds to messages to verify that the task @@ -87,8 +170,8 @@ class PrmDbImpl final : public PrmDbComponentBase { //! \param portNum the number of the incoming port. //! \param opCode the opcode being registered. //! \param key the key value that is returned with the ping response - void pingIn_handler(FwIndexType portNum, U32 key); + //! \brief PrmDb PRM_SAVE_FILE command handler //! //! This function saves the parameter values stored in RAM to the file @@ -99,20 +182,74 @@ class PrmDbImpl final : public PrmDbComponentBase { //! \param cmdSeq The sequence number of the command void PRM_SAVE_FILE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq); + //! \brief PrmDb PRM_SAVE_FILE command handler + //! + //! This function loads the parameter values from a specified + //! file into the backup parameter database. The command + //! takes a reset argument which specifies whether the existing + //! backup database should be cleared before loading the file, otherwise + //! the file contents are merged with the existing database. + //! + //! \param opCode The opcode of this commands + //! \param cmdSeq The sequence number of the command + //! \param fileName The name of the parameter load file + //! \param merge Whether to merge (true) or fully reset (false) the parameter database from the file contents + void PRM_LOAD_FILE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq, const Fw::CmdStringArg& fileName, PrmDb_Merge merge); + + //! \brief PrmDb PRM_COMMIT_STAGED command handler + //! + //! This function copies the contents of the staging parameter database + //! to the active parameter database, making the staged parameters + //! active. This command should only be called after a successful + //! PRM_LOAD_FILE command. + //! + //! \param opCode The opcode of this commands + //! \param cmdSeq The sequence number of the command + void PRM_COMMIT_STAGED_cmdHandler(FwOpcodeType opCode, U32 cmdSeq); + + //! ---------------------------------------------------------------------- + //! Helpers for database management + //! ---------------------------------------------------------------------- + //! \brief PrmDb clear database function //! //! This function clears all entries from the RAM database //! + //! \param dbType The type of database to clear (active or staging) + void clearDb(PrmDbType prmDbType); - void clearDb(); //!< clear the parameter database + //! \brief PrmDb get db pointer function + //! This function returns a pointer to the requested database + //! \param dbType The type of database requested (active or staging) + //! \return Pointer to the database array to be set + t_dbStruct* getDbPtr(PrmDbType dbType); - Fw::String m_fileName; //!< filename for parameter storage + //! \brief PrmDb get db string function + //! This function returns a string for the requested database + //! \param dbType The type of database requested (active or staging) + //! \return string representing the database + static Fw::String getDbString(PrmDbType dbType); - struct t_dbStruct { - bool used; //!< whether slot is being used - FwPrmIdType id; //!< the id being stored in the slot - Fw::ParamBuffer val; //!< the serialized value of the parameter - } m_db[PRMDB_NUM_DB_ENTRIES]; + //! \brief Check param db equality + //! + //! This helper method verifies the active and staging parameter dbs are equal + bool dbEqual(); + + //! \brief Deep copy for db + //! + //! Copies one db to another + //! \param dest The destination db to copy to (active or staging) + //! \param src The source db to copy from (active or staging) + void dbCopy(PrmDbType dest, PrmDbType src); + + //! \brief Deep copy for single db entry + //! + //! Copies one db entry to another at specified index + //! + //! \param dest The destination db to copy to (active or staging) + //! \param src The source db to copy from (active or staging) + //! \param index The index of the entry to copy + void dbCopySingle(PrmDbType dest, PrmDbType src, FwSizeType index); }; } // namespace Svc diff --git a/Svc/PrmDb/test/ut/PrmDbTestMain.cpp b/Svc/PrmDb/test/ut/PrmDbTestMain.cpp index 4b375783de..440031277f 100644 --- a/Svc/PrmDb/test/ut/PrmDbTestMain.cpp +++ b/Svc/PrmDb/test/ut/PrmDbTestMain.cpp @@ -175,6 +175,102 @@ TEST(ParameterDbTest, PrmFileWriteError) { tester.runFileWriteError(); } +TEST(ParameterDbTest, PrmDbEqualTest) { + Svc::PrmDbImpl impl("PrmDbImpl"); + + impl.init(10, 0); + impl.configure("TestFile.prm"); + + Svc::PrmDbTester tester(impl); + + tester.init(); + + // connect ports + connectPorts(impl, tester); + + tester.runDbEqualTest(); +} + +TEST(ParameterDbTest, PrmDbCopyTest) { + Svc::PrmDbImpl impl("PrmDbImpl"); + + impl.init(10, 0); + impl.configure("TestFile.prm"); + + Svc::PrmDbTester tester(impl); + + tester.init(); + + // connect ports + connectPorts(impl, tester); + + tester.runDbCopyTest(); +} + +TEST(ParameterDbTest, PrmDbCommitTest) { + Svc::PrmDbImpl impl("PrmDbImpl"); + + impl.init(10, 0); + impl.configure("TestFile.prm"); + + Svc::PrmDbTester tester(impl); + + tester.init(); + + // connect ports + connectPorts(impl, tester); + + tester.runDbCommitTest(); +} + +TEST(ParameterDbTest, PrmDbFileLoadNominal) { + Svc::PrmDbImpl impl("PrmDbImpl"); + + impl.init(10, 0); + impl.configure("TestFile.prm"); + + Svc::PrmDbTester tester(impl); + + tester.init(); + + // connect ports + connectPorts(impl, tester); + + tester.runPrmFileLoadNominal(); +} + +TEST(ParameterDbTest, PrmDbFileLoadWithErrors) { + Svc::PrmDbImpl impl("PrmDbImpl"); + + impl.init(10, 0); + impl.configure("TestFile.prm"); + + Svc::PrmDbTester tester(impl); + + tester.init(); + + // connect ports + connectPorts(impl, tester); + + tester.runPrmFileLoadWithErrors(); +} + +TEST(ParameterDbTest, PrmFileLoadIllegalActions) { + Svc::PrmDbImpl impl("PrmDbImpl"); + + impl.init(10, 0); + impl.configure("TestFile.prm"); + + Svc::PrmDbTester tester(impl); + + tester.init(); + + // connect ports + connectPorts(impl, tester); + + tester.runPrmFileLoadIllegal(); +} + int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/Svc/PrmDb/test/ut/PrmDbTester.cpp b/Svc/PrmDb/test/ut/PrmDbTester.cpp index 2ab43e3af5..f2ca47ba82 100644 --- a/Svc/PrmDb/test/ut/PrmDbTester.cpp +++ b/Svc/PrmDb/test/ut/PrmDbTester.cpp @@ -23,7 +23,7 @@ typedef PrmDb_PrmReadError PrmReadError; void PrmDbTester::runNominalPopulate() { // clear database - this->m_impl.clearDb(); + this->m_impl.clearDb(PrmDbType::DB_ACTIVE); // build a test parameter value with a simple value U32 val = 0x10; @@ -158,7 +158,7 @@ void PrmDbTester::runNominalLoadFile() { this->m_impl.readParamFile(); ASSERT_EVENTS_SIZE(1); ASSERT_EVENTS_PrmFileLoadComplete_SIZE(1); - ASSERT_EVENTS_PrmFileLoadComplete(0, 2); + ASSERT_EVENTS_PrmFileLoadComplete(0, "ACTIVE", 2, 2, 0); // verify values (populated by runNominalPopulate()) @@ -192,7 +192,7 @@ void PrmDbTester::runMissingExtraParams() { ASSERT_EVENTS_PrmIdNotFound(0, 0x1000); // clear database - this->m_impl.clearDb(); + this->m_impl.clearDb(PrmDbType::DB_ACTIVE); this->clearEvents(); // write too many entries @@ -343,56 +343,6 @@ void PrmDbTester::runRefPrmFile() { ASSERT_EVENTS_PrmFileSaveComplete(0, 4); } -PrmDbTester* PrmDbTester::PrmDbTestFile::s_tester = nullptr; - -void PrmDbTester::PrmDbTestFile::setTester(Svc::PrmDbTester* tester) { - ASSERT_NE(tester, nullptr); - s_tester = tester; -} - -Os::File::Status PrmDbTester::PrmDbTestFile::read(U8* buffer, FwSizeType& size, Os::File::WaitType wait) { - EXPECT_NE(s_tester, nullptr); - Os::File::Status status = this->Os::Stub::File::Test::TestFile::read(buffer, size, wait); - if (s_tester->m_waits == 0) { - switch (s_tester->m_errorType) { - case FILE_STATUS_ERROR: - status = s_tester->m_status; - break; - case FILE_SIZE_ERROR: - size += 1; - break; - case FILE_DATA_ERROR: - buffer[0] += 1; - break; - default: - break; - } - } else { - s_tester->m_waits -= 1; - } - return status; -} - -Os::File::Status PrmDbTester::PrmDbTestFile::write(const U8* buffer, FwSizeType& size, Os::File::WaitType wait) { - EXPECT_NE(s_tester, nullptr); - Os::File::Status status = this->Os::Stub::File::Test::TestFile::write(buffer, size, wait); - if (s_tester->m_waits == 0) { - switch (s_tester->m_errorType) { - case FILE_STATUS_ERROR: - status = s_tester->m_status; - break; - case FILE_SIZE_ERROR: - size += 1; - break; - default: - break; - } - } else { - s_tester->m_waits -= 1; - } - return status; -} - void PrmDbTester::runFileReadError() { // Preconditions setup and test this->runNominalLoadFile(); @@ -602,6 +552,791 @@ void PrmDbTester::runFileWriteError() { } } +void PrmDbTester::runDbEqualTest() { + Fw::SerializeStatus serStat; + + // 1. Test with empty databases - should be equal + this->m_impl.clearDb(PrmDb_PrmDbType::DB_ACTIVE); + this->m_impl.clearDb(PrmDb_PrmDbType::DB_STAGING); + EXPECT_TRUE(this->m_impl.dbEqual()); + + // 2. Add an entry to active DB only - should not be equal + U32 val1 = 0x42; + FwPrmIdType id1 = 0x100; + Fw::ParamBuffer pBuff; + + serStat = pBuff.serializeFrom(val1); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, serStat); + + this->m_impl.updateAddPrmImpl(id1, pBuff, PrmDb_PrmDbType::DB_ACTIVE); + EXPECT_FALSE(this->m_impl.dbEqual()); + + // 3. Add same entry to staging DB - should be equal again + pBuff.resetSer(); + serStat = pBuff.serializeFrom(val1); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, serStat); + + this->m_impl.updateAddPrmImpl(id1, pBuff, PrmDb_PrmDbType::DB_STAGING); + EXPECT_TRUE(this->m_impl.dbEqual()); + + // 4. Update entry in active DB only - should not be equal + U32 val2 = 0x43; + pBuff.resetSer(); + serStat = pBuff.serializeFrom(val2); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, serStat); + + this->m_impl.updateAddPrmImpl(id1, pBuff, PrmDb_PrmDbType::DB_STAGING); + EXPECT_FALSE(this->m_impl.dbEqual()); + + // 5. Update staging DB to match - should be equal again + pBuff.resetSer(); + serStat = pBuff.serializeFrom(val2); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, serStat); + + this->m_impl.updateAddPrmImpl(id1, pBuff, PrmDb_PrmDbType::DB_ACTIVE); + EXPECT_TRUE(this->m_impl.dbEqual()); + + // 6. Add different entry to staging DB - should not be equal + U32 val3 = 0x44; + FwPrmIdType id2 = 0x101; + pBuff.resetSer(); + serStat = pBuff.serializeFrom(val3); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, serStat); + + this->m_impl.updateAddPrmImpl(id2, pBuff, PrmDb_PrmDbType::DB_STAGING); + EXPECT_FALSE(this->m_impl.dbEqual()); +} + +void PrmDbTester::runDbCopyTest() { + Fw::SerializeStatus serStat; + + // Clear both databases + this->m_impl.clearDb(PrmDb_PrmDbType::DB_ACTIVE); + this->m_impl.clearDb(PrmDb_PrmDbType::DB_STAGING); + + // Add entries to active DB only + U32 val1 = 0x1234; + FwPrmIdType id1 = 0x100; + Fw::ParamBuffer pBuff; + + // Add first parameter + pBuff.resetSer(); + serStat = pBuff.serializeFrom(val1); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, serStat); + this->m_impl.updateAddPrmImpl(id1, pBuff, PrmDb_PrmDbType::DB_ACTIVE); + + // Add second parameter + F32 val2 = 3.14159f; + FwPrmIdType id2 = 0x200; + pBuff.resetSer(); + serStat = pBuff.serializeFrom(val2); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, serStat); + this->m_impl.updateAddPrmImpl(id2, pBuff, PrmDb_PrmDbType::DB_ACTIVE); + + // Verify databases are not equal + EXPECT_FALSE(this->m_impl.dbEqual()); + + // Copy active DB to staging DB + this->m_impl.dbCopy(PrmDb_PrmDbType::DB_STAGING, PrmDb_PrmDbType::DB_ACTIVE); + + // Verify databases are now equal + EXPECT_TRUE(this->m_impl.dbEqual()); + + // Verify values in the staging DB + pBuff.resetSer(); + U32 testVal1; + FwSizeType idx = 0; + // Find the parameter and get its index + for (FwSizeType i = 0; i < PRMDB_NUM_DB_ENTRIES; i++) { + if (this->m_impl.m_activeDb[i].used && this->m_impl.m_stagingDb[i].id == id1) { + idx = i; + break; + } + } + EXPECT_TRUE(this->m_impl.m_activeDb[idx].used); + EXPECT_EQ(id1, this->m_impl.m_stagingDb[idx].id); + pBuff = this->m_impl.m_stagingDb[idx].val; + serStat = pBuff.deserializeTo(testVal1); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, serStat); + EXPECT_EQ(val1, testVal1); + + // Clear both databases + this->m_impl.clearDb(PrmDb_PrmDbType::DB_ACTIVE); + this->m_impl.clearDb(PrmDb_PrmDbType::DB_STAGING); + + // Add different entries to active and staging DBs + + // active DB - add first parameter + pBuff.resetSer(); + serStat = pBuff.serializeFrom(val1); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, serStat); + this->m_impl.updateAddPrmImpl(id1, pBuff, PrmDb_PrmDbType::DB_ACTIVE); + + // active DB - add second parameter + pBuff.resetSer(); + serStat = pBuff.serializeFrom(val2); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, serStat); + this->m_impl.updateAddPrmImpl(id2, pBuff, PrmDb_PrmDbType::DB_ACTIVE); + + // staging DB - add different parameter + U16 val3 = 0x5678; + FwPrmIdType id3 = 0x300; + pBuff.resetSer(); + serStat = pBuff.serializeFrom(val3); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, serStat); + this->m_impl.updateAddPrmImpl(id3, pBuff, PrmDb_PrmDbType::DB_STAGING); + + // Verify databases are not equal + EXPECT_FALSE(this->m_impl.dbEqual()); + + // Copy only the second entry from active to staging at the same index + FwSizeType activeIdx2 = 1; // Index of second entry in active DB + this->m_impl.dbCopySingle(PrmDb_PrmDbType::DB_STAGING, PrmDb_PrmDbType::DB_ACTIVE, activeIdx2); + + // Verify the specific entry was copied correctly + EXPECT_TRUE(this->m_impl.m_stagingDb[activeIdx2].used); + EXPECT_EQ(id2, this->m_impl.m_stagingDb[activeIdx2].id); + + // Verify value matches + pBuff = this->m_impl.m_stagingDb[activeIdx2].val; + F32 testVal2; + serStat = pBuff.deserializeTo(testVal2); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, serStat); + EXPECT_EQ(val2, testVal2); + + // Verify the original entry in staging DB is still there + bool foundOriginal = false; + for (FwSizeType i = 0; i < PRMDB_NUM_DB_ENTRIES; i++) { + if (this->m_impl.m_stagingDb[i].used && this->m_impl.m_stagingDb[i].id == id3) { + foundOriginal = true; + + // Verify value is still correct + pBuff = this->m_impl.m_stagingDb[i].val; + U16 testVal3; + serStat = pBuff.deserializeTo(testVal3); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, serStat); + EXPECT_EQ(val3, testVal3); + break; + } + } + EXPECT_TRUE(foundOriginal); + + // Databases should still not be equal since we only copied one entry + EXPECT_FALSE(this->m_impl.dbEqual()); +} + +void PrmDbTester::runDbCommitTest() { + // 1. Set the m_state to FILE_UPDATES_STAGED + this->m_impl.m_state = PrmDbFileLoadState::FILE_UPDATES_STAGED; + + // 2. Populate both databases with different content + // Clear both databases first + this->m_impl.clearDb(PrmDbType::DB_ACTIVE); + this->m_impl.clearDb(PrmDbType::DB_STAGING); + + // Add parameters to active database + U32 activeVal1 = 0x1234; + FwPrmIdType activeId1 = 0x100; + Fw::ParamBuffer pBuff; + + Fw::SerializeStatus stat = pBuff.serializeFrom(activeVal1); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, stat); + this->m_impl.updateAddPrmImpl(activeId1, pBuff, PrmDbType::DB_ACTIVE); + + // Add different parameters to staging database + U32 stagingVal1 = 0x5678; + FwPrmIdType stagingId1 = 0x100; // Same ID but different value + pBuff.resetSer(); + stat = pBuff.serializeFrom(stagingVal1); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, stat); + this->m_impl.updateAddPrmImpl(stagingId1, pBuff, PrmDbType::DB_STAGING); + + // Add a parameter that's only in the staging database + F32 stagingVal2 = 3.14159f; + FwPrmIdType stagingId2 = 0x200; + pBuff.resetSer(); + stat = pBuff.serializeFrom(stagingVal2); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, stat); + this->m_impl.updateAddPrmImpl(stagingId2, pBuff, PrmDbType::DB_STAGING); + + // Store pointers to databases before swap for verification + PrmDbImpl::t_dbStruct* preSwapActiveDb = this->m_impl.m_activeDb; + PrmDbImpl::t_dbStruct* preSwapStagingDb = this->m_impl.m_stagingDb; + + // Clear events and command history + this->clearEvents(); + this->clearHistory(); + + // 3. Execute the commit command + this->sendCmd_PRM_COMMIT_STAGED(0, 10); + Fw::QueuedComponentBase::MsgDispatchStatus dispatchStatus = this->m_impl.doDispatch(); + EXPECT_EQ(dispatchStatus, Fw::QueuedComponentBase::MSG_DISPATCH_OK); + + // 4. Verify results + + // Check command response + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, PrmDbImpl::OPCODE_PRM_COMMIT_STAGED, 10, Fw::CmdResponse::OK); + + // Check event + ASSERT_EVENTS_SIZE(1); + ASSERT_EVENTS_PrmDbCommitComplete_SIZE(1); + + // Verify the state is now IDLE + EXPECT_EQ(this->m_impl.m_state, PrmDbFileLoadState::IDLE); + + // Verify that the database pointers have been swapped + EXPECT_EQ(this->m_impl.m_activeDb, preSwapStagingDb); + EXPECT_EQ(this->m_impl.m_stagingDb, preSwapActiveDb); + + // Verify that the new staging database is empty + bool allEntriesCleared = true; + for (FwSizeType i = 0; i < PRMDB_NUM_DB_ENTRIES; i++) { + if (this->m_impl.m_stagingDb[i].used) { + allEntriesCleared = false; + break; + } + } + EXPECT_TRUE(allEntriesCleared); + + // Verify that parameters can be accessed from the newly active database + // (which was formerly the staging database) + pBuff.resetSer(); + U32 retrievedVal1; + this->invoke_to_getPrm(0, stagingId1, pBuff); + stat = pBuff.deserializeTo(retrievedVal1); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, stat); + EXPECT_EQ(retrievedVal1, stagingVal1); + + pBuff.resetSer(); + F32 retrievedVal2; + this->invoke_to_getPrm(0, stagingId2, pBuff); + stat = pBuff.deserializeTo(retrievedVal2); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, stat); + EXPECT_EQ(retrievedVal2, stagingVal2); + + // Test invalid state handling - try to commit again when not in FILE_UPDATES_STAGED state + this->clearEvents(); + this->clearHistory(); + this->sendCmd_PRM_COMMIT_STAGED(0, 11); + dispatchStatus = this->m_impl.doDispatch(); + EXPECT_EQ(dispatchStatus, Fw::QueuedComponentBase::MSG_DISPATCH_OK); + + // Should get validation error and warning event + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, PrmDbImpl::OPCODE_PRM_COMMIT_STAGED, 11, Fw::CmdResponse::VALIDATION_ERROR); + ASSERT_EVENTS_SIZE(1); + ASSERT_EVENTS_PrmDbFileLoadInvalidAction_SIZE(1); +} + +void PrmDbTester::runPrmFileLoadNominal() { + Fw::String file = "TestFile.prm"; + Fw::QueuedComponentBase::MsgDispatchStatus dispatchStatus; + + // Store pointers to databases before swap for verification + PrmDbImpl::t_dbStruct* preSwapActiveDb = this->m_impl.m_activeDb; + PrmDbImpl::t_dbStruct* preSwapStagingDb = this->m_impl.m_stagingDb; + + // Ensure we're in IDLE state + EXPECT_EQ(this->m_impl.m_state, PrmDbFileLoadState::IDLE); + + // Populate the active DB and save to file + runNominalSaveFile(); + printf("Saved into File: \n"); + printDb(PrmDbType::DB_ACTIVE); + printDb(PrmDbType::DB_STAGING); + + // Clear both databases + this->m_impl.clearDb(PrmDbType::DB_ACTIVE); + this->m_impl.clearDb(PrmDbType::DB_STAGING); + + printf("Cleared: \n"); + printDb(PrmDbType::DB_ACTIVE); + printDb(PrmDbType::DB_STAGING); + + // Populate active database with some values so we can test merge=true + // A new ID + U32 activeVal1 = 0x1234; + FwPrmIdType activeId1 = 0x100; + Fw::ParamBuffer pBuff; + + // Add parameter to active database + Fw::SerializeStatus stat = pBuff.serializeFrom(activeVal1); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, stat); + this->m_impl.updateAddPrmImpl(activeId1, pBuff, PrmDbType::DB_ACTIVE); + // Verify parameter is in active database + pBuff.resetSer(); + U32 testVal; + this->invoke_to_getPrm(0, activeId1, pBuff); + stat = pBuff.deserializeTo(testVal); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, stat); + EXPECT_EQ(testVal, activeVal1); + // A existing (in file) ID + U32 activeVal2Original = 0x30; + U32 activeVal2Update = activeVal2Original + 1; + FwPrmIdType activeId2 = 0x25; // This ID exists in the file with a different value + pBuff.resetSer(); + + // Add parameter to active database + stat = pBuff.serializeFrom(activeVal2Update); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, stat); + this->m_impl.updateAddPrmImpl(activeId2, pBuff, PrmDbType::DB_ACTIVE); + // Verify parameter is in active database + pBuff.resetSer(); + U32 testVal2; + this->invoke_to_getPrm(0, activeId2, pBuff); + stat = pBuff.deserializeTo(testVal2); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, stat); + EXPECT_EQ(testVal2, activeVal2Update); + + printf("Added new: \n"); + printDb(PrmDbType::DB_ACTIVE); + printDb(PrmDbType::DB_STAGING); + + // Send PRM_LOAD_FILE command with merge=true to merge with active database + Os::Stub::File::Test::StaticData::setReadResult(m_io_data, Os::Stub::File::Test::StaticData::data.pointer); + Os::Stub::File::Test::StaticData::setNextStatus(Os::File::OP_OK); + this->clearEvents(); + this->clearHistory(); + this->sendCmd_PRM_LOAD_FILE(0, 10, file, PrmDb_Merge::MERGE); + dispatchStatus = this->m_impl.doDispatch(); + EXPECT_EQ(dispatchStatus, Fw::QueuedComponentBase::MSG_DISPATCH_OK); + + ASSERT_EVENTS_SIZE(2); + // Verify EVRs for copy (because we are merging) + ASSERT_EVENTS_PrmDbCopyAllComplete_SIZE(1); + ASSERT_EVENTS_PrmDbCopyAllComplete(0, "ACTIVE", "STAGING"); + // Verify EVRs for the file load + ASSERT_EVENTS_PrmFileLoadComplete_SIZE(1); + ASSERT_EVENTS_PrmFileLoadComplete(0, "STAGING", 2, 1, 1); + + printf("Parameter Load file complete: \n"); + printDb(PrmDbType::DB_ACTIVE); + printDb(PrmDbType::DB_STAGING); + + // Verify state and command response after PRM_LOAD_FILE + EXPECT_EQ(this->m_impl.m_state, PrmDbFileLoadState::FILE_UPDATES_STAGED); + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, PrmDbImpl::OPCODE_PRM_LOAD_FILE, 10, Fw::CmdResponse::OK); + + // Verify that parameters in staging database have expected values + for (FwSizeType i = 0; i < PRMDB_NUM_DB_ENTRIES; i++) { + // Check for the parameter that we added after the save file (since we are merging) + if (this->m_impl.m_stagingDb[i].used && this->m_impl.m_stagingDb[i].id == activeId1) { + pBuff = this->m_impl.m_stagingDb[i].val; + U32 checkVal; + stat = pBuff.deserializeTo(checkVal); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, stat); + EXPECT_EQ(checkVal, activeVal1); + } + // Check for the parameter that we updated after the save file (since we are merging) + if (this->m_impl.m_stagingDb[i].used && this->m_impl.m_stagingDb[i].id == activeId2) { + pBuff = this->m_impl.m_stagingDb[i].val; + U32 checkVal; + stat = pBuff.deserializeTo(checkVal); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, stat); + EXPECT_EQ(checkVal, activeVal2Original); // Value should match what was in the file, not what we set + } + } + + // Send PRM_COMMIT_STAGED command + this->clearEvents(); + this->clearHistory(); + this->sendCmd_PRM_COMMIT_STAGED(0, 11); + dispatchStatus = this->m_impl.doDispatch(); + EXPECT_EQ(dispatchStatus, Fw::QueuedComponentBase::MSG_DISPATCH_OK); + + // Verify state and command response after PRM_COMMIT_STAGED + EXPECT_EQ(this->m_impl.m_state, PrmDbFileLoadState::IDLE); + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, PrmDbImpl::OPCODE_PRM_COMMIT_STAGED, 11, Fw::CmdResponse::OK); + ASSERT_EVENTS_SIZE(1); + ASSERT_EVENTS_PrmDbCommitComplete_SIZE(1); + + // Verify the database pointers have been swapped + EXPECT_EQ(this->m_impl.m_activeDb, preSwapStagingDb); + EXPECT_EQ(this->m_impl.m_stagingDb, preSwapActiveDb); + + // Verify the new staging database (former active) is empty + bool allEntriesCleared = true; + for (FwSizeType i = 0; i < PRMDB_NUM_DB_ENTRIES; i++) { + if (this->m_impl.m_stagingDb[i].used) { + allEntriesCleared = false; + break; + } + } + EXPECT_TRUE(allEntriesCleared); + + // Verify we can now perform operations only allowed in IDLE state + // Try setting a parameter - should work in IDLE state + U32 newVal3 = 0x9ABC; + FwPrmIdType newId3 = 0x300; + pBuff.resetSer(); + stat = pBuff.serializeFrom(newVal3); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, stat); + + this->clearEvents(); + this->invoke_to_setPrm(0, newId3, pBuff); + dispatchStatus = this->m_impl.doDispatch(); + EXPECT_EQ(dispatchStatus, Fw::QueuedComponentBase::MSG_DISPATCH_OK); + + // Verify parameter was added + ASSERT_EVENTS_SIZE(1); + ASSERT_EVENTS_PrmIdAdded_SIZE(1); + + // Verify we can retrieve the newly set parameter + pBuff.resetSer(); + U32 retrievedVal3; + Fw::ParamValid valid = this->invoke_to_getPrm(0, newId3, pBuff); + EXPECT_EQ(Fw::ParamValid::VALID, valid); + stat = pBuff.deserializeTo(retrievedVal3); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, stat); + EXPECT_EQ(retrievedVal3, newVal3); +} + +void PrmDbTester::runPrmFileLoadWithErrors() { + Fw::String file = "TestFile.prm"; + Fw::QueuedComponentBase::MsgDispatchStatus dispatchStatus; + + // Store pointers to databases before swap for verification + PrmDbImpl::t_dbStruct* preSwapActiveDb = this->m_impl.m_activeDb; + PrmDbImpl::t_dbStruct* preSwapStagingDb = this->m_impl.m_stagingDb; + + // Ensure we're in IDLE state + EXPECT_EQ(this->m_impl.m_state, PrmDbFileLoadState::IDLE); + + // Populate the active DB and save to file + runNominalSaveFile(); + printf("Saved into File: \n"); + printDb(PrmDbType::DB_ACTIVE); + printDb(PrmDbType::DB_STAGING); + + // Clear both databases + this->m_impl.clearDb(PrmDbType::DB_ACTIVE); + this->m_impl.clearDb(PrmDbType::DB_STAGING); + + printf("Cleared: \n"); + printDb(PrmDbType::DB_ACTIVE); + printDb(PrmDbType::DB_STAGING); + + // Populate active database with some values so we can test merge=true + // A new ID + U32 activeVal1 = 0x1234; + FwPrmIdType activeId1 = 0x100; + Fw::ParamBuffer pBuff; + + // Add parameter to active database + Fw::SerializeStatus stat = pBuff.serializeFrom(activeVal1); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, stat); + this->m_impl.updateAddPrmImpl(activeId1, pBuff, PrmDbType::DB_ACTIVE); + // Verify parameter is in active database + pBuff.resetSer(); + U32 testVal; + this->invoke_to_getPrm(0, activeId1, pBuff); + stat = pBuff.deserializeTo(testVal); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, stat); + EXPECT_EQ(testVal, activeVal1); + // A existing (in file) ID + U32 activeVal2Original = 0x30; + U32 activeVal2Update = activeVal2Original + 1; + FwPrmIdType activeId2 = 0x25; // This ID exists in the file with a different value + pBuff.resetSer(); + + // Add parameter to active database + stat = pBuff.serializeFrom(activeVal2Update); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, stat); + this->m_impl.updateAddPrmImpl(activeId2, pBuff, PrmDbType::DB_ACTIVE); + // Verify parameter is in active database + pBuff.resetSer(); + U32 testVal2; + this->invoke_to_getPrm(0, activeId2, pBuff); + stat = pBuff.deserializeTo(testVal2); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, stat); + EXPECT_EQ(testVal2, activeVal2Update); + + printf("Added new: \n"); + printDb(PrmDbType::DB_ACTIVE); + printDb(PrmDbType::DB_STAGING); + + // Send PRM_LOAD_FILE command with merge=true to merge with active database + // but with a file open error + Os::Stub::File::Test::StaticData::setReadResult(m_io_data, Os::Stub::File::Test::StaticData::data.pointer); + Os::Stub::File::Test::StaticData::setNextStatus(Os::File::DOESNT_EXIST); + this->clearEvents(); + this->clearHistory(); + this->sendCmd_PRM_LOAD_FILE(0, 10, file, PrmDb_Merge::MERGE); + dispatchStatus = this->m_impl.doDispatch(); + EXPECT_EQ(dispatchStatus, Fw::QueuedComponentBase::MSG_DISPATCH_OK); + + ASSERT_EVENTS_SIZE(3); + // Verify EVRs for copy (because we are merging) + ASSERT_EVENTS_PrmDbCopyAllComplete_SIZE(1); + // Verify EVRs for file load read error + ASSERT_EVENTS_PrmFileReadError_SIZE(1); + // Verify EVRs for the file load cmd failure + ASSERT_EVENTS_PrmDbFileLoadFailed_SIZE(1); + + printf("Parameter Load file complete: \n"); + printDb(PrmDbType::DB_ACTIVE); + printDb(PrmDbType::DB_STAGING); + + // Verify state and command response after PRM_LOAD_FILE + EXPECT_EQ(this->m_impl.m_state, PrmDbFileLoadState::IDLE); + ASSERT_CMD_RESPONSE(0, PrmDbImpl::OPCODE_PRM_LOAD_FILE, 10, Fw::CmdResponse::EXECUTION_ERROR); + + // Verify the database pointers have NOT been swapped + EXPECT_EQ(this->m_impl.m_activeDb, preSwapActiveDb); + EXPECT_EQ(this->m_impl.m_stagingDb, preSwapStagingDb); + + // Verify the staging database is empty + bool allEntriesCleared = true; + for (FwSizeType i = 0; i < PRMDB_NUM_DB_ENTRIES; i++) { + if (this->m_impl.m_stagingDb[i].used) { + allEntriesCleared = false; + break; + } + } + EXPECT_TRUE(allEntriesCleared); +} + +void PrmDbTester::runPrmFileLoadIllegal() { + Fw::QueuedComponentBase::MsgDispatchStatus dispatchStatus; + Fw::ParamBuffer pBuff; + U32 testVal = 0x1234; + FwPrmIdType testId = 0x42; + + // Serialize test value for parameter setting + Fw::SerializeStatus stat = pBuff.serializeFrom(testVal); + EXPECT_EQ(Fw::FW_SERIALIZE_OK, stat); + + // ------------------------------------------------------------------- + // 1. Test illegal operations in LOADING_FILE_UPDATES state + // ------------------------------------------------------------------- + this->m_impl.m_state = PrmDbFileLoadState::LOADING_FILE_UPDATES; + + // 1.1 Attempt PRM_LOAD_FILE while already loading + this->clearEvents(); + this->clearHistory(); + this->sendCmd_PRM_LOAD_FILE(0, 10, Fw::String("file.prm"), PrmDb_Merge::MERGE); + dispatchStatus = this->m_impl.doDispatch(); + EXPECT_EQ(dispatchStatus, Fw::QueuedComponentBase::MSG_DISPATCH_OK); + + // Verify appropriate error response + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, PrmDbImpl::OPCODE_PRM_LOAD_FILE, 10, Fw::CmdResponse::BUSY); + ASSERT_EVENTS_SIZE(1); + ASSERT_EVENTS_PrmDbFileLoadInvalidAction_SIZE(1); + ASSERT_EVENTS_PrmDbFileLoadInvalidAction(0, PrmDbFileLoadState::LOADING_FILE_UPDATES, + PrmDb_PrmLoadAction::LOAD_FILE_COMMAND); + + // 1.2 Attempt PRM_SAVE_FILE during loading + this->clearEvents(); + this->clearHistory(); + this->sendCmd_PRM_SAVE_FILE(0, 11); + dispatchStatus = this->m_impl.doDispatch(); + EXPECT_EQ(dispatchStatus, Fw::QueuedComponentBase::MSG_DISPATCH_OK); + + // Verify appropriate error response + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, PrmDbImpl::OPCODE_PRM_SAVE_FILE, 11, Fw::CmdResponse::BUSY); + ASSERT_EVENTS_SIZE(1); + ASSERT_EVENTS_PrmDbFileLoadInvalidAction_SIZE(1); + ASSERT_EVENTS_PrmDbFileLoadInvalidAction(0, PrmDbFileLoadState::LOADING_FILE_UPDATES, + PrmDb_PrmLoadAction::SAVE_FILE_COMMAND); + + // 1.3 Attempt to set parameter during loading + this->clearEvents(); + this->clearHistory(); + this->invoke_to_setPrm(0, testId, pBuff); + dispatchStatus = this->m_impl.doDispatch(); + EXPECT_EQ(dispatchStatus, Fw::QueuedComponentBase::MSG_DISPATCH_OK); + + // Verify appropriate error response (warning event only, no added event) + ASSERT_EVENTS_SIZE(1); + ASSERT_EVENTS_PrmDbFileLoadInvalidAction_SIZE(1); + ASSERT_EVENTS_PrmDbFileLoadInvalidAction(0, PrmDbFileLoadState::LOADING_FILE_UPDATES, + PrmDb_PrmLoadAction::SET_PARAMETER); + ASSERT_EVENTS_PrmIdAdded_SIZE(0); + ASSERT_EVENTS_PrmIdUpdated_SIZE(0); + + // 1.4 Attempt PRM_COMMIT_STAGED during loading + this->clearEvents(); + this->clearHistory(); + this->sendCmd_PRM_COMMIT_STAGED(0, 12); + dispatchStatus = this->m_impl.doDispatch(); + EXPECT_EQ(dispatchStatus, Fw::QueuedComponentBase::MSG_DISPATCH_OK); + + // Verify appropriate error response + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, PrmDbImpl::OPCODE_PRM_COMMIT_STAGED, 12, Fw::CmdResponse::VALIDATION_ERROR); + ASSERT_EVENTS_SIZE(1); + ASSERT_EVENTS_PrmDbFileLoadInvalidAction_SIZE(1); + ASSERT_EVENTS_PrmDbFileLoadInvalidAction(0, PrmDbFileLoadState::LOADING_FILE_UPDATES, + PrmDb_PrmLoadAction::COMMIT_STAGED_COMMAND); + + // Verify state hasn't changed + EXPECT_EQ(this->m_impl.m_state, PrmDbFileLoadState::LOADING_FILE_UPDATES); + + // ------------------------------------------------------------------- + // 2. Test illegal operations in FILE_UPDATES_STAGED state + // ------------------------------------------------------------------- + this->m_impl.m_state = PrmDbFileLoadState::FILE_UPDATES_STAGED; + + // 2.1 Attempt PRM_LOAD_FILE when updates are staged + this->clearEvents(); + this->clearHistory(); + this->sendCmd_PRM_LOAD_FILE(0, 13, Fw::String("file.prm"), PrmDb_Merge::RESET); + dispatchStatus = this->m_impl.doDispatch(); + EXPECT_EQ(dispatchStatus, Fw::QueuedComponentBase::MSG_DISPATCH_OK); + + // Verify appropriate error response + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, PrmDbImpl::OPCODE_PRM_LOAD_FILE, 13, Fw::CmdResponse::BUSY); + ASSERT_EVENTS_SIZE(1); + ASSERT_EVENTS_PrmDbFileLoadInvalidAction_SIZE(1); + ASSERT_EVENTS_PrmDbFileLoadInvalidAction(0, PrmDbFileLoadState::FILE_UPDATES_STAGED, + PrmDb_PrmLoadAction::LOAD_FILE_COMMAND); + + // 2.2 Attempt PRM_SAVE_FILE when updates are staged + this->clearEvents(); + this->clearHistory(); + this->sendCmd_PRM_SAVE_FILE(0, 14); + dispatchStatus = this->m_impl.doDispatch(); + EXPECT_EQ(dispatchStatus, Fw::QueuedComponentBase::MSG_DISPATCH_OK); + + // Verify appropriate error response + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, PrmDbImpl::OPCODE_PRM_SAVE_FILE, 14, Fw::CmdResponse::BUSY); + ASSERT_EVENTS_SIZE(1); + ASSERT_EVENTS_PrmDbFileLoadInvalidAction_SIZE(1); + ASSERT_EVENTS_PrmDbFileLoadInvalidAction(0, PrmDbFileLoadState::FILE_UPDATES_STAGED, + PrmDb_PrmLoadAction::SAVE_FILE_COMMAND); + + // 2.3 Attempt to set parameter when updates are staged + this->clearEvents(); + this->clearHistory(); + this->invoke_to_setPrm(0, testId, pBuff); + dispatchStatus = this->m_impl.doDispatch(); + EXPECT_EQ(dispatchStatus, Fw::QueuedComponentBase::MSG_DISPATCH_OK); + + // Verify appropriate error response (warning event only, no added event) + ASSERT_EVENTS_SIZE(1); + ASSERT_EVENTS_PrmDbFileLoadInvalidAction_SIZE(1); + ASSERT_EVENTS_PrmDbFileLoadInvalidAction(0, PrmDbFileLoadState::FILE_UPDATES_STAGED, + PrmDb_PrmLoadAction::SET_PARAMETER); + ASSERT_EVENTS_PrmIdAdded_SIZE(0); + ASSERT_EVENTS_PrmIdUpdated_SIZE(0); + + // Verify state hasn't changed + EXPECT_EQ(this->m_impl.m_state, PrmDbFileLoadState::FILE_UPDATES_STAGED); + + // ------------------------------------------------------------------- + // 3. Test illegal operations in IDLE state + // ------------------------------------------------------------------- + this->m_impl.m_state = PrmDbFileLoadState::IDLE; + + // 3.1 Attempt PRM_COMMIT_STAGED in IDLE state + this->clearEvents(); + this->clearHistory(); + this->sendCmd_PRM_COMMIT_STAGED(0, 15); + dispatchStatus = this->m_impl.doDispatch(); + EXPECT_EQ(dispatchStatus, Fw::QueuedComponentBase::MSG_DISPATCH_OK); + + // Verify appropriate error response + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, PrmDbImpl::OPCODE_PRM_COMMIT_STAGED, 15, Fw::CmdResponse::VALIDATION_ERROR); + ASSERT_EVENTS_SIZE(1); + ASSERT_EVENTS_PrmDbFileLoadInvalidAction_SIZE(1); + ASSERT_EVENTS_PrmDbFileLoadInvalidAction(0, PrmDbFileLoadState::IDLE, PrmDb_PrmLoadAction::COMMIT_STAGED_COMMAND); + + // Verify state hasn't changed + EXPECT_EQ(this->m_impl.m_state, PrmDbFileLoadState::IDLE); + + // 3.2 Verify legal operations are still allowed in IDLE state + // Setting a parameter should work in IDLE state + this->clearEvents(); + this->clearHistory(); + this->invoke_to_setPrm(0, testId, pBuff); + dispatchStatus = this->m_impl.doDispatch(); + EXPECT_EQ(dispatchStatus, Fw::QueuedComponentBase::MSG_DISPATCH_OK); + + // Verify parameter was added + ASSERT_EVENTS_SIZE(1); + ASSERT_EVENTS_PrmIdAdded_SIZE(1); + + // ------------------------------------------------------------------- + // 4. Test state transitions back to IDLE after errors + // ------------------------------------------------------------------- + + // 4.1 Test that failed file load sets state back to IDLE + this->m_impl.m_state = PrmDbFileLoadState::LOADING_FILE_UPDATES; + this->m_impl.clearDb(PrmDbType::DB_STAGING); + + // Simulate failed file load + this->m_impl.m_state = PrmDbFileLoadState::IDLE; + + // Verify that setPrm now works + this->clearEvents(); + this->clearHistory(); + this->invoke_to_setPrm(0, testId + 1, pBuff); + dispatchStatus = this->m_impl.doDispatch(); + EXPECT_EQ(dispatchStatus, Fw::QueuedComponentBase::MSG_DISPATCH_OK); + + // Verify parameter was added + ASSERT_EVENTS_SIZE(1); + ASSERT_EVENTS_PrmIdAdded_SIZE(1); +} + +PrmDbTester* PrmDbTester::PrmDbTestFile::s_tester = nullptr; + +void PrmDbTester::PrmDbTestFile::setTester(Svc::PrmDbTester* tester) { + ASSERT_NE(tester, nullptr); + s_tester = tester; +} + +Os::File::Status PrmDbTester::PrmDbTestFile::read(U8* buffer, FwSizeType& size, Os::File::WaitType wait) { + EXPECT_NE(s_tester, nullptr); + Os::File::Status status = this->Os::Stub::File::Test::TestFile::read(buffer, size, wait); + if (s_tester->m_waits == 0) { + switch (s_tester->m_errorType) { + case FILE_STATUS_ERROR: + status = s_tester->m_status; + break; + case FILE_SIZE_ERROR: + size += 1; + break; + case FILE_DATA_ERROR: + buffer[0] += 1; + break; + default: + break; + } + } else { + s_tester->m_waits -= 1; + } + return status; +} + +Os::File::Status PrmDbTester::PrmDbTestFile::write(const U8* buffer, FwSizeType& size, Os::File::WaitType wait) { + EXPECT_NE(s_tester, nullptr); + Os::File::Status status = this->Os::Stub::File::Test::TestFile::write(buffer, size, wait); + if (s_tester->m_waits == 0) { + switch (s_tester->m_errorType) { + case FILE_STATUS_ERROR: + status = s_tester->m_status; + break; + case FILE_SIZE_ERROR: + size += 1; + break; + default: + break; + } + } else { + s_tester->m_waits -= 1; + } + return status; +} + PrmDbTester::PrmDbTester(Svc::PrmDbImpl& inst) : PrmDbGTestBase("testerbase", 100), m_impl(inst) { PrmDbTester::PrmDbTestFile::setTester(this); } @@ -611,6 +1346,25 @@ PrmDbTester::~PrmDbTester() {} void PrmDbTester ::from_pingOut_handler(const FwIndexType portNum, U32 key) { this->pushFromPortEntry_pingOut(key); } + +void PrmDbTester::printDb(PrmDb_PrmDbType dbType) { + PrmDbImpl::t_dbStruct* db = this->m_impl.getDbPtr(dbType); + printf("%s Parameter DB @ %p \n", PrmDbImpl::getDbString(dbType).toChar(), static_cast(db)); + for (FwSizeType entry = 0; entry < PRMDB_NUM_DB_ENTRIES; entry++) { + U8* data = db[entry].val.getBuffAddr(); + FwSizeType len = db[entry].val.getBuffLength(); + if (db[entry].used) { + std::cout << " " << std::setw(2) << entry << " :"; + printf(" ID = %08X", db[entry].id); + printf(" Value = "); + for (FwSizeType i = 0; i < len; ++i) { + printf("%02X ", data[i]); + } + printf("\n"); + } + } +} + } /* namespace Svc */ namespace Os { diff --git a/Svc/PrmDb/test/ut/PrmDbTester.hpp b/Svc/PrmDb/test/ut/PrmDbTester.hpp index 08e9b95a77..502c46534a 100644 --- a/Svc/PrmDb/test/ut/PrmDbTester.hpp +++ b/Svc/PrmDb/test/ut/PrmDbTester.hpp @@ -26,6 +26,12 @@ class PrmDbTester : public PrmDbGTestBase { void runMissingExtraParams(); void runFileReadError(); void runFileWriteError(); + void runDbEqualTest(); + void runDbCopyTest(); + void runDbCommitTest(); + void runPrmFileLoadNominal(); + void runPrmFileLoadWithErrors(); + void runPrmFileLoadIllegal(); void runRefPrmFile(); @@ -67,6 +73,8 @@ class PrmDbTester : public PrmDbGTestBase { static void setTester(PrmDbTester* tester); static PrmDbTester* s_tester; }; + + void printDb(PrmDb_PrmDbType dbType); }; } // namespace Svc