F´ Flight Software - C/C++ Documentation
A framework for building embedded system applications to NASA flight quality standards.
DpCatalog.cpp
Go to the documentation of this file.
1 // ======================================================================
2 
3 // \title DpCatalog.cpp
4 // \author tcanham
5 // \brief cpp file for DpCatalog component implementation class
6 // ======================================================================
7 
9 #include "Fw/Dp/DpContainer.hpp"
10 #include "Fw/FPrimeBasicTypes.hpp"
11 
12 #include <new> // placement new
13 #include "Fw/Types/StringUtils.hpp"
14 #include "Os/File.hpp"
15 #include "Os/FileSystem.hpp"
16 #include "Utils/Hash/Hash.hpp"
17 
18 namespace Svc {
19 static_assert(DP_MAX_DIRECTORIES > 0, "Configuration DP_MAX_DIRECTORIES must be positive");
20 static_assert(DP_MAX_FILES > 0, "Configuration DP_MAX_FILES must be positive");
21 // ----------------------------------------------------------------------
22 // Component construction and destruction
23 // ----------------------------------------------------------------------
24 
25 DpCatalog ::DpCatalog(const char* const compName) : DpCatalogComponentBase(compName) {
26  // Members are default-initialized via in-class initializers in the header.
27 }
28 
30  Fw::FileNameString& stateFile,
31  FwEnumStoreType memId,
32  Fw::MemAllocator& allocator) {
33  const FwSizeType numDirs = directories.getSize();
34  // Do some assertion checks
35  FW_ASSERT(numDirs <= DP_MAX_DIRECTORIES, static_cast<FwAssertArgType>(numDirs));
36 
37  this->m_stateFile = stateFile;
38 
39  // Request memory for state file data storage.
40  // RedBlackTreeSet storage is allocated as a member variable, so we only need
41  // to allocate memory for the state file tracking array.
42  static const FwSizeType slotSize = sizeof(DpDstateFileEntry);
43  this->m_memSize = DP_MAX_FILES * slotSize;
44  bool notUsed; // we don't need to recover the catalog.
45  // request memory. this->m_memSize will be modified if there is less than we requested
46  this->m_memPtr = allocator.allocate(memId, this->m_memSize, notUsed);
47 
48  // Initialize if there is enough room for at least one record and memory was allocated
49  if ((this->m_memSize >= slotSize) and (this->m_memPtr != nullptr)) {
50  // set the number of available record slots based on how much memory we actually got,
51  // never exceeding the DP_MAX_FILES working arrays even if the allocator returned more
52  // memory than was requested
53  const FwSizeType allocatedSlots = this->m_memSize / slotSize;
54  this->m_numDpSlots = (allocatedSlots < DP_MAX_FILES) ? allocatedSlots : DP_MAX_FILES;
55  // Initialize the catalog
56  this->resetCatalog();
57  // assign pointer for the state file storage
58  this->m_stateFileData = static_cast<DpDstateFileEntry*>(this->m_memPtr);
59  } else {
60  // if we don't have enough memory, set the number of records
61  // to zero for later detection
62  this->m_numDpSlots = 0;
63  }
64 
65  // assign directory names
66  for (FwSizeType dir = 0; dir < numDirs; dir++) {
67  this->m_directories[dir] = directories[dir];
68  }
69  this->m_numDirectories = numDirs;
70 
71  // store allocator
72  this->m_allocator = &allocator;
73  this->m_allocatorId = memId;
74  this->m_initialized = true;
75 }
76 
78  FwSizeType numDirs,
79  Fw::FileNameString& stateFile,
80  FwEnumStoreType memId,
81  Fw::MemAllocator& allocator) {
82  FW_ASSERT(numDirs <= DP_MAX_DIRECTORIES, static_cast<FwAssertArgType>(numDirs));
83  const Fw::ExternalArray<Fw::FileNameString> directoryArray(directories, numDirs);
84  this->configure(directoryArray, stateFile, memId, allocator);
85 }
86 
87 void DpCatalog::resetCatalog() {
88  // Clear the catalog
89  this->m_dpCatalog.clear();
90  // Clear transmission state
91  this->m_hasCurrentXmit = false;
92  // Reset counters
93  this->m_pendingFiles = 0;
94  this->m_pendingDpBytes = 0;
95  // Mark the catalog as un-built
96  this->m_catalogBuilt = false;
97 }
98 
99 void DpCatalog::resetStateFileData() {
100  // clear state file data
101  for (FwSizeType slot = 0; slot < this->m_numDpSlots; slot++) {
102  this->m_stateFileData[slot].used = false;
103  this->m_stateFileData[slot].visited = false;
104  (void)new (&this->m_stateFileData[slot].entry.record) DpRecord();
105  }
106  this->m_stateFileEntries = 0;
107 }
108 
109 Fw::CmdResponse DpCatalog::loadStateFile() {
110  FW_ASSERT(this->m_stateFileData != nullptr);
111 
112  // Make sure that a file was specified
113  if (this->m_stateFile.length() == 0) {
115  return Fw::CmdResponse::OK;
116  }
117 
118  // buffer for reading entries
119 
120  BYTE buffer[sizeof(FwIndexType) + DpRecord::SERIALIZED_SIZE];
121  Fw::ExternalSerializeBuffer entryBuffer(buffer, sizeof(buffer));
122 
123  // open the state file
124  Os::File stateFile;
125  Os::File::Status stat = stateFile.open(this->m_stateFile.toChar(), Os::File::OPEN_READ);
126  if (stat == Os::File::DOESNT_EXIST) {
127  // A missing state file is expected on first boot and is not an error
128  this->log_WARNING_LO_NoStateFile(this->m_stateFile);
129  return Fw::CmdResponse::OK;
130  }
131  if (stat != Os::File::OP_OK) {
132  this->log_WARNING_HI_StateFileOpenError(this->m_stateFile, stat);
134  }
135 
136  FwSizeType fileLoc = 0;
137  this->m_stateFileEntries = 0;
138 
139  // read entries from the state file
140  for (FwSizeType entry = 0; entry < this->m_numDpSlots; entry++) {
141  FwSizeType size = static_cast<FwSizeType>(sizeof(buffer));
142  // read the directory index
143  stat = stateFile.read(buffer, size);
144  if (stat != Os::File::OP_OK) {
145  this->log_WARNING_HI_StateFileReadError(this->m_stateFile, stat, static_cast<I32>(fileLoc));
146  stateFile.close();
148  }
149 
150  if (0 == size) {
151  // no more entries
152  break;
153  }
154 
155  // check to see if the full entry was read. If not,
156  // abandon it and finish. We can at least operate on
157  // the entries that were read.
158  if (size != sizeof(buffer)) {
159  this->log_WARNING_HI_StateFileTruncated(this->m_stateFile, static_cast<I32>(fileLoc),
160  static_cast<I32>(size));
161  stateFile.close();
162  return Fw::CmdResponse::OK;
163  }
164 
165  // reset the buffer for deserializing the entry
166  Fw::SerializeStatus serStat = entryBuffer.setBuffLen(static_cast<Fw::Serializable::SizeType>(size));
167  // should always fit
168  FW_ASSERT(Fw::FW_SERIALIZE_OK == serStat, serStat);
169  entryBuffer.resetDeser();
170 
171  // deserialization after this point should always work, since
172  // the source buffer was specifically sized to hold the data
173 
174  // Deserialize the file directory index. If an error occurs processing the file,
175  // generate event and return EXECUTION_ERROR.
176  Fw::SerializeStatus status = entryBuffer.deserializeTo(this->m_stateFileData[entry].entry.dir);
177  if (status != Fw::FW_SERIALIZE_OK) {
178  this->log_WARNING_HI_FileCorruptedDataError(this->m_stateFile, static_cast<I32>(status));
179  stateFile.close();
181  }
182  status = entryBuffer.deserializeTo(this->m_stateFileData[entry].entry.record);
183  if (status != Fw::FW_SERIALIZE_OK) {
184  this->log_WARNING_HI_FileCorruptedDataError(this->m_stateFile, static_cast<I32>(status));
185  stateFile.close();
187  }
188  this->m_stateFileData[entry].used = true;
189  this->m_stateFileData[entry].visited = false;
190 
191  // increment the file location
192  fileLoc += size;
193  this->m_stateFileEntries++;
194  }
195  stateFile.close();
196  return Fw::CmdResponse::OK;
197 }
198 
199 void DpCatalog::getFileState(DpStateEntry& entry) {
200  FW_ASSERT(this->m_stateFileData != nullptr);
201  // search the file state data for the entry
202  for (FwSizeType line = 0; line < this->m_stateFileEntries; line++) {
203  // check for a match (compare dir, then id, priority, & time)
204  if (this->m_stateFileData[line].entry.dir == entry.dir && this->m_stateFileData[line].entry == entry) {
205  // update the transmitted state
206  entry.record.set_state(this->m_stateFileData[line].entry.record.get_state());
207  entry.record.set_blocks(this->m_stateFileData[line].entry.record.get_blocks());
208  // mark it as visited for later pruning if necessary
209  this->m_stateFileData[line].visited = true;
210  return;
211  }
212  }
213 }
214 
215 void DpCatalog::pruneAndWriteStateFile() {
216  FW_ASSERT(this->m_stateFileData != nullptr);
217 
218  // There is a chance that a data product file can disappear after
219  // the state file is written from the last catalog build and transmit.
220  // This function will walk the state file data and write back only
221  // the entries that were visited during the last catalog build. This will
222  // remove any entries that are no longer valid.
223 
224  // open the state file
225  Os::File stateFile;
226  // we open it as a new file so we don't accumulate invalid entries
227  Os::File::Status stat =
228  stateFile.open(this->m_stateFile.toChar(), Os::File::OPEN_CREATE, Os::FileInterface::OVERWRITE);
229 
230  if (stat != Os::File::OP_OK) {
231  this->log_WARNING_HI_StateFileOpenError(this->m_stateFile, stat);
232  return;
233  }
234 
235  // buffer for writing entries
236  BYTE buffer[sizeof(FwIndexType) + DpRecord::SERIALIZED_SIZE];
237  Fw::ExternalSerializeBuffer entryBuffer(buffer, sizeof(buffer));
238 
239  // write entries to the state file
240  for (FwSizeType entry = 0; entry < this->m_numDpSlots; entry++) {
241  // only write entries that were used
242  if ((this->m_stateFileData[entry].used) and (this->m_stateFileData[entry].visited)) {
243  // reset the buffer for serializing the entry
244  entryBuffer.resetSer();
245  // serialize the file directory index
246  Fw::SerializeStatus serStat = entryBuffer.serializeFrom(this->m_stateFileData[entry].entry.dir);
247  // Should always fit
248  FW_ASSERT(Fw::FW_SERIALIZE_OK == serStat, serStat);
249  serStat = entryBuffer.serializeFrom(this->m_stateFileData[entry].entry.record);
250  // Should always fit
251  FW_ASSERT(Fw::FW_SERIALIZE_OK == serStat, serStat);
252  // write the entry
253  FwSizeType size = entryBuffer.getSize();
254  // Protect against overflow
255  stat = stateFile.write(buffer, size);
256  if (stat != Os::File::OP_OK) {
257  this->log_WARNING_HI_StateFileWriteError(this->m_stateFile, stat);
258  stateFile.close();
259  return;
260  }
261  }
262  }
263 
264  // close the state file
265  stateFile.close();
266 }
267 
268 void DpCatalog::appendFileState(const DpStateEntry& entry) {
269  FW_ASSERT(this->m_stateFileData != nullptr);
270  FW_ASSERT(entry.dir < static_cast<FwIndexType>(this->m_numDirectories), static_cast<FwAssertArgType>(entry.dir),
271  static_cast<FwAssertArgType>(this->m_numDirectories));
272 
273  // We will append state to the existing state file
274  // TODO: Have to handle case where state file has partially transmitted
275  // state already
276 
277  // open the state file
278  Os::File stateFile;
279  // we open it as a new file so we don't accumulate invalid entries
280  Os::File::Status stat = stateFile.open(this->m_stateFile.toChar(), Os::File::OPEN_APPEND);
281  if (stat != Os::File::OP_OK) {
282  this->log_WARNING_HI_StateFileOpenError(this->m_stateFile, stat);
283  return;
284  }
285 
286  // buffer for writing entries
287  BYTE buffer[sizeof(FwIndexType) + DpRecord::SERIALIZED_SIZE];
288  Fw::ExternalSerializeBuffer entryBuffer(buffer, sizeof(buffer));
289  // reset the buffer for serializing the entry
290  entryBuffer.resetSer();
291  // serialize the file directory index
292  Fw::SerializeStatus serStat = entryBuffer.serializeFrom(entry.dir);
293  // should fit
294  FW_ASSERT(serStat == Fw::FW_SERIALIZE_OK, serStat);
295  serStat = entryBuffer.serializeFrom(entry.record);
296  // should fit
297  FW_ASSERT(serStat == Fw::FW_SERIALIZE_OK, serStat);
298  // write the entry
299  FwSizeType size = entryBuffer.getSize();
300  stat = stateFile.write(buffer, size);
301  if (stat != Os::File::OP_OK) {
302  stateFile.close();
303  this->log_WARNING_HI_StateFileWriteError(this->m_stateFile, stat);
304  return;
305  }
306 
307  // close the state file
308  stateFile.close();
309 }
310 
311 Fw::CmdResponse DpCatalog::doCatalogBuild() {
312  // check initialization
313  if (not this->checkInit()) {
315  }
316 
317  // check that initialization got memory
318  if (0 == this->m_numDpSlots) {
321  }
322 
323  // make sure a downlink is not in progress
324  if (this->m_xmitInProgress) {
327  }
328 
329  // reset state file data
330  this->resetStateFileData();
331 
332  // load state data from file; proceeding on a failed load would later
333  // overwrite the state file and destroy the transmit state it records
334  Fw::CmdResponse response = this->loadStateFile();
335  if (response != Fw::CmdResponse::OK) {
336  this->resetStateFileData();
337  return response;
338  }
339 
340  // reset catalog
341  this->resetCatalog();
342 
343  // fill the catalog with DP files
344  response = this->fillBinaryTree();
345  if (response != Fw::CmdResponse::OK) {
346  // clean up the catalog
347  this->resetCatalog();
348  this->resetStateFileData();
349  return response;
350  }
351 
352  // prune and rewrite the state file
353  this->pruneAndWriteStateFile();
354 
356 
357  // Flag so addToCat knows it is good to go
358  this->m_catalogBuilt = true;
359 
360  return Fw::CmdResponse::OK;
361 }
362 
363 Fw::CmdResponse DpCatalog::fillBinaryTree() {
364  // keep cumulative number of files
365  FwSizeType totalFiles = 0;
366 
367  // get file listings from file system
368  // double bounds to appease static analysis
369  for (FwSizeType dir = 0; dir < this->m_numDirectories && dir < static_cast<FwSizeType>(DP_MAX_DIRECTORIES); dir++) {
370  // read in each directory and keep track of total
371  this->log_ACTIVITY_LO_ProcessingDirectory(this->m_directories[dir]);
372  FwSizeType filesRead = 0;
373  U32 filesProcessed = 0;
374 
375  Os::Directory dpDir;
376  Os::Directory::Status status = dpDir.open(this->m_directories[dir].toChar(), Os::Directory::OpenMode::READ);
377  if (status != Os::Directory::OP_OK) {
378  this->log_WARNING_HI_DirectoryOpenError(this->m_directories[dir], status);
380  }
381  Fw::ExternalArray<Fw::String> fileList(this->m_fileList, this->m_numDpSlots - totalFiles);
382  status = dpDir.readDirectory(fileList, filesRead);
383 
384  if (status != Os::Directory::OP_OK) {
385  this->log_WARNING_HI_DirectoryOpenError(this->m_directories[dir], status);
387  }
388 
389  // Assert number of files isn't more than asked
390  FW_ASSERT(filesRead <= this->m_numDpSlots - totalFiles, static_cast<FwAssertArgType>(filesRead),
391  static_cast<FwAssertArgType>(this->m_numDpSlots - totalFiles));
392 
393  // extract metadata for each file
394  for (FwSizeType file = 0; file < filesRead; file++) {
395  // only consider files with the DP extension
396 
397  const FwSizeType fileNameLength = this->m_fileList[file].length();
398  const FwSizeType dpExtLength = Fw::StringUtils::string_length(DP_EXT, sizeof(DP_EXT));
399  const FwSignedSizeType loc = Fw::StringUtils::substring_find_last(this->m_fileList[file].toChar(),
400  fileNameLength, DP_EXT, dpExtLength);
401 
402  // Only accept files whose final suffix is the data product extension
403  if ((-1 == loc) || (static_cast<FwSizeType>(loc) + dpExtLength != fileNameLength)) {
404  continue;
405  }
406 
407  Fw::String fullFile;
408  Fw::FormatStatus formatStatus =
409  fullFile.format("%s/%s", this->m_directories[dir].toChar(), this->m_fileList[file].toChar());
410  if (formatStatus != Fw::FormatStatus::SUCCESS) {
411  this->log_WARNING_HI_FileNameFormatError(this->m_fileList[file],
412  static_cast<Fw::StringFormatStatus::T>(formatStatus));
413  continue;
414  }
415 
416  const ProcessFileStatus ret = processFile(fullFile, dir);
417  if (ret == ProcessFileStatus::QUIT) {
418  break;
419  }
420 
421  if (ret == ProcessFileStatus::SUCCESS) {
422  filesProcessed++;
423  }
424 
425  } // end for each file in a directory
426 
427  totalFiles += filesProcessed;
428 
429  this->log_ACTIVITY_HI_ProcessingDirectoryComplete(this->m_directories[dir], static_cast<U32>(totalFiles),
430  this->m_pendingFiles, this->m_pendingDpBytes);
431 
432  // check to see if catalog is full
433  // that means generated products exceed the catalog size
434  if (totalFiles == this->m_numDpSlots) {
435  this->log_WARNING_HI_CatalogFull(this->m_directories[dir]);
436  break;
437  }
438  } // end for each directory
439 
440  return Fw::CmdResponse::OK;
441 
442 } // end fillBinaryTree()
443 
444 FwSizeType DpCatalog::determineDirectory(const Fw::String& fullFile) {
445  FW_ASSERT(this->m_numDirectories <= DP_MAX_DIRECTORIES, static_cast<FwAssertArgType>(this->m_numDirectories));
446  // Grab the directory string (up until the final slash)
447  // Could be found directly w/ a dirname func or regex
449  fullFile.toChar(), fullFile.length(), DIRECTORY_DELIMITER,
451 
452  // Seems like the logic works so long as the path styles match (i.e. relative vs absolute)
453  // Full path resolution might be a worthwhile add
454 
455  // No directory delimiter found; return DP_MAX_DIRECTORIES to signal failure
456  if (-1 == loc) {
457  return DP_MAX_DIRECTORIES;
458  }
459 
460  for (FwSizeType dir = 0; dir < this->m_numDirectories; dir++) {
461  const Fw::FileNameString& dir_string = this->m_directories[dir];
462 
463  // Compare both strings up to location of final slash
464  // StringUtils::substring_find will return zero if both paths agree
465  // memory safe since both are fixed width strings
466  // and loc is before the fixed width
467  if ((dir_string.length() == static_cast<FwSizeType>(loc)) &&
468  (Fw::StringUtils::substring_find(dir_string.toChar(), dir_string.length(), fullFile.toChar(),
469  static_cast<FwSizeType>(loc)) == 0)) {
470  return dir;
471  }
472  }
473 
474  // No directory matched
475  return DP_MAX_DIRECTORIES;
476 }
477 
478 DpCatalog::ProcessFileStatus DpCatalog::processFile(const Fw::String& fullFile, FwSizeType dir) {
479  FW_ASSERT(dir < static_cast<FwSizeType>(DP_MAX_DIRECTORIES), static_cast<FwAssertArgType>(dir));
480  // file class instance for processing files
481  Os::File dpFile;
482 
483  // Working buffer for DP headers
484  U8 dpBuff[Fw::DpContainer::MIN_PACKET_SIZE]; // Header buffer
485  Fw::Buffer hdrBuff(dpBuff, sizeof(dpBuff)); // buffer for container header decoding
486  Fw::DpContainer container; // container object for extracting header fields
487 
488  this->log_ACTIVITY_LO_ProcessingFile(fullFile);
489 
490  // get file size
491  FwSizeType fileSize = 0;
492  Os::FileSystem::Status sizeStat = Os::FileSystem::getFileSize(fullFile.toChar(), fileSize);
493  if (sizeStat != Os::FileSystem::OP_OK) {
494  this->log_WARNING_HI_FileSizeError(fullFile, sizeStat);
495  return ProcessFileStatus::FAILED;
496  }
497 
498  if (fileSize < Fw::DpContainer::MIN_PACKET_SIZE) {
500  return ProcessFileStatus::FAILED;
501  }
502 
503  Os::File::Status stat = dpFile.open(fullFile.toChar(), Os::File::OPEN_READ);
504  if (stat != Os::File::OP_OK) {
505  this->log_WARNING_HI_FileOpenError(fullFile, stat);
506  return ProcessFileStatus::FAILED;
507  }
508 
509  // Read DP header and header hash
511 
512  stat = dpFile.read(dpBuff, size);
513  if (stat != Os::File::OP_OK) {
514  this->log_WARNING_HI_FileReadError(fullFile, stat);
515  dpFile.close();
516  return ProcessFileStatus::FAILED;
517  }
518 
519  // if full header and hashes aren't read, something's wrong with the file, so skip
520  if (size != Fw::DpContainer::MIN_PACKET_SIZE) {
522  dpFile.close();
523  return ProcessFileStatus::FAILED;
524  }
525 
526  // if all is well, don't need the file any more
527  dpFile.close();
528 
529  // give buffer to container instance
530  container.setBuffer(hdrBuff);
531 
532  // make sure the header metadata matches its stored hash before trusting it
533  Utils::HashBuffer storedHash;
534  Utils::HashBuffer computedHash;
535  Fw::Success::T hashStatus = container.checkHeaderHash(storedHash, computedHash);
536  if (hashStatus != Fw::Success::SUCCESS) {
537  this->log_WARNING_HI_FileHdrError(fullFile, DpHdrField::CRC, computedHash.asBigEndianU32(),
538  storedHash.asBigEndianU32());
539  return ProcessFileStatus::FAILED;
540  }
541 
542  // reset header deserialization in the container
543  Fw::SerializeStatus desStat = container.deserializeHeader();
544  if (desStat != Fw::FW_SERIALIZE_OK) {
545  this->log_WARNING_HI_FileHdrDesError(fullFile, desStat);
546  return ProcessFileStatus::FAILED;
547  }
548 
549  const FwSizeType dataSize = container.getDataSize();
550  const FwSizeType expectedDataSize = fileSize - Fw::DpContainer::MIN_PACKET_SIZE;
551  if (dataSize != expectedDataSize) {
553  return ProcessFileStatus::FAILED;
554  }
555 
556  Fw::FileNameString canonicalFileName;
557  Fw::FormatStatus canonicalFormatStatus =
558  canonicalFileName.format(DP_FILENAME_FORMAT, this->m_directories[dir].toChar(), container.getId(),
559  container.getTimeTag().getSeconds(), container.getTimeTag().getUSeconds());
560  if (canonicalFormatStatus != Fw::FormatStatus::SUCCESS) {
561  this->log_WARNING_HI_FileNameFormatError(fullFile,
562  static_cast<Fw::StringFormatStatus::T>(canonicalFormatStatus));
563  return ProcessFileStatus::FAILED;
564  }
565  if (canonicalFileName != fullFile) {
566  this->log_WARNING_HI_InvalidFileName(fullFile, canonicalFileName);
567  return ProcessFileStatus::FAILED;
568  }
569 
570  // skip adding an already transmitted file
571  if (container.getState() == Fw::DpState::TRANSMITTED) {
572  this->log_ACTIVITY_HI_DpFileSkipped(fullFile);
573  return ProcessFileStatus::FAILED;
574  }
575 
576  // add entry to catalog.
577  DpStateEntry entry;
578  entry.dir = static_cast<FwIndexType>(dir);
579  entry.record.set_id(container.getId());
580  entry.record.set_priority(container.getPriority());
581  entry.record.set_state(container.getState());
582  entry.record.set_tSec(container.getTimeTag().getSeconds());
583  entry.record.set_tSub(container.getTimeTag().getUSeconds());
584  entry.record.set_size(static_cast<U64>(fileSize));
585 
586  // check the state file to see if there is transmit state
587  this->getFileState(entry);
588 
589  // a duplicate insert updates the tree in place; skip it so pending counters are not double-counted
590  if (this->m_dpCatalog.find(entry) == Fw::Success::SUCCESS) {
591  this->log_ACTIVITY_HI_DpFileSkipped(fullFile);
592  return ProcessFileStatus::FAILED;
593  }
594 
595  // insert entry into sorted catalog. if can't insert, quit
596  bool inserted = this->insertEntry(entry);
597  if (!inserted) {
598  this->log_WARNING_HI_DpInsertError(entry.record);
599  // return and hope new slots open up later
600  return ProcessFileStatus::QUIT;
601  }
602 
603  // increment our counters
604  this->m_pendingFiles++;
605  this->m_pendingDpBytes += entry.record.get_size();
606 
607  // make sure we haven't exceeded the limit
608  if (this->m_pendingFiles > this->m_numDpSlots) {
609  this->log_WARNING_HI_DpCatalogFull(entry.record);
610  return ProcessFileStatus::QUIT;
611  }
612 
613  this->log_ACTIVITY_HI_DpFileAdded(canonicalFileName);
614 
615  // No need to track iterator state - begin() always gives us the highest priority entry
616  // and we remove entries as we transmit them
617 
618  return ProcessFileStatus::SUCCESS;
619 }
620 
621 // ----------------------------------------------------------------------
622 // DpStateEntry Comparison Ops
623 // ----------------------------------------------------------------------
624 I8 DpCatalog::DpStateEntry::compareEntries(const DpStateEntry& left, const DpStateEntry& right) {
625  // check priority. Lower is higher priority
626  if (left.record.get_priority() < right.record.get_priority()) {
627  return -1;
628  } else if (left.record.get_priority() > right.record.get_priority()) {
629  return 1;
630  }
631 
632  // check time. Older is higher priority
633  else if (left.record.get_tSec() < right.record.get_tSec()) {
634  return -1;
635  } else if (left.record.get_tSec() > right.record.get_tSec()) {
636  return 1;
637  }
638 
639  // check subsecond time. Older is higher priority
640  else if (left.record.get_tSub() < right.record.get_tSub()) {
641  return -1;
642  } else if (left.record.get_tSub() > right.record.get_tSub()) {
643  return 1;
644  }
645 
646  // check ID. Lower is higher priority
647  else if (left.record.get_id() < right.record.get_id()) {
648  return -1;
649  } else if (left.record.get_id() > right.record.get_id()) {
650  return 1;
651  }
652 
653  // if ids are equal we have two nodes with the same value
654  else {
655  return 0;
656  }
657 }
658 
659 bool DpCatalog::DpStateEntry::operator==(const DpStateEntry& other) const {
660  return compareEntries(*this, other) == 0;
661 }
662 bool DpCatalog::DpStateEntry::operator!=(const DpStateEntry& other) const {
663  return compareEntries(*this, other) != 0;
664 }
665 
666 bool DpCatalog::DpStateEntry::operator>(const DpStateEntry& other) const {
667  return compareEntries(*this, other) > 0;
668 }
669 bool DpCatalog::DpStateEntry::operator<(const DpStateEntry& other) const {
670  return compareEntries(*this, other) < 0;
671 }
672 
673 bool DpCatalog::insertEntry(DpStateEntry& entry) {
674  // Insert into the RedBlackTreeSet
675  // The tree maintains sorting by priority, time, and ID via DpStateEntry comparison operators
676  Fw::Success status = this->m_dpCatalog.insert(entry);
677  return (status == Fw::Success::SUCCESS);
678 }
679 
680 void DpCatalog::sendNextEntry() {
681  // Use xmit flag to break upon STOP_XMIT_CATALOG
682  if (this->m_xmitInProgress != true) {
683  return;
684  }
685 
686  // Look for the next entry to send
687  DpStateEntry entry;
688  if (!this->findNextEntry(entry)) {
689  // if no entry found, we are done
690  this->m_xmitInProgress = false;
691  this->log_ACTIVITY_HI_CatalogXmitCompleted(this->m_xmitBytes);
692  this->dispatchWaitedResponse(Fw::CmdResponse::OK);
693  return;
694  }
695 
696  // Save current entry for fileDone_handler
697  this->m_currentXmitEntry = entry;
698  this->m_hasCurrentXmit = true;
699 
700  // Build file name based on the found entry
701  Fw::FormatStatus formatStatus =
702  this->m_currXmitFileName.format(DP_FILENAME_FORMAT, this->m_directories[entry.dir].toChar(),
703  entry.record.get_id(), entry.record.get_tSec(), entry.record.get_tSub());
704  if (formatStatus != Fw::FormatStatus::SUCCESS) {
705  this->log_WARNING_HI_FileNameFormatError(this->m_currXmitFileName,
706  static_cast<Fw::StringFormatStatus::T>(formatStatus));
707  // No send is in flight, so no fileDone will arrive: abort the transmit
708  // rather than leaving it wedged in progress
709  this->m_hasCurrentXmit = false;
710  this->m_xmitInProgress = false;
711  this->dispatchWaitedResponse(Fw::CmdResponse::EXECUTION_ERROR);
712  return;
713  }
714  this->log_ACTIVITY_LO_SendingProduct(this->m_currXmitFileName, static_cast<U32>(entry.record.get_size()),
715  entry.record.get_priority());
716  Svc::SendFileResponse resp = this->fileOut_out(0, this->m_currXmitFileName, this->m_currXmitFileName, 0, 0);
717  if (resp.get_status() != Svc::SendFileStatus::STATUS_OK) {
718  this->log_WARNING_HI_DpFileSendError(this->m_currXmitFileName, resp.get_status());
719  // A rejected send produces no fileDone callback: abort the transmit
720  // rather than leaving it wedged in progress
721  this->m_hasCurrentXmit = false;
722  this->m_xmitInProgress = false;
723  this->dispatchWaitedResponse(Fw::CmdResponse::EXECUTION_ERROR);
724  }
725 } // end sendNextEntry()
726 
727 bool DpCatalog::findNextEntry(DpStateEntry& entry) {
728  // If catalog is empty, return false
729  if (this->m_dpCatalog.getSize() == 0) {
730  return false;
731  }
732 
733  // Get the highest priority entry (begin() returns highest priority)
734  // Since we remove entries as we transmit them, begin() always gives us the next entry
735  typename Fw::RedBlackTreeSet<DpStateEntry, DP_MAX_FILES>::ConstIterator iter = this->m_dpCatalog.begin();
736 
737  // Verify iterator is valid
738  if (iter == this->m_dpCatalog.end()) {
739  return false;
740  }
741 
742  // Get the entry
743  entry = *iter;
744 
745  return true;
746 }
747 
748 bool DpCatalog::checkInit() {
749  if (not this->m_initialized) {
751  return false;
752  } else if (0 == this->m_numDpSlots) {
754  return false;
755  }
756 
757  return true;
758 }
759 
761  // only try to deallocate if both pointers are non-zero
762  // it's a way to more gracefully shut down if there are missing
763  // pointers
764  if ((this->m_allocator != nullptr) and (this->m_memPtr != nullptr)) {
765  this->m_allocator->deallocate(this->m_allocatorId, this->m_memPtr);
766  }
767 }
768 
769 // ----------------------------------------------------------------------
770 // Handler implementations for user-defined typed input ports
771 // ----------------------------------------------------------------------
772 
773 void DpCatalog ::fileDone_handler(FwIndexType portNum, const Svc::SendFileResponse& resp) {
774  // check file status
776  this->log_WARNING_HI_DpFileXmitError(this->m_currXmitFileName, resp.get_status());
777  this->m_xmitInProgress = false;
778  this->dispatchWaitedResponse(Fw::CmdResponse::EXECUTION_ERROR);
779  return;
780  }
781 
782  // Catalog cleared while this file was sent; clear xmit state and answer any waited command
783  if (!this->m_catalogBuilt) {
784  this->m_hasCurrentXmit = false;
785  this->m_xmitInProgress = false;
786  this->dispatchWaitedResponse(Fw::CmdResponse::EXECUTION_ERROR);
787  return;
788  }
789 
790  // Should have a valid current transmit entry
791  FW_ASSERT(this->m_hasCurrentXmit);
792 
793  // Reduce pending
794  this->m_pendingDpBytes -= this->m_currentXmitEntry.record.get_size();
795  this->m_pendingFiles--;
796  // Log File Complete & pending
797  this->log_ACTIVITY_LO_ProductComplete(this->m_currXmitFileName, this->m_pendingFiles, this->m_pendingDpBytes);
798 
799  // mark the entry as transmitted
800  this->m_currentXmitEntry.record.set_state(Fw::DpState::TRANSMITTED);
801  // update the transmitted state in the state file
802  this->appendFileState(this->m_currentXmitEntry);
803  // add the size
804  this->m_xmitBytes += this->m_currentXmitEntry.record.get_size();
805 
806  // Remove from catalog
807  Fw::Success status = this->m_dpCatalog.remove(this->m_currentXmitEntry);
808  FW_ASSERT(status == Fw::Success::SUCCESS);
809 
810  this->m_hasCurrentXmit = false;
811 
812  // send the next entry, if it exists
813  this->sendNextEntry();
814 }
815 
816 void DpCatalog ::pingIn_handler(FwIndexType portNum, U32 key) {
817  // return code for health ping
818  this->pingOut_out(0, key);
819 }
820 
821 void DpCatalog ::addToCat_handler(FwIndexType portNum,
822  const Fw::StringBase& fileName,
823  FwDpPriorityType priority,
824  FwSizeType size) {
825  // check initialization
826  if (not this->checkInit()) {
827  return;
828  }
829 
830  // check that initialization got memory
831  if (0 == this->m_numDpSlots) {
833  return;
834  }
835 
836  // Check the catalog has been built
837  if (not this->m_catalogBuilt) {
838  this->log_ACTIVITY_HI_NotLoaded(fileName);
839  return;
840  }
841 
842  // Both of these are grabbed from the header
843  (void)priority;
844  (void)size;
845 
846  // Since this is a runtime addition
847  // Check if file is in one of our directories
848  FwSizeType dir = this->determineDirectory(fileName);
849 
850  // Not in one of our directories; skip this file
851  if (dir == DP_MAX_DIRECTORIES) {
852  this->log_WARNING_HI_DirectoryNotManaged(fileName);
853  return;
854  }
855 
856  const ProcessFileStatus ret = processFile(fileName, dir);
857 
858  if (ret == ProcessFileStatus::SUCCESS) {
859  // If we already finished, sendNext only if remainingActive
860  if (!this->m_xmitInProgress && this->m_remainActive) {
861  this->m_xmitInProgress = true;
862  this->sendNextEntry();
863  }
864  // Otherwise, Current File finishing will invoke sendNextFile & find the right file
865  // Or will be manually tx-ed at next command
866 
867  // prune and rewrite the state file
868  this->pruneAndWriteStateFile();
869  }
870 }
871 
872 // ----------------------------------------------------------------------
873 // Handler implementations for commands
874 // ----------------------------------------------------------------------
875 
876 void DpCatalog ::BUILD_CATALOG_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) {
877  // invoke helper
878  this->cmdResponse_out(opCode, cmdSeq, this->doCatalogBuild());
879 }
880 
881 void DpCatalog ::START_XMIT_CATALOG_cmdHandler(FwOpcodeType opCode,
882  U32 cmdSeq,
883  const Fw::Wait& wait,
884  bool remainActive) {
885  this->m_remainActive = remainActive;
886 
887  // Arm the waited response before starting: an empty catalog completes the
888  // transmit inside doCatalogXmit and must still answer a waited command
889  if (Fw::Wait::WAIT == wait) {
890  this->m_xmitCmdWait = true;
891  this->m_xmitOpCode = opCode;
892  this->m_xmitCmdSeq = cmdSeq;
893  }
894 
895  Fw::CmdResponse resp = this->doCatalogXmit();
896  FW_ASSERT(resp.isValid(), static_cast<FwAssertArgType>(resp.e));
897 
898  if (resp != Fw::CmdResponse::OK) {
899  this->m_xmitCmdWait = false;
900  this->m_xmitOpCode = 0;
901  this->m_xmitCmdSeq = 0;
902  this->cmdResponse_out(opCode, cmdSeq, resp);
903  } else if (Fw::Wait::NO_WAIT == wait) {
904  this->cmdResponse_out(opCode, cmdSeq, resp);
905  }
906 }
907 
908 Fw::CmdResponse DpCatalog::doCatalogXmit() {
909  // check initialization
910  if (not this->checkInit()) {
912  }
913 
914  // check that initialization got memory
915  if (0 == this->m_numDpSlots) {
918  }
919 
920  // make sure a downlink is not in progress
921  if (this->m_xmitInProgress) {
924  }
925 
926  // Check the catalog has been built
927  if (not this->m_catalogBuilt) {
930  }
931 
932  // start transmission
933  this->m_xmitBytes = 0;
934 
935  this->m_xmitInProgress = true;
936  // Step 3b - search for and send first entry
937  this->sendNextEntry();
938  return Fw::CmdResponse::OK;
939 }
940 
941 void DpCatalog ::STOP_XMIT_CATALOG_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) {
942  if (not this->m_xmitInProgress) {
944  // benign error, so don't fail the command
945  this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
946  } else {
947  this->log_ACTIVITY_HI_CatalogXmitStopped(this->m_xmitBytes);
948  // Disarm the flag so next sendNextEntry stops transmission
949  this->m_xmitInProgress = false;
950  // Respond to original cmd to start xmit
951  // (if we haven't already)
952  this->dispatchWaitedResponse(Fw::CmdResponse::OK);
953 
954  // Respond to this command
955  this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
956  }
957 }
958 
959 void DpCatalog ::CLEAR_CATALOG_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) {
960  this->resetCatalog();
961  this->resetStateFileData();
962 
963  this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
964 }
965 
966 void DpCatalog ::dispatchWaitedResponse(Fw::CmdResponse response) {
967  if (this->m_xmitCmdWait) {
968  this->cmdResponse_out(this->m_xmitOpCode, this->m_xmitCmdSeq, response);
969 
970  // Prevent a Duplicate Cmd Response
971  this->m_xmitCmdWait = false;
972  this->m_xmitOpCode = 0;
973  this->m_xmitCmdSeq = 0;
974  }
975 }
976 
977 } // namespace Svc
Serialization/Deserialization operation was successful.
A data product Container.
Definition: DpContainer.hpp:26
void pingOut_out(FwIndexType portNum, U32 key) const
Invoke output port pingOut.
FwDpPriorityType getPriority() const
virtual void * allocate(const FwEnumStoreType identifier, FwSizeType &size, bool &recoverable, FwSizeType alignment=alignof(std::max_align_t))=0
void log_WARNING_HI_StateFileTruncated(const Fw::StringBase &file, I32 offset, I32 size) const
Log event StateFileTruncated.
FwIdType FwOpcodeType
The type of a command opcode.
void log_WARNING_HI_DirectoryOpenError(const Fw::StringBase &loc, I32 stat) const
Representing success.
PlatformSizeType FwSizeType
void log_WARNING_HI_DpFileXmitError(const Fw::StringBase &file, const Svc::SendFileStatus &stat)
Log event DpFileXmitError.
U32 FwDpPriorityType
The type of a data product priority.
void log_ACTIVITY_HI_NotLoaded(const Fw::StringBase &file) const
Log event NotLoaded.
I32 FwEnumStoreType
Auto-generated base for DpCatalog component.
FwSignedSizeType substring_find(const CHAR *source_string, FwSizeType source_size, const CHAR *sub_string, FwSizeType sub_size)
find the first occurrence of a substring
Definition: StringUtils.cpp:43
void log_WARNING_HI_FileNameFormatError(const Fw::StringBase &file, const Fw::StringFormatStatus &status)
Wait or don&#39;t wait for something.
Definition: WaitEnumAc.hpp:18
void log_WARNING_HI_DpInsertError(const Svc::DpRecord &dp)
static constexpr FwSizeType MIN_PACKET_SIZE
Definition: DpContainer.hpp:65
static const FwIndexType DP_MAX_DIRECTORIES
Svc::SendFileResponse fileOut_out(FwIndexType portNum, const Fw::StringBase &sourceFileName, const Fw::StringBase &destFileName, U32 offset, U32 length) const
Invoke output port fileOut.
int8_t I8
8-bit signed integer
Definition: BasicTypes.h:51
ConstIterator begin() const override
Svc::SendFileStatus::T get_status() const
Get member status.
void configure(const Fw::ExternalArray< Fw::FileNameString > &directories, Fw::FileNameString &stateFile, FwEnumStoreType memId, Fw::MemAllocator &allocator)
Configure the DpCatalog.
Definition: DpCatalog.cpp:29
enum T e
The raw enum value.
Overwrite file when it exists and creation was requested.
Definition: File.hpp:60
PlatformSignedSizeType FwSignedSizeType
void log_WARNING_HI_StateFileOpenError(const Fw::StringBase &file, I32 stat) const
Log event StateFileOpenError.
Enum representing a command response.
#define DIRECTORY_DELIMITER
Definition: DpCatalog.hpp:22
void log_ACTIVITY_HI_DpFileAdded(const Fw::StringBase &file) const
void setBuffer(const Buffer &buffer)
Set the packet buffer.
bool isValid() const
Check raw enum value for validity.
void cmdResponse_out(FwOpcodeType opCode, U32 cmdSeq, Fw::CmdResponse response)
Emit command response.
SerializeStatus
forward declaration for string
void log_WARNING_HI_FileCorruptedDataError(const Fw::StringBase &file, I32 stat) const
void log_WARNING_HI_FileOpenError(const Fw::StringBase &loc, I32 stat)
void log_ACTIVITY_LO_ProductComplete(const Fw::StringBase &file, U32 pending, U64 pending_bytes) const
Os::FileInterface::Status open(const char *path, Mode mode)
open file with supplied path and mode
Definition: File.cpp:50
void log_WARNING_HI_NoDpMemory() const
Log event NoDpMemory.
void log_WARNING_HI_FileSizeError(const Fw::StringBase &file, I32 stat)
The transmitted state.
void log_WARNING_HI_FileReadError(const Fw::StringBase &file, I32 stat)
void clear() override
Clear the set.
Open file for appending.
Definition: File.hpp:37
void log_ACTIVITY_HI_DpFileSkipped(const Fw::StringBase &file) const
void log_ACTIVITY_LO_ProcessingDirectory(const Fw::StringBase &directory) const
File doesn&#39;t exist (for read)
Definition: File.hpp:43
void log_ACTIVITY_HI_ProcessingDirectoryComplete(const Fw::StringBase &loc, U32 total, U32 pending, U64 pending_bytes) const
void log_ACTIVITY_LO_SendingProduct(const Fw::StringBase &file, U32 bytes, U32 prio) const
void log_WARNING_LO_NoStateFileSpecified() const
Log event NoStateFileSpecified.
External serialize buffer with no copy semantics.
void log_WARNING_LO_NoStateFile(const Fw::StringBase &file) const
Log event NoStateFile.
T
The raw enum type.
U32 getSeconds() const
Definition: Time.cpp:128
constexpr const char * DP_FILENAME_FORMAT
Definition: DpCfg.hpp:21
Fw::Time getTimeTag() const
FwSizeType getSize() const override
void close() override
close the file, if not opened then do nothing
Definition: File.cpp:97
Status write(const U8 *buffer, FwSizeType &size)
write data to this file from the supplied buffer bounded by size
Definition: File.cpp:213
void log_WARNING_LO_XmitNotActive() const
Log event XmitNotActive.
const char * toChar() const
Convert to a C-style char*.
void log_ACTIVITY_HI_CatalogXmitCompleted(U64 bytes) const
void log_WARNING_HI_DpFileSendError(const Fw::StringBase &file, const Svc::SendFileStatus &stat)
Log event DpFileSendError.
void log_ACTIVITY_LO_ProcessingFile(const Fw::StringBase &file) const
static const FwIndexType DP_MAX_FILES
FormatStatus format(const CHAR *formatString,...)
write formatted string to buffer
Definition: StringBase.cpp:58
FwSizeType getSize() const
Command successfully executed.
The size of the serial representation.
void log_WARNING_HI_FileHdrError(const Fw::StringBase &file, const Svc::DpHdrField &field, U32 exp, U32 act)
void log_ACTIVITY_HI_CatalogXmitStopped(U64 bytes) const
uint8_t U8
8-bit unsigned integer
Definition: BasicTypes.h:54
DpCatalog(const char *const compName)
DpCatalog constructor.
Definition: DpCatalog.cpp:25
Directory class.
Definition: Directory.hpp:116
static Status getFileSize(const char *path, FwSizeType &size)
Get the size of the file (in bytes) at the specified path.
Definition: FileSystem.cpp:227
FwSignedSizeType substring_find_last(const CHAR *source_string, FwSizeType source_size, const CHAR *sub_string, FwSizeType sub_size)
find the last occurrence of a substring
Definition: StringUtils.cpp:85
Status read(U8 *buffer, FwSizeType &size)
read data from this file into supplied buffer bounded by size
Definition: File.cpp:194
Success remove(const T &element) override
Command had execution error.
Success find(const T &element) const override
void log_WARNING_HI_StateFileReadError(const Fw::StringBase &file, I32 stat, I32 offset) const
Log event StateFileReadError.
Fw::DpState getState() const
Get the product state.
void log_WARNING_HI_DirectoryNotManaged(const Fw::StringBase &file) const
Memory Allocation base class.
void log_WARNING_HI_CatalogFull(const Fw::StringBase &dir)
Operation was successful.
Definition: File.hpp:42
U32 getUSeconds() const
Definition: Time.cpp:132
void log_WARNING_HI_XmitUnbuiltCatalog() const
Log event XmitUnbuiltCatalog.
Status readDirectory(Fw::ExternalArray< Fw::String > &filenameArray, FwSizeType &filenameCount)
Read the contents of the directory and store filenames in the supplied array.
Definition: Directory.cpp:114
PlatformIndexType FwIndexType
void log_WARNING_HI_FileHdrDesError(const Fw::StringBase &file, I32 stat)
Operation was successful.
Definition: Directory.hpp:22
Open file for reading.
Definition: File.hpp:33
Send file response struct.
U32 asBigEndianU32() const
Convert bytes 0 through 3 of the hash data to a big-Endian U32 value.
#define DP_EXT
Definition: DpCfg.hpp:20
A container class for holding a hash buffer.
Definition: HashBuffer.hpp:26
Success::T checkHeaderHash(Utils::HashBuffer &storedHash, Utils::HashBuffer &computedHash) const
Check the header hash.
Status open(const char *path, OpenMode mode) override
Open or create a directory.
Definition: Directory.cpp:31
Success insert(const T &element) override
RateGroupDivider component implementation.
ConstIterator end() const override
virtual SizeType length() const
Get the length of the string.
U8 BYTE
byte type
Definition: BasicTypes.h:57
Fw::SerializeStatus deserializeHeader()
Definition: DpContainer.cpp:38
Operation was successful.
Definition: FileSystem.hpp:24
virtual void deallocate(const FwEnumStoreType identifier, void *ptr)=0
Invalid size parameter.
Definition: File.hpp:46
Wait for something.
Definition: WaitEnumAc.hpp:34
FwDpIdType getId() const
FwSizeType getDataSize() const
Don&#39;t wait for something.
Definition: WaitEnumAc.hpp:36
void log_WARNING_HI_InvalidFileName(const Fw::StringBase &file, const Fw::StringBase &expected)
#define FW_ASSERT(...)
Definition: Assert.hpp:14
Success/Failure.
void log_WARNING_HI_DpCatalogFull(const Svc::DpRecord &dp)
void log_WARNING_HI_StateFileWriteError(const Fw::StringBase &file, I32 stat) const
Log event StateFileWriteError.
PlatformAssertArgType FwAssertArgType
The type of arguments to assert functions.
FwSizeType string_length(const CHAR *source, FwSizeType buffer_size)
get the length of the source string
Definition: StringUtils.cpp:32
FormatStatus
status of string format calls
Definition: format.hpp:18
Open file for writing and truncates file if it exists, ie same flags as creat()
Definition: File.hpp:34