Provide ability to update parameters via a file (#4165)

* Save point for PRM_SET_FILE work, add basic infrastruture

* fprime-format

* Create prime and backup DB, initial implementation of file based set, some UT updates

* More work on Prime and Backup DBs including helper methods, utilizing those for copy, and UTs

* spelling

* printf in Tester fix

* printf in Tester fix

* printf in Tester fix

* Remove a debug printf, update new method args, additional offNom Set File tests

* Spelling and format

* Clean up comments

* Sync with upstream devel updates

* Add additional UT for reverting parameter db on set file failure

* Spelling and format

* Updates to PrmDb after first review; change to active/staging design

* Spelling

* Remove FIXME comment

* Review fixes

* Fix UT

* Format

* Format II

* Format III

---------

Co-authored-by: M Starch <LeStarch@googlemail.com>
This commit is contained in:
Mishaal 2025-10-14 13:40:27 -07:00 committed by GitHub
parent 9c11872889
commit 0fc995fefd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 1467 additions and 228 deletions

View File

@ -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"
}

View File

@ -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

View File

@ -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): {}."

View File

@ -18,8 +18,6 @@ static_assert(std::numeric_limits<FwSizeType>::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<FwAssertArgType>(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<FwSizeType>(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<U32>(sizeof(FwPrmIdType) + this->m_db[entry].val.getBuffLength());
U32 recordSize = static_cast<U32>(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<FwAssertArgType>(serStat));
@ -208,8 +220,8 @@ void PrmDbImpl::PRM_SAVE_FILE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) {
// write serialized parameter value
writeSize = static_cast<FwSizeType>(this->m_db[entry].val.getBuffLength());
stat = paramFile.write(this->m_db[entry].val.getBuffAddr(), writeSize, Os::File::WaitType::WAIT);
writeSize = static_cast<FwSizeType>(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<I32>(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<FwSizeType>(this->m_db[entry].val.getBuffLength())) {
if (writeSize != static_cast<FwSizeType>(db[entry].val.getBuffLength())) {
this->unLock();
this->log_WARNING_HI_PrmFileWriteError(PrmWriteError::PARAMETER_VALUE_SIZE,
static_cast<I32>(numRecords), static_cast<I32>(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<I32>(recordNum), fStat);
return;
this->log_WARNING_HI_PrmFileReadError(PrmReadError::DELIMITER, static_cast<I32>(recordNumTotal), fStat);
return PrmLoadStatus::ERROR;
}
if (sizeof(delimiter) != readSize) {
this->log_WARNING_HI_PrmFileReadError(PrmReadError::DELIMITER_SIZE, static_cast<I32>(recordNum),
this->log_WARNING_HI_PrmFileReadError(PrmReadError::DELIMITER_SIZE, static_cast<I32>(recordNumTotal),
static_cast<I32>(readSize));
return;
return PrmLoadStatus::ERROR;
}
if (PRMDB_ENTRY_DELIMITER != delimiter) {
this->log_WARNING_HI_PrmFileReadError(PrmReadError::DELIMITER_VALUE, static_cast<I32>(recordNum),
this->log_WARNING_HI_PrmFileReadError(PrmReadError::DELIMITER_VALUE, static_cast<I32>(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<I32>(recordNum), fStat);
return;
this->log_WARNING_HI_PrmFileReadError(PrmReadError::RECORD_SIZE, static_cast<I32>(recordNumTotal), fStat);
return PrmLoadStatus::ERROR;
}
if (sizeof(recordSize) != readSize) {
this->log_WARNING_HI_PrmFileReadError(PrmReadError::RECORD_SIZE_SIZE, static_cast<I32>(recordNum),
this->log_WARNING_HI_PrmFileReadError(PrmReadError::RECORD_SIZE_SIZE, static_cast<I32>(recordNumTotal),
static_cast<I32>(readSize));
return;
return PrmLoadStatus::ERROR;
}
// set serialized size to read size
Fw::SerializeStatus desStat = buff.setBuffLen(static_cast<Fw::Serializable::SizeType>(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<I32>(recordNum),
this->log_WARNING_HI_PrmFileReadError(PrmReadError::RECORD_SIZE_VALUE, static_cast<I32>(recordNumTotal),
static_cast<I32>(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<I32>(recordNum), fStat);
return;
this->log_WARNING_HI_PrmFileReadError(PrmReadError::PARAMETER_ID, static_cast<I32>(recordNumTotal), fStat);
return PrmLoadStatus::ERROR;
}
if (sizeof(parameterId) != static_cast<FwSizeType>(readSize)) {
this->log_WARNING_HI_PrmFileReadError(PrmReadError::PARAMETER_ID_SIZE, static_cast<I32>(recordNum),
this->log_WARNING_HI_PrmFileReadError(PrmReadError::PARAMETER_ID_SIZE, static_cast<I32>(recordNumTotal),
static_cast<I32>(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<Fw::Serializable::SizeType>(readSize));
FW_ASSERT(Fw::FW_SERIALIZE_OK == desStat, static_cast<FwAssertArgType>(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<I32>(recordNum), fStat);
return;
this->log_WARNING_HI_PrmFileReadError(PrmReadError::PARAMETER_VALUE, static_cast<I32>(recordNumTotal),
fStat);
return PrmLoadStatus::ERROR;
}
if (static_cast<U32>(readSize) != recordSize - sizeof(parameterId)) {
this->log_WARNING_HI_PrmFileReadError(PrmReadError::PARAMETER_VALUE_SIZE, static_cast<I32>(recordNum),
this->log_WARNING_HI_PrmFileReadError(PrmReadError::PARAMETER_VALUE_SIZE, static_cast<I32>(recordNumTotal),
static_cast<I32>(readSize));
return;
return PrmLoadStatus::ERROR;
}
// set serialized size to read size
desStat = this->m_db[entry].val.setBuffLen(static_cast<Fw::Serializable::SizeType>(readSize));
// should never fail
FW_ASSERT(Fw::FW_SERIALIZE_OK == desStat, static_cast<FwAssertArgType>(desStat));
recordNum++;
// Actually update or add parameter
PrmUpdateType updateStatus = updateAddPrmImpl(parameterId, tmpParamBuffer, dbType);
if (updateStatus == PARAM_ADDED) {
recordNumAdded++;
} else if (updateStatus == PARAM_UPDATED) {
recordNumUpdated++;
}
this->log_ACTIVITY_HI_PrmFileLoadComplete(recordNum);
if (updateStatus == NO_SLOTS) {
this->log_WARNING_HI_PrmDbFull(parameterId);
}
recordNumTotal++;
}
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

View File

@ -16,10 +16,17 @@
#include <Fw/Types/String.hpp>
#include <Os/Mutex.hpp>
#include <Svc/PrmDb/PrmDbComponentAc.hpp>
#include <Svc/PrmDb/PrmDb_PrmDbFileLoadStateEnumAc.hpp>
#include <Svc/PrmDb/PrmDb_PrmDbTypeEnumAc.hpp>
#include <config/PrmDbImplCfg.hpp>
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

View File

@ -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();

View File

@ -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<void*>(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 {

View File

@ -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