mirror of
https://github.com/nasa/fprime.git
synced 2025-12-10 00:44:37 -06:00
* 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>
870 lines
33 KiB
C++
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
|