fprime/Svc/DpCatalog/DpCatalog.cpp
Vince Woo 48e4720419
Created new SerialBufferBase as a parent of SerializeBufferBase (now renamed LinearBufferBase). (#4288)
* Created new SerialBufferBase as a parent of SerializeBufferBase. Renaming interface functions to be less confusing.

* Deprecating copyRawOffset. No direct use-cases in F' core.

* Make SerialBufferBase a true pure virtual interface.

* Changing Serializable to work with SerialBufferBase parent interface.

* Changing copyRaw and copyRawOffset to work with SerialBufferBase

* Updating documentation for SerialBufferBase usage

* Adding some documentation. Adding missing ASSERT in copyRaw. Fixing some bugs that new ASSERT uncovered.

* Renaming SerializeBufferBase to LinearBufferBase. Add a using declaration to maintain backwards compatability. Properly mark LinearBufferBase functions as override.

* Filling in the rest of the docstrings for the classes in Serializable

* Removing redundant virtual keyword on override function

* Applying clang formatting

* Incorporating PR comments

* Fix compile issues

* Bump version to alpha

* Format

* v

---------

Co-authored-by: M Starch <LeStarch@googlemail.com>
2025-11-06 16:23:20 -08:00

870 lines
33 KiB
C++

// ======================================================================
// \title DpCatalog.cpp
// \author tcanham
// \brief cpp file for DpCatalog component implementation class
// ======================================================================
#include "Svc/DpCatalog/DpCatalog.hpp"
#include "Fw/Dp/DpContainer.hpp"
#include "Fw/FPrimeBasicTypes.hpp"
#include <new> // placement new
#include "Fw/Types/StringUtils.hpp"
#include "Os/File.hpp"
#include "Os/FileSystem.hpp"
namespace Svc {
static_assert(DP_MAX_DIRECTORIES > 0, "Configuration DP_MAX_DIRECTORIES must be positive");
static_assert(DP_MAX_FILES > 0, "Configuration DP_MAX_FILES must be positive");
// ----------------------------------------------------------------------
// Component construction and destruction
// ----------------------------------------------------------------------
DpCatalog ::DpCatalog(const char* const compName)
: DpCatalogComponentBase(compName),
m_initialized(false),
m_dpTree(nullptr),
m_freeListHead(nullptr),
m_freeListFoot(nullptr),
m_traverseStack(nullptr),
m_currentNode(nullptr),
m_currentXmitNode(nullptr),
m_numDpRecords(0),
m_numDpSlots(0),
m_numDirectories(0),
m_stateFileData(nullptr),
m_stateFileEntries(0),
m_memSize(0),
m_memPtr(nullptr),
m_allocatorId(0),
m_allocator(nullptr),
m_xmitInProgress(false),
m_xmitCmdWait(false),
m_xmitBytes(0),
m_xmitOpCode(0),
m_xmitCmdSeq(0) {}
DpCatalog ::~DpCatalog() {}
void DpCatalog::configure(Fw::FileNameString directories[DP_MAX_DIRECTORIES],
FwSizeType numDirs,
Fw::FileNameString& stateFile,
FwEnumStoreType memId,
Fw::MemAllocator& allocator) {
// Do some assertion checks
FW_ASSERT(numDirs <= DP_MAX_DIRECTORIES, static_cast<FwAssertArgType>(numDirs));
FW_ASSERT(stateFile.length());
this->m_stateFile = stateFile;
// request memory for catalog which is DP_MAX_FILES * slot size.
//
// A "slot" consists of a set of three memory locations for each data product consisting
// an entry in the binary tree, an entry in the binary tree traversal stack, and
// an entry in the state file data. These may not be fully used in a given
// situation based on the number of actual data products, but this provides room for the
// maximum possible.
static const FwSizeType slotSize = sizeof(DpBtreeNode) + sizeof(DpBtreeNode**) + sizeof(DpDstateFileEntry);
this->m_memSize = DP_MAX_FILES * slotSize;
bool notUsed; // we don't need to recover the catalog.
// request memory. this->m_memSize will be modified if there is less than we requested
this->m_memPtr = allocator.allocate(memId, this->m_memSize, notUsed);
// adjust to actual size if less allocated and only initialize
// if there is enough room for at least one record and memory
// was allocated.
// Since we are given a monolithic block of memory, the data structures
// are interspersed in the memory using the following method:
//
// 1) Recompute how many slots can fit in the provided memory if we
// don't get the full amount requested. This allows for graceful degradation
// if there are memory issues.
//
// 2) Place the binary tree free list at the beginning of the memory.
//
// 3) Place the binary tree traverse stack in memory just after the binary
// tree free list by indexing the free list as an array one element past the
// end of the free list.
//
// 4) Place the state file data in memory after the binary tree traverse
// stack by indexing the traverse stack to one element past the end of
// the traverse tree.
if ((this->m_memSize >= sizeof(DpBtreeNode)) and (this->m_memPtr != nullptr)) {
// set the number of available record slots based on how much memory we actually got
this->m_numDpSlots = this->m_memSize / slotSize; // Step 1.
this->resetBinaryTree(); // Step 2
// assign pointer for the stack - Step 3
this->m_traverseStack = reinterpret_cast<DpBtreeNode**>(&this->m_freeListHead[this->m_numDpSlots]);
this->resetTreeStack();
// assign pointer for the state file storage - Step 4
this->m_stateFileData = reinterpret_cast<DpDstateFileEntry*>(&this->m_traverseStack[this->m_numDpSlots]);
} else {
// if we don't have enough memory, set the number of records
// to zero for later detection
this->m_numDpSlots = 0;
}
// assign directory names
for (FwSizeType dir = 0; dir < numDirs; dir++) {
this->m_directories[dir] = directories[dir];
}
this->m_numDirectories = numDirs;
// store allocator
this->m_allocator = &allocator;
this->m_allocatorId = memId;
this->m_initialized = true;
}
void DpCatalog::resetBinaryTree() {
// initialize data structures in the free list
// Step 2 in memory partition (see configure() comments)
FW_ASSERT(this->m_memPtr);
this->m_freeListHead = static_cast<DpBtreeNode*>(this->m_memPtr);
for (FwSizeType slot = 0; slot < this->m_numDpSlots; slot++) {
// overlay new instance of the DpState entry on the memory
(void)new (&this->m_freeListHead[slot]) DpBtreeNode();
this->m_freeListHead[slot].left = nullptr;
this->m_freeListHead[slot].right = nullptr;
// link the free list
if (slot > 0) {
this->m_freeListHead[slot - 1].left = &this->m_freeListHead[slot];
}
}
// set the foot of the free list
this->m_freeListFoot = &this->m_freeListHead[this->m_numDpSlots - 1];
// clear binary tree
this->m_dpTree = nullptr;
// reset number of records
this->m_numDpRecords = 0;
}
void DpCatalog::resetStateFileData() {
// clear state file data
for (FwSizeType slot = 0; slot < this->m_numDpSlots; slot++) {
this->m_stateFileData[slot].used = false;
this->m_stateFileData[slot].visited = false;
(void)new (&this->m_stateFileData[slot].entry.record) DpRecord();
}
this->m_stateFileEntries = 0;
}
Fw::CmdResponse DpCatalog::loadStateFile() {
FW_ASSERT(this->m_stateFileData);
// Make sure that a file was specified
if (this->m_stateFile.length() == 0) {
this->log_WARNING_LO_NoStateFileSpecified();
return Fw::CmdResponse::OK;
}
// buffer for reading entries
BYTE buffer[sizeof(FwIndexType) + DpRecord::SERIALIZED_SIZE];
Fw::ExternalSerializeBuffer entryBuffer(buffer, sizeof(buffer));
// open the state file
Os::File stateFile;
Os::File::Status stat = stateFile.open(this->m_stateFile.toChar(), Os::File::OPEN_READ);
if (stat != Os::File::OP_OK) {
this->log_WARNING_HI_StateFileOpenError(this->m_stateFile, stat);
return Fw::CmdResponse::EXECUTION_ERROR;
}
FwSizeType fileLoc = 0;
this->m_stateFileEntries = 0;
// read entries from the state file
for (FwSizeType entry = 0; entry < this->m_numDpSlots; entry++) {
FwSizeType size = static_cast<FwSizeType>(sizeof(buffer));
// read the directory index
stat = stateFile.read(buffer, size);
if (stat != Os::File::OP_OK) {
this->log_WARNING_HI_StateFileReadError(this->m_stateFile, stat, static_cast<I32>(fileLoc));
return Fw::CmdResponse::EXECUTION_ERROR;
}
if (0 == size) {
// no more entries
break;
}
// check to see if the full entry was read. If not,
// abandon it and finish. We can at least operate on
// the entries that were read.
if (size != sizeof(buffer)) {
this->log_WARNING_HI_StateFileTruncated(this->m_stateFile, static_cast<I32>(fileLoc),
static_cast<I32>(size));
return Fw::CmdResponse::OK;
}
// reset the buffer for deserializing the entry
Fw::SerializeStatus serStat = entryBuffer.setBuffLen(static_cast<Fw::Serializable::SizeType>(size));
// should always fit
FW_ASSERT(Fw::FW_SERIALIZE_OK == serStat, serStat);
entryBuffer.resetDeser();
// deserialization after this point should always work, since
// the source buffer was specifically sized to hold the data
// Deserialize the file directory index
Fw::SerializeStatus status = entryBuffer.deserializeTo(this->m_stateFileData[entry].entry.dir);
FW_ASSERT(Fw::FW_SERIALIZE_OK == status, status);
status = entryBuffer.deserializeTo(this->m_stateFileData[entry].entry.record);
FW_ASSERT(Fw::FW_SERIALIZE_OK == status, status);
this->m_stateFileData[entry].used = true;
this->m_stateFileData[entry].visited = false;
// increment the file location
fileLoc += size;
this->m_stateFileEntries++;
}
return Fw::CmdResponse::OK;
}
void DpCatalog::getFileState(DpStateEntry& entry) {
FW_ASSERT(this->m_stateFileData);
// search the file state data for the entry
for (FwSizeType line = 0; line < this->m_stateFileEntries; line++) {
// check for a match
if ((this->m_stateFileData[line].entry.dir == entry.dir) and
(this->m_stateFileData[line].entry.record.get_id() == entry.record.get_id()) and
(this->m_stateFileData[line].entry.record.get_tSec() == entry.record.get_tSec()) and
(this->m_stateFileData[line].entry.record.get_tSub() == entry.record.get_tSub()) and
(this->m_stateFileData[line].entry.record.get_priority() == entry.record.get_priority())) {
// update the transmitted state
entry.record.set_state(this->m_stateFileData[line].entry.record.get_state());
entry.record.set_blocks(this->m_stateFileData[line].entry.record.get_blocks());
// mark it as visited for later pruning if necessary
this->m_stateFileData[line].visited = true;
return;
}
}
}
void DpCatalog::pruneAndWriteStateFile() {
FW_ASSERT(this->m_stateFileData);
// There is a chance that a data product file can disappear after
// the state file is written from the last catalog build and transmit.
// This function will walk the state file data and write back only
// the entries that were visited during the last catalog build. This will
// remove any entries that are no longer valid.
// open the state file
Os::File stateFile;
// we open it as a new file so we don't accumulate invalid entries
Os::File::Status stat =
stateFile.open(this->m_stateFile.toChar(), Os::File::OPEN_CREATE, Os::FileInterface::OVERWRITE);
if (stat != Os::File::OP_OK) {
this->log_WARNING_HI_StateFileOpenError(this->m_stateFile, stat);
return;
}
// buffer for writing entries
BYTE buffer[sizeof(FwIndexType) + DpRecord::SERIALIZED_SIZE];
Fw::ExternalSerializeBuffer entryBuffer(buffer, sizeof(buffer));
// write entries to the state file
for (FwSizeType entry = 0; entry < this->m_numDpSlots; entry++) {
// only write entries that were used
if ((this->m_stateFileData[entry].used) and (this->m_stateFileData[entry].visited)) {
// reset the buffer for serializing the entry
entryBuffer.resetSer();
// serialize the file directory index
Fw::SerializeStatus serStat = entryBuffer.serializeFrom(this->m_stateFileData[entry].entry.dir);
// Should always fit
FW_ASSERT(Fw::FW_SERIALIZE_OK == serStat, serStat);
serStat = entryBuffer.serializeFrom(this->m_stateFileData[entry].entry.record);
// Should always fit
FW_ASSERT(Fw::FW_SERIALIZE_OK == serStat, serStat);
// write the entry
FwSizeType size = entryBuffer.getSize();
// Protect against overflow
stat = stateFile.write(buffer, size);
if (stat != Os::File::OP_OK) {
this->log_WARNING_HI_StateFileWriteError(this->m_stateFile, stat);
return;
}
}
}
// close the state file
stateFile.close();
}
void DpCatalog::appendFileState(const DpStateEntry& entry) {
FW_ASSERT(this->m_stateFileData);
FW_ASSERT(entry.dir < static_cast<FwIndexType>(this->m_numDirectories), static_cast<FwAssertArgType>(entry.dir),
static_cast<FwAssertArgType>(this->m_numDirectories));
// We will append state to the existing state file
// TODO: Have to handle case where state file has partially transmitted
// state already
// open the state file
Os::File stateFile;
// we open it as a new file so we don't accumulate invalid entries
Os::File::Status stat = stateFile.open(this->m_stateFile.toChar(), Os::File::OPEN_APPEND);
if (stat != Os::File::OP_OK) {
this->log_WARNING_HI_StateFileOpenError(this->m_stateFile, stat);
return;
}
// buffer for writing entries
BYTE buffer[sizeof(entry.dir) + sizeof(entry.record)];
Fw::ExternalSerializeBuffer entryBuffer(buffer, sizeof(buffer));
// reset the buffer for serializing the entry
entryBuffer.resetSer();
// serialize the file directory index
Fw::SerializeStatus serStat = entryBuffer.serializeFrom(entry.dir);
// should fit
FW_ASSERT(serStat == Fw::FW_SERIALIZE_OK, serStat);
serStat = entryBuffer.serializeFrom(entry.record);
// should fit
FW_ASSERT(serStat == Fw::FW_SERIALIZE_OK, serStat);
// write the entry
FwSizeType size = entryBuffer.getSize();
stat = stateFile.write(buffer, size);
if (stat != Os::File::OP_OK) {
this->log_WARNING_HI_StateFileWriteError(this->m_stateFile, stat);
return;
}
// close the state file
stateFile.close();
}
Fw::CmdResponse DpCatalog::doCatalogBuild() {
// check initialization
if (not this->checkInit()) {
this->log_WARNING_HI_NotInitialized();
return Fw::CmdResponse::EXECUTION_ERROR;
}
// check that initialization got memory
if (0 == this->m_numDpSlots) {
this->log_WARNING_HI_NoDpMemory();
return Fw::CmdResponse::EXECUTION_ERROR;
}
// make sure a downlink is not in progress
if (this->m_xmitInProgress) {
this->log_WARNING_LO_DpXmitInProgress();
return Fw::CmdResponse::EXECUTION_ERROR;
}
// reset state file data
this->resetStateFileData();
// load state data from file
Fw::CmdResponse response = this->loadStateFile();
// reset free list for entries
this->resetBinaryTree();
// fill the binary tree with DP files
response = this->fillBinaryTree();
if (response != Fw::CmdResponse::OK) {
// clean up the binary tree
this->resetBinaryTree();
this->resetStateFileData();
return response;
}
// prune and rewrite the state file
this->pruneAndWriteStateFile();
this->log_ACTIVITY_HI_CatalogBuildComplete();
return Fw::CmdResponse::OK;
}
Fw::CmdResponse DpCatalog::fillBinaryTree() {
// keep cumulative number of files
FwSizeType totalFiles = 0;
// file class instance for processing files
Os::File dpFile;
// Working buffer for DP headers
U8 dpBuff[Fw::DpContainer::MIN_PACKET_SIZE]; // Header buffer
Fw::Buffer hdrBuff(dpBuff, sizeof(dpBuff)); // buffer for container header decoding
Fw::DpContainer container; // container object for extracting header fields
// get file listings from file system
for (FwSizeType dir = 0; dir < this->m_numDirectories; dir++) {
// read in each directory and keep track of total
this->log_ACTIVITY_LO_ProcessingDirectory(this->m_directories[dir]);
FwSizeType filesRead = 0;
U32 pendingFiles = 0;
U64 pendingDpBytes = 0;
U32 filesProcessed = 0;
Os::Directory dpDir;
Os::Directory::Status status = dpDir.open(this->m_directories[dir].toChar(), Os::Directory::OpenMode::READ);
if (status != Os::Directory::OP_OK) {
this->log_WARNING_HI_DirectoryOpenError(this->m_directories[dir], status);
return Fw::CmdResponse::EXECUTION_ERROR;
}
status = dpDir.readDirectory(this->m_fileList, (this->m_numDpSlots - totalFiles), filesRead);
if (status != Os::Directory::OP_OK) {
this->log_WARNING_HI_DirectoryOpenError(this->m_directories[dir], status);
return Fw::CmdResponse::EXECUTION_ERROR;
}
// Assert number of files isn't more than asked
FW_ASSERT(filesRead <= this->m_numDpSlots - totalFiles, static_cast<FwAssertArgType>(filesRead),
static_cast<FwAssertArgType>(this->m_numDpSlots - totalFiles));
// extract metadata for each file
for (FwSizeType file = 0; file < filesRead; file++) {
// only consider files with the DP extension
FwSignedSizeType loc =
Fw::StringUtils::substring_find(this->m_fileList[file].toChar(), this->m_fileList[file].length(),
DP_EXT, Fw::StringUtils::string_length(DP_EXT, sizeof(DP_EXT)));
if (-1 == loc) {
continue;
}
Fw::String fullFile;
fullFile.format("%s/%s", this->m_directories[dir].toChar(), this->m_fileList[file].toChar());
this->log_ACTIVITY_LO_ProcessingFile(fullFile);
// get file size
FwSizeType fileSize = 0;
Os::FileSystem::Status sizeStat = Os::FileSystem::getFileSize(fullFile.toChar(), fileSize);
if (sizeStat != Os::FileSystem::OP_OK) {
this->log_WARNING_HI_FileSizeError(fullFile, sizeStat);
continue;
}
Os::File::Status stat = dpFile.open(fullFile.toChar(), Os::File::OPEN_READ);
if (stat != Os::File::OP_OK) {
this->log_WARNING_HI_FileOpenError(fullFile, stat);
continue;
}
// Read DP header
FwSizeType size = Fw::DpContainer::Header::SIZE;
stat = dpFile.read(dpBuff, size);
if (stat != Os::File::OP_OK) {
this->log_WARNING_HI_FileReadError(fullFile, stat);
dpFile.close();
continue; // maybe next file is fine
}
// if full header isn't read, something's wrong with the file, so skip
if (size != Fw::DpContainer::Header::SIZE) {
this->log_WARNING_HI_FileReadError(fullFile, Os::File::BAD_SIZE);
dpFile.close();
continue; // maybe next file is fine
}
// if all is well, don't need the file any more
dpFile.close();
// give buffer to container instance
container.setBuffer(hdrBuff);
// reset header deserialization in the container
Fw::SerializeStatus desStat = container.deserializeHeader();
if (desStat != Fw::FW_SERIALIZE_OK) {
this->log_WARNING_HI_FileHdrDesError(fullFile, desStat);
}
// add entry to catalog.
DpStateEntry entry;
entry.dir = static_cast<FwIndexType>(dir);
entry.record.set_id(container.getId());
entry.record.set_priority(container.getPriority());
entry.record.set_state(container.getState());
entry.record.set_tSec(container.getTimeTag().getSeconds());
entry.record.set_tSub(container.getTimeTag().getUSeconds());
entry.record.set_size(static_cast<U64>(fileSize));
// check the state file to see if there is transmit state
this->getFileState(entry);
// insert entry into sorted list. if can't insert, quit
bool insertedOk = this->insertEntry(entry);
if (not insertedOk) {
this->log_WARNING_HI_DpInsertError(entry.record);
// clean up and return
this->resetBinaryTree();
this->resetStateFileData();
break;
}
if (entry.record.get_state() == Fw::DpState::UNTRANSMITTED) {
pendingFiles++;
pendingDpBytes += entry.record.get_size();
}
// make sure we haven't exceeded the limit
if (this->m_numDpRecords > this->m_numDpSlots) {
this->log_WARNING_HI_DpCatalogFull(entry.record);
break;
}
filesProcessed++;
} // end for each file in a directory
totalFiles += filesProcessed;
this->log_ACTIVITY_HI_ProcessingDirectoryComplete(this->m_directories[dir], static_cast<U32>(totalFiles),
pendingFiles, pendingDpBytes);
// check to see if catalog is full
// that means generated products exceed the catalog size
if (totalFiles == this->m_numDpSlots) {
this->log_WARNING_HI_CatalogFull(this->m_directories[dir]);
break;
}
} // end for each directory
return Fw::CmdResponse::OK;
} // end fillBinaryTree()
bool DpCatalog::insertEntry(DpStateEntry& entry) {
// the tree is filled in the following priority order:
// 1. DP priority - lower number is higher priority
// 2. DP time - older is higher priority
// 3. DP ID - lower number is higher priority
// Higher priority is to the left of the tree
// if the tree is empty, add the first entry
if (this->m_dpTree == nullptr) {
bool goodInsert = this->allocateNode(this->m_dpTree, entry);
if (not goodInsert) {
return false;
}
// otherwise, search depth-first to sort the entry
} else {
// to avoid recursion, loop through a max of the number of available records
DpBtreeNode* node = this->m_dpTree;
for (FwSizeType record = 0; record < this->m_numDpSlots; record++) {
CheckStat stat = CheckStat::CHECK_CONT;
// check priority. Lower is higher priority
if (entry.record.get_priority() == node->entry.record.get_priority()) {
// check time. Older is higher priority
if (entry.record.get_tSec() == node->entry.record.get_tSec()) {
// check ID. Lower is higher priority
stat = this->checkLeftRight(entry.record.get_id() < node->entry.record.get_id(), node, entry);
} else { // if seconds are not equal. Older is higher priority
stat = this->checkLeftRight(entry.record.get_tSec() < node->entry.record.get_tSec(), node, entry);
}
} else { // if priority is not equal. Lower is higher priority.
stat =
this->checkLeftRight(entry.record.get_priority() < node->entry.record.get_priority(), node, entry);
} // end checking for left/right insertion
// act on status
if (stat == CheckStat::CHECK_ERROR) {
return false;
} else if (stat == CheckStat::CHECK_OK) {
break;
}
} // end for each possible record
}
// increment the number of records
this->m_numDpRecords++;
return true;
}
DpCatalog::CheckStat DpCatalog::checkLeftRight(bool condition, DpBtreeNode*& node, const DpStateEntry& newEntry) {
if (condition) {
if (node->left == nullptr) {
bool allocated = this->allocateNode(node->left, newEntry);
if (not allocated) {
return CheckStat::CHECK_ERROR;
}
return CheckStat::CHECK_OK;
} else {
node = node->left;
return CheckStat::CHECK_CONT;
}
} else {
if (node->right == nullptr) {
bool allocated = this->allocateNode(node->right, newEntry);
if (not allocated) {
return CheckStat::CHECK_ERROR;
}
return CheckStat::CHECK_OK;
} else {
node = node->right;
return CheckStat::CHECK_CONT;
}
}
}
bool DpCatalog::allocateNode(DpBtreeNode*& newNode, const DpStateEntry& newEntry) {
// should always be null since we are allocating an empty slot
FW_ASSERT(newNode == nullptr);
// make sure there is an entry from the free list
if (this->m_freeListHead == nullptr) {
this->log_WARNING_HI_DpCatalogFull(newEntry.record);
return false;
}
// get a new node from the free list
newNode = this->m_freeListHead;
// move the head of the free list to the next node
this->m_freeListHead = this->m_freeListHead->left;
// initialize the new node
newNode->left = nullptr;
newNode->right = nullptr;
newNode->entry = newEntry;
// we got one, so return success
return true;
}
void DpCatalog::deleteEntry(DpStateEntry& entry) {}
void DpCatalog::sendNextEntry() {
// check some asserts
FW_ASSERT(this->m_dpTree);
FW_ASSERT(this->m_xmitInProgress);
FW_ASSERT(this->m_traverseStack);
// look in the tree for the next entry to send
this->m_currentXmitNode = this->findNextTreeNode();
if (this->m_currentXmitNode == nullptr) {
// if no entry found, we are done
this->m_xmitInProgress = false;
this->log_ACTIVITY_HI_CatalogXmitCompleted(this->m_xmitBytes);
if (this->m_xmitCmdWait) {
this->cmdResponse_out(this->m_xmitOpCode, this->m_xmitCmdSeq, Fw::CmdResponse::OK);
}
return;
} else {
// build file name based on the found entry
this->m_currXmitFileName.format(
DP_FILENAME_FORMAT, this->m_directories[this->m_currentXmitNode->entry.dir].toChar(),
this->m_currentXmitNode->entry.record.get_id(), this->m_currentXmitNode->entry.record.get_tSec(),
this->m_currentXmitNode->entry.record.get_tSub());
this->log_ACTIVITY_LO_SendingProduct(this->m_currXmitFileName,
static_cast<U32>(this->m_currentXmitNode->entry.record.get_size()),
this->m_currentXmitNode->entry.record.get_priority());
Svc::SendFileResponse resp = this->fileOut_out(0, this->m_currXmitFileName, this->m_currXmitFileName, 0, 0);
if (resp.get_status() != Svc::SendFileStatus::STATUS_OK) {
// warn, but keep going since it may be an issue with this file but others could
// make it
this->log_WARNING_HI_DpFileSendError(this->m_currXmitFileName, resp.get_status());
}
}
} // end sendNextEntry()
DpCatalog::DpBtreeNode* DpCatalog::findNextTreeNode() {
// check some asserts
FW_ASSERT(this->m_dpTree);
FW_ASSERT(this->m_xmitInProgress);
FW_ASSERT(this->m_traverseStack);
DpBtreeNode* found = nullptr;
// traverse the tree, finding nodes in order. Max iteration of the loop
// would be the number of records in the tree
for (FwSizeType record = 0; record < this->m_numDpRecords; record++) {
// initialize found entry to nullptr
found = nullptr;
// check for current node to be null
if (this->m_currentNode == nullptr) {
// see if we fully traversed the tree
if (this->m_currStackEntry < 0) {
// Step 5 - we are done
return nullptr;
} else {
// Step 4 - if the current node is null, pop back up the stack
this->m_currentNode = this->m_traverseStack[this->m_currStackEntry--];
if (this->m_currentNode->entry.record.get_state() != Fw::DpState::TRANSMITTED) {
found = this->m_currentNode;
} // check if transmitted
this->m_currentNode = this->m_currentNode->right;
if (found != nullptr) {
return found;
}
}
break;
} else {
if (this->m_currentNode->left != nullptr) {
// Step 3 - push current entry on the stack
this->m_traverseStack[++this->m_currStackEntry] = this->m_currentNode;
this->m_currentNode = this->m_currentNode->left;
} else {
// Step 4 - check to see if this node has already been transmitted, if so, pop back up the stack
if (this->m_currentNode->entry.record.get_state() != Fw::DpState::TRANSMITTED) {
// we found an entry, so set the return to the current node
found = this->m_currentNode;
} // check if transmitted
// go to the right node
this->m_currentNode = this->m_currentNode->right;
// if a node was found, return it
if (found != nullptr) {
return found;
}
} // check if left is null
} // end else current node is not null
} // end for each possible node in the tree
return found;
}
bool DpCatalog::checkInit() {
if (not this->m_initialized) {
this->log_WARNING_HI_ComponentNotInitialized();
return false;
} else if (0 == this->m_numDpSlots) {
this->log_WARNING_HI_ComponentNoMemory();
return false;
}
return true;
}
void DpCatalog::shutdown() {
// only try to deallocate if both pointers are non-zero
// it's a way to more gracefully shut down if there are missing
// pointers
if ((this->m_allocator != nullptr) and (this->m_memPtr != nullptr)) {
this->m_allocator->deallocate(this->m_allocatorId, this->m_memPtr);
}
}
// ----------------------------------------------------------------------
// Handler implementations for user-defined typed input ports
// ----------------------------------------------------------------------
void DpCatalog ::fileDone_handler(FwIndexType portNum, const Svc::SendFileResponse& resp) {
// check some asserts
FW_ASSERT(this->m_dpTree);
FW_ASSERT(this->m_traverseStack);
// check file status
if (resp.get_status() != Svc::SendFileStatus::STATUS_OK) {
this->log_WARNING_HI_DpFileXmitError(this->m_currXmitFileName, resp.get_status());
this->m_xmitInProgress = false;
this->cmdResponse_out(this->m_xmitOpCode, this->m_xmitCmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
}
this->log_ACTIVITY_LO_ProductComplete(this->m_currXmitFileName);
// mark the entry as transmitted
this->m_currentXmitNode->entry.record.set_state(Fw::DpState::TRANSMITTED);
// update the transmitted state in the state file
this->appendFileState(this->m_currentXmitNode->entry);
// add the size
this->m_xmitBytes += this->m_currentXmitNode->entry.record.get_size();
// send the next entry, if it exists
this->sendNextEntry();
}
void DpCatalog ::pingIn_handler(FwIndexType portNum, U32 key) {
// return code for health ping
this->pingOut_out(0, key);
}
// ----------------------------------------------------------------------
// Handler implementations for commands
// ----------------------------------------------------------------------
void DpCatalog ::BUILD_CATALOG_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) {
// invoke helper
this->cmdResponse_out(opCode, cmdSeq, this->doCatalogBuild());
}
void DpCatalog ::START_XMIT_CATALOG_cmdHandler(FwOpcodeType opCode, U32 cmdSeq, Fw::Wait wait) {
Fw::CmdResponse resp = this->doCatalogXmit();
if (resp != Fw::CmdResponse::OK) {
this->cmdResponse_out(opCode, cmdSeq, resp);
} else {
if (Fw::Wait::NO_WAIT == wait) {
this->cmdResponse_out(opCode, cmdSeq, resp);
this->m_xmitCmdWait = false;
this->m_xmitOpCode = 0;
this->m_xmitCmdSeq = 0;
} else {
this->m_xmitCmdWait = true;
this->m_xmitOpCode = opCode;
this->m_xmitCmdSeq = cmdSeq;
}
}
}
Fw::CmdResponse DpCatalog::doCatalogXmit() {
// check initialization
if (not this->checkInit()) {
this->log_WARNING_HI_NotInitialized();
return Fw::CmdResponse::EXECUTION_ERROR;
}
// check that initialization got memory
if (0 == this->m_numDpSlots) {
this->log_WARNING_HI_NoDpMemory();
return Fw::CmdResponse::EXECUTION_ERROR;
}
// make sure a downlink is not in progress
if (this->m_xmitInProgress) {
this->log_WARNING_LO_DpXmitInProgress();
return Fw::CmdResponse::EXECUTION_ERROR;
}
// start transmission
this->m_xmitBytes = 0;
// make sure we have valid pointers
FW_ASSERT(this->m_dpTree);
FW_ASSERT(this->m_traverseStack);
// Traverse the tree using a stack to avoid recursion
// https://codestandard.net/articles/binary-tree-inorder-traversal/
this->resetTreeStack();
this->m_xmitInProgress = true;
// Step 3b - search for and send first entry
this->sendNextEntry();
return Fw::CmdResponse::OK;
}
void DpCatalog::resetTreeStack() {
// See URL above
// Step 1 - reset the stack
this->m_currStackEntry = -1;
// Step 2 - assign root of the tree to the current entry
this->m_currentNode = this->m_dpTree;
}
void DpCatalog ::STOP_XMIT_CATALOG_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) {
if (not this->m_xmitInProgress) {
this->log_WARNING_LO_XmitNotActive();
// benign error, so don't fail the command
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
} else {
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
}
}
void DpCatalog ::CLEAR_CATALOG_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) {
// TODO
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
}
} // namespace Svc