GCC Code Coverage Report


Directory: ./
File: Svc/DpCatalog/DpCatalog.cpp
Date: 2026-09-03 22:12:29
Exec Total Coverage
Lines: 45 486 9.3%
Functions: 7 32 21.9%
Branches: 23 425 5.4%

Line Branch Exec Source
1 // ======================================================================
2
3 // \title DpCatalog.cpp
4 // \author tcanham
5 // \brief cpp file for DpCatalog component implementation class
6 // ======================================================================
7
8 #include "Svc/DpCatalog/DpCatalog.hpp"
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
7/13
✓ Branch 2 taken 1 times.
✓ Branch 5 taken 1 times.
✓ Branch 8 taken 2 times.
✓ Branch 10 taken 2 times.
✓ Branch 11 taken 1 times.
✓ Branch 13 taken 1 times.
✓ Branch 16 taken 1 times.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
3 DpCatalog ::DpCatalog(const char* const compName) : DpCatalogComponentBase(compName) {
26 // Members are default-initialized via in-class initializers in the header.
27 1 }
28
29 1 void DpCatalog::configure(const Fw::ExternalArray<Fw::FileNameString>& directories,
30 Fw::FileNameString& stateFile,
31 FwEnumStoreType memId,
32 Fw::MemAllocator& allocator) {
33 1 const FwSizeType numDirs = directories.getSize();
34 // Do some assertion checks
35 1 FW_ASSERT(numDirs <= DP_MAX_DIRECTORIES, static_cast<FwAssertArgType>(numDirs));
36
37
1/1
✓ Branch 1 taken 1 times.
1 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 1 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
1/1
✓ Branch 1 taken 1 times.
1 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
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 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 1 const FwSizeType allocatedSlots = this->m_memSize / slotSize;
54 1 this->m_numDpSlots = (allocatedSlots < DP_MAX_FILES) ? allocatedSlots : DP_MAX_FILES;
55 // Initialize the catalog
56
1/1
✓ Branch 1 taken 1 times.
1 this->resetCatalog();
57 // assign pointer for the state file storage
58 1 this->m_stateFileData = static_cast<DpDstateFileEntry*>(this->m_memPtr);
59 1 } 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
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (FwSizeType dir = 0; dir < numDirs; dir++) {
67
2/2
✓ Branch 1 taken 1 times.
✓ Branch 4 taken 1 times.
1 this->m_directories[dir] = directories[dir];
68 }
69 1 this->m_numDirectories = numDirs;
70
71 // store allocator
72 1 this->m_allocator = &allocator;
73 1 this->m_allocatorId = memId;
74 1 this->m_initialized = true;
75 1 }
76
77 void DpCatalog::configure(Fw::FileNameString directories[DP_MAX_DIRECTORIES],
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 1 void DpCatalog::resetCatalog() {
88 // Clear the catalog
89 1 this->m_dpCatalog.clear();
90 // Clear transmission state
91 1 this->m_hasCurrentXmit = false;
92 // Reset counters
93 1 this->m_pendingFiles = 0;
94 1 this->m_pendingDpBytes = 0;
95 // Mark the catalog as un-built
96 1 this->m_catalogBuilt = false;
97 1 }
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) {
114 this->log_WARNING_LO_NoStateFileSpecified();
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);
133 return Fw::CmdResponse::EXECUTION_ERROR;
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();
147 return Fw::CmdResponse::EXECUTION_ERROR;
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();
180 return Fw::CmdResponse::EXECUTION_ERROR;
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();
186 return Fw::CmdResponse::EXECUTION_ERROR;
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()) {
314 return Fw::CmdResponse::EXECUTION_ERROR;
315 }
316
317 // check that initialization got memory
318 if (0 == this->m_numDpSlots) {
319 this->log_WARNING_HI_NoDpMemory();
320 return Fw::CmdResponse::EXECUTION_ERROR;
321 }
322
323 // make sure a downlink is not in progress
324 if (this->m_xmitInProgress) {
325 this->log_WARNING_LO_DpXmitInProgress();
326 return Fw::CmdResponse::EXECUTION_ERROR;
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
355 this->log_ACTIVITY_HI_CatalogBuildComplete();
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 filesProcessed = 0;
373
374 Os::Directory dpDir;
375 Os::Directory::Status status = dpDir.open(this->m_directories[dir].toChar(), Os::Directory::OpenMode::READ);
376 if (status != Os::Directory::OP_OK) {
377 this->log_WARNING_HI_DirectoryOpenError(this->m_directories[dir], status);
378 return Fw::CmdResponse::EXECUTION_ERROR;
379 }
380
381 // bound the read loop by the number of entries in the directory
382 FwSizeType fileCount = 0;
383 status = dpDir.getFileCount(fileCount);
384 if (status != Os::Directory::OP_OK) {
385 this->log_WARNING_HI_DirectoryOpenError(this->m_directories[dir], status);
386 return Fw::CmdResponse::EXECUTION_ERROR;
387 }
388
389 // read entries one at a time so non-DP files do not consume catalog slots
390 Fw::String fileName;
391 for (FwSizeType entry = 0; entry < fileCount; entry++) {
392 status = dpDir.read(fileName);
393 if (status == Os::Directory::NO_MORE_FILES) {
394 break;
395 }
396 if (status != Os::Directory::OP_OK) {
397 this->log_WARNING_HI_DirectoryOpenError(this->m_directories[dir], status);
398 return Fw::CmdResponse::EXECUTION_ERROR;
399 }
400
401 // only consider files with the DP extension
402 const FwSizeType fileNameLength = fileName.length();
403 const FwSizeType dpExtLength = Fw::StringUtils::string_length(DP_EXT, sizeof(DP_EXT));
404 const FwSignedSizeType loc =
405 Fw::StringUtils::substring_find_last(fileName.toChar(), fileNameLength, DP_EXT, dpExtLength);
406
407 // Only accept files whose final suffix is the data product extension
408 if ((-1 == loc) || (static_cast<FwSizeType>(loc) + dpExtLength != fileNameLength)) {
409 continue;
410 }
411
412 // stop if there is no free catalog slot for this DP file
413 if ((totalFiles + filesProcessed) == this->m_numDpSlots) {
414 break;
415 }
416
417 Fw::String fullFile;
418 Fw::FormatStatus formatStatus =
419 fullFile.format("%s/%s", this->m_directories[dir].toChar(), fileName.toChar());
420 if (formatStatus != Fw::FormatStatus::SUCCESS) {
421 this->log_WARNING_HI_FileNameFormatError(fileName,
422 static_cast<Fw::StringFormatStatus::T>(formatStatus));
423 continue;
424 }
425
426 const ProcessFileStatus ret = processFile(fullFile, dir);
427 if (ret == ProcessFileStatus::QUIT) {
428 break;
429 }
430
431 if (ret == ProcessFileStatus::SUCCESS) {
432 filesProcessed++;
433 }
434
435 } // end for each file in a directory
436
437 totalFiles += filesProcessed;
438
439 this->log_ACTIVITY_HI_ProcessingDirectoryComplete(this->m_directories[dir], static_cast<U32>(totalFiles),
440 this->m_pendingFiles, this->m_pendingDpBytes);
441
442 // check to see if catalog is full
443 // that means generated products exceed the catalog size
444 if (totalFiles == this->m_numDpSlots) {
445 this->log_WARNING_HI_CatalogFull(this->m_directories[dir]);
446 break;
447 }
448 } // end for each directory
449
450 return Fw::CmdResponse::OK;
451
452 } // end fillBinaryTree()
453
454 FwSizeType DpCatalog::determineDirectory(const Fw::String& fullFile) {
455 FW_ASSERT(this->m_numDirectories <= DP_MAX_DIRECTORIES, static_cast<FwAssertArgType>(this->m_numDirectories));
456 // Grab the directory string (up until the final slash)
457 // Could be found directly w/ a dirname func or regex
458 FwSignedSizeType loc = Fw::StringUtils::substring_find_last(
459 fullFile.toChar(), fullFile.length(), DIRECTORY_DELIMITER,
460 Fw::StringUtils::string_length(DIRECTORY_DELIMITER, sizeof(DIRECTORY_DELIMITER)));
461
462 // Seems like the logic works so long as the path styles match (i.e. relative vs absolute)
463 // Full path resolution might be a worthwhile add
464
465 // No directory delimiter found; return DP_MAX_DIRECTORIES to signal failure
466 if (-1 == loc) {
467 return DP_MAX_DIRECTORIES;
468 }
469
470 for (FwSizeType dir = 0; dir < this->m_numDirectories; dir++) {
471 const Fw::FileNameString& dir_string = this->m_directories[dir];
472
473 // Compare both strings up to location of final slash
474 // StringUtils::substring_find will return zero if both paths agree
475 // memory safe since both are fixed width strings
476 // and loc is before the fixed width
477 if ((dir_string.length() == static_cast<FwSizeType>(loc)) &&
478 (Fw::StringUtils::substring_find(dir_string.toChar(), dir_string.length(), fullFile.toChar(),
479 static_cast<FwSizeType>(loc)) == 0)) {
480 return dir;
481 }
482 }
483
484 // No directory matched
485 return DP_MAX_DIRECTORIES;
486 }
487
488 DpCatalog::ProcessFileStatus DpCatalog::processFile(const Fw::String& fullFile, FwSizeType dir) {
489 FW_ASSERT(dir < static_cast<FwSizeType>(DP_MAX_DIRECTORIES), static_cast<FwAssertArgType>(dir));
490 // file class instance for processing files
491 Os::File dpFile;
492
493 // Working buffer for DP headers
494 U8 dpBuff[Fw::DpContainer::MIN_PACKET_SIZE]; // Header buffer
495 Fw::Buffer hdrBuff(dpBuff, sizeof(dpBuff)); // buffer for container header decoding
496 Fw::DpContainer container; // container object for extracting header fields
497
498 this->log_ACTIVITY_LO_ProcessingFile(fullFile);
499
500 // get file size
501 FwSizeType fileSize = 0;
502 Os::FileSystem::Status sizeStat = Os::FileSystem::getFileSize(fullFile.toChar(), fileSize);
503 if (sizeStat != Os::FileSystem::OP_OK) {
504 this->log_WARNING_HI_FileSizeError(fullFile, sizeStat);
505 return ProcessFileStatus::FAILED;
506 }
507
508 if (fileSize < Fw::DpContainer::MIN_PACKET_SIZE) {
509 this->log_WARNING_HI_FileReadError(fullFile, Os::File::BAD_SIZE);
510 return ProcessFileStatus::FAILED;
511 }
512
513 Os::File::Status stat = dpFile.open(fullFile.toChar(), Os::File::OPEN_READ);
514 if (stat != Os::File::OP_OK) {
515 this->log_WARNING_HI_FileOpenError(fullFile, stat);
516 return ProcessFileStatus::FAILED;
517 }
518
519 // Read DP header and header hash
520 FwSizeType size = Fw::DpContainer::MIN_PACKET_SIZE;
521
522 stat = dpFile.read(dpBuff, size);
523 if (stat != Os::File::OP_OK) {
524 this->log_WARNING_HI_FileReadError(fullFile, stat);
525 dpFile.close();
526 return ProcessFileStatus::FAILED;
527 }
528
529 // if full header and hashes aren't read, something's wrong with the file, so skip
530 if (size != Fw::DpContainer::MIN_PACKET_SIZE) {
531 this->log_WARNING_HI_FileReadError(fullFile, Os::File::BAD_SIZE);
532 dpFile.close();
533 return ProcessFileStatus::FAILED;
534 }
535
536 // if all is well, don't need the file any more
537 dpFile.close();
538
539 // give buffer to container instance
540 container.setBuffer(hdrBuff);
541
542 // make sure the header metadata matches its stored hash before trusting it
543 Utils::HashBuffer storedHash;
544 Utils::HashBuffer computedHash;
545 Fw::Success::T hashStatus = container.checkHeaderHash(storedHash, computedHash);
546 if (hashStatus != Fw::Success::SUCCESS) {
547 this->log_WARNING_HI_FileHdrError(fullFile, DpHdrField::CRC, computedHash.asBigEndianU32(),
548 storedHash.asBigEndianU32());
549 return ProcessFileStatus::FAILED;
550 }
551
552 // reset header deserialization in the container
553 Fw::SerializeStatus desStat = container.deserializeHeader();
554 if (desStat != Fw::FW_SERIALIZE_OK) {
555 this->log_WARNING_HI_FileHdrDesError(fullFile, desStat);
556 return ProcessFileStatus::FAILED;
557 }
558
559 const FwSizeType dataSize = container.getDataSize();
560 const FwSizeType expectedDataSize = fileSize - Fw::DpContainer::MIN_PACKET_SIZE;
561 if (dataSize != expectedDataSize) {
562 this->log_WARNING_HI_FileReadError(fullFile, Os::File::BAD_SIZE);
563 return ProcessFileStatus::FAILED;
564 }
565
566 Fw::FileNameString canonicalFileName;
567 Fw::FormatStatus canonicalFormatStatus =
568 canonicalFileName.format(DP_FILENAME_FORMAT, this->m_directories[dir].toChar(), container.getId(),
569 container.getTimeTag().getSeconds(), container.getTimeTag().getUSeconds());
570 if (canonicalFormatStatus != Fw::FormatStatus::SUCCESS) {
571 this->log_WARNING_HI_FileNameFormatError(fullFile,
572 static_cast<Fw::StringFormatStatus::T>(canonicalFormatStatus));
573 return ProcessFileStatus::FAILED;
574 }
575 if (canonicalFileName != fullFile) {
576 this->log_WARNING_HI_InvalidFileName(fullFile, canonicalFileName);
577 return ProcessFileStatus::FAILED;
578 }
579
580 // skip adding an already transmitted file
581 if (container.getState() == Fw::DpState::TRANSMITTED) {
582 this->log_ACTIVITY_HI_DpFileSkipped(fullFile);
583 return ProcessFileStatus::FAILED;
584 }
585
586 // add entry to catalog.
587 DpStateEntry entry;
588 entry.dir = static_cast<FwIndexType>(dir);
589 entry.record.set_id(container.getId());
590 entry.record.set_priority(container.getPriority());
591 entry.record.set_state(container.getState());
592 entry.record.set_tSec(container.getTimeTag().getSeconds());
593 entry.record.set_tSub(container.getTimeTag().getUSeconds());
594 entry.record.set_size(static_cast<U64>(fileSize));
595
596 // check the state file to see if there is transmit state
597 this->getFileState(entry);
598
599 // a duplicate insert updates the tree in place; skip it so pending counters are not double-counted
600 if (this->m_dpCatalog.find(entry) == Fw::Success::SUCCESS) {
601 this->log_ACTIVITY_HI_DpFileSkipped(fullFile);
602 return ProcessFileStatus::FAILED;
603 }
604
605 // insert entry into sorted catalog. if can't insert, quit
606 bool inserted = this->insertEntry(entry);
607 if (!inserted) {
608 this->log_WARNING_HI_DpInsertError(entry.record);
609 // return and hope new slots open up later
610 return ProcessFileStatus::QUIT;
611 }
612
613 // increment our counters
614 this->m_pendingFiles++;
615 this->m_pendingDpBytes += entry.record.get_size();
616
617 // make sure we haven't exceeded the limit
618 if (this->m_pendingFiles > this->m_numDpSlots) {
619 this->log_WARNING_HI_DpCatalogFull(entry.record);
620 return ProcessFileStatus::QUIT;
621 }
622
623 this->log_ACTIVITY_HI_DpFileAdded(canonicalFileName);
624
625 // No need to track iterator state - begin() always gives us the highest priority entry
626 // and we remove entries as we transmit them
627
628 return ProcessFileStatus::SUCCESS;
629 }
630
631 // ----------------------------------------------------------------------
632 // DpStateEntry Comparison Ops
633 // ----------------------------------------------------------------------
634 I8 DpCatalog::DpStateEntry::compareEntries(const DpStateEntry& left, const DpStateEntry& right) {
635 // check priority. Lower is higher priority
636 if (left.record.get_priority() < right.record.get_priority()) {
637 return -1;
638 } else if (left.record.get_priority() > right.record.get_priority()) {
639 return 1;
640 }
641
642 // check time. Older is higher priority
643 else if (left.record.get_tSec() < right.record.get_tSec()) {
644 return -1;
645 } else if (left.record.get_tSec() > right.record.get_tSec()) {
646 return 1;
647 }
648
649 // check subsecond time. Older is higher priority
650 else if (left.record.get_tSub() < right.record.get_tSub()) {
651 return -1;
652 } else if (left.record.get_tSub() > right.record.get_tSub()) {
653 return 1;
654 }
655
656 // check ID. Lower is higher priority
657 else if (left.record.get_id() < right.record.get_id()) {
658 return -1;
659 } else if (left.record.get_id() > right.record.get_id()) {
660 return 1;
661 }
662
663 // if ids are equal we have two nodes with the same value
664 else {
665 return 0;
666 }
667 }
668
669 bool DpCatalog::DpStateEntry::operator==(const DpStateEntry& other) const {
670 return compareEntries(*this, other) == 0;
671 }
672 bool DpCatalog::DpStateEntry::operator!=(const DpStateEntry& other) const {
673 return compareEntries(*this, other) != 0;
674 }
675
676 bool DpCatalog::DpStateEntry::operator>(const DpStateEntry& other) const {
677 return compareEntries(*this, other) > 0;
678 }
679 bool DpCatalog::DpStateEntry::operator<(const DpStateEntry& other) const {
680 return compareEntries(*this, other) < 0;
681 }
682
683 bool DpCatalog::insertEntry(DpStateEntry& entry) {
684 // Insert into the RedBlackTreeSet
685 // The tree maintains sorting by priority, time, and ID via DpStateEntry comparison operators
686 Fw::Success status = this->m_dpCatalog.insert(entry);
687 return (status == Fw::Success::SUCCESS);
688 }
689
690 void DpCatalog::sendNextEntry() {
691 // Use xmit flag to break upon STOP_XMIT_CATALOG
692 if (this->m_xmitInProgress != true) {
693 return;
694 }
695
696 // Look for the next entry to send
697 DpStateEntry entry;
698 if (!this->findNextEntry(entry)) {
699 // if no entry found, we are done
700 this->m_xmitInProgress = false;
701 this->log_ACTIVITY_HI_CatalogXmitCompleted(this->m_xmitBytes);
702 this->dispatchWaitedResponse(Fw::CmdResponse::OK);
703 return;
704 }
705
706 // Save current entry for fileDone_handler
707 this->m_currentXmitEntry = entry;
708 this->m_hasCurrentXmit = true;
709
710 // Build file name based on the found entry
711 Fw::FormatStatus formatStatus =
712 this->m_currXmitFileName.format(DP_FILENAME_FORMAT, this->m_directories[entry.dir].toChar(),
713 entry.record.get_id(), entry.record.get_tSec(), entry.record.get_tSub());
714 if (formatStatus != Fw::FormatStatus::SUCCESS) {
715 this->log_WARNING_HI_FileNameFormatError(this->m_currXmitFileName,
716 static_cast<Fw::StringFormatStatus::T>(formatStatus));
717 // No send is in flight, so no fileDone will arrive: abort the transmit
718 // rather than leaving it wedged in progress
719 this->m_hasCurrentXmit = false;
720 this->m_xmitInProgress = false;
721 this->dispatchWaitedResponse(Fw::CmdResponse::EXECUTION_ERROR);
722 return;
723 }
724 this->log_ACTIVITY_LO_SendingProduct(this->m_currXmitFileName, static_cast<U32>(entry.record.get_size()),
725 entry.record.get_priority());
726 Svc::SendFileResponse resp = this->fileOut_out(0, this->m_currXmitFileName, this->m_currXmitFileName, 0, 0);
727 if (resp.get_status() != Svc::SendFileStatus::STATUS_OK) {
728 this->log_WARNING_HI_DpFileSendError(this->m_currXmitFileName, resp.get_status());
729 // A rejected send produces no fileDone callback: abort the transmit
730 // rather than leaving it wedged in progress
731 this->m_hasCurrentXmit = false;
732 this->m_xmitInProgress = false;
733 this->dispatchWaitedResponse(Fw::CmdResponse::EXECUTION_ERROR);
734 }
735 } // end sendNextEntry()
736
737 bool DpCatalog::findNextEntry(DpStateEntry& entry) {
738 // If catalog is empty, return false
739 if (this->m_dpCatalog.getSize() == 0) {
740 return false;
741 }
742
743 // Get the highest priority entry (begin() returns highest priority)
744 // Since we remove entries as we transmit them, begin() always gives us the next entry
745 typename Fw::RedBlackTreeSet<DpStateEntry, DP_MAX_FILES>::ConstIterator iter = this->m_dpCatalog.begin();
746
747 // Verify iterator is valid
748 if (iter == this->m_dpCatalog.end()) {
749 return false;
750 }
751
752 // Get the entry
753 entry = *iter;
754
755 return true;
756 }
757
758 2 bool DpCatalog::checkInit() {
759
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (not this->m_initialized) {
760 this->log_WARNING_HI_ComponentNotInitialized();
761 return false;
762
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 } else if (0 == this->m_numDpSlots) {
763 this->log_WARNING_HI_ComponentNoMemory();
764 return false;
765 }
766
767 2 return true;
768 }
769
770 1 void DpCatalog::shutdown() {
771 // only try to deallocate if both pointers are non-zero
772 // it's a way to more gracefully shut down if there are missing
773 // pointers
774
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 if ((this->m_allocator != nullptr) and (this->m_memPtr != nullptr)) {
775 1 this->m_allocator->deallocate(this->m_allocatorId, this->m_memPtr);
776 }
777 1 }
778
779 // ----------------------------------------------------------------------
780 // Handler implementations for user-defined typed input ports
781 // ----------------------------------------------------------------------
782
783 void DpCatalog ::fileDone_handler(FwIndexType portNum, const Svc::SendFileResponse& resp) {
784 // check file status
785 if (resp.get_status() != Svc::SendFileStatus::STATUS_OK) {
786 this->log_WARNING_HI_DpFileXmitError(this->m_currXmitFileName, resp.get_status());
787 this->m_xmitInProgress = false;
788 this->dispatchWaitedResponse(Fw::CmdResponse::EXECUTION_ERROR);
789 return;
790 }
791
792 // Catalog cleared while this file was sent; clear xmit state and answer any waited command
793 if (!this->m_catalogBuilt) {
794 this->m_hasCurrentXmit = false;
795 this->m_xmitInProgress = false;
796 this->dispatchWaitedResponse(Fw::CmdResponse::EXECUTION_ERROR);
797 return;
798 }
799
800 // Should have a valid current transmit entry
801 FW_ASSERT(this->m_hasCurrentXmit);
802
803 // Reduce pending
804 this->m_pendingDpBytes -= this->m_currentXmitEntry.record.get_size();
805 this->m_pendingFiles--;
806 // Log File Complete & pending
807 this->log_ACTIVITY_LO_ProductComplete(this->m_currXmitFileName, this->m_pendingFiles, this->m_pendingDpBytes);
808
809 // mark the entry as transmitted
810 this->m_currentXmitEntry.record.set_state(Fw::DpState::TRANSMITTED);
811 // update the transmitted state in the state file
812 this->appendFileState(this->m_currentXmitEntry);
813 // add the size
814 this->m_xmitBytes += this->m_currentXmitEntry.record.get_size();
815
816 // Remove from catalog
817 Fw::Success status = this->m_dpCatalog.remove(this->m_currentXmitEntry);
818 FW_ASSERT(status == Fw::Success::SUCCESS);
819
820 this->m_hasCurrentXmit = false;
821
822 // send the next entry, if it exists
823 this->sendNextEntry();
824 }
825
826 59 void DpCatalog ::pingIn_handler(FwIndexType portNum, U32 key) {
827 // return code for health ping
828 59 this->pingOut_out(0, key);
829 59 }
830
831 2 void DpCatalog ::addToCat_handler(FwIndexType portNum,
832 const Fw::StringBase& fileName,
833 FwDpPriorityType priority,
834 FwSizeType size) {
835 // check initialization
836
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if (not this->checkInit()) {
837 return;
838 }
839
840 // check that initialization got memory
841
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (0 == this->m_numDpSlots) {
842 this->log_WARNING_HI_NoDpMemory();
843 return;
844 }
845
846 // Check the catalog has been built
847
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (not this->m_catalogBuilt) {
848 2 this->log_ACTIVITY_HI_NotLoaded(fileName);
849 2 return;
850 }
851
852 // Both of these are grabbed from the header
853 (void)priority;
854 (void)size;
855
856 // Since this is a runtime addition
857 // Check if file is in one of our directories
858 FwSizeType dir = this->determineDirectory(fileName);
859
860 // Not in one of our directories; skip this file
861 if (dir == DP_MAX_DIRECTORIES) {
862 this->log_WARNING_HI_DirectoryNotManaged(fileName);
863 return;
864 }
865
866 const ProcessFileStatus ret = processFile(fileName, dir);
867
868 if (ret == ProcessFileStatus::SUCCESS) {
869 // If we already finished, sendNext only if remainingActive
870 if (!this->m_xmitInProgress && this->m_remainActive) {
871 this->m_xmitInProgress = true;
872 this->sendNextEntry();
873 }
874 // Otherwise, Current File finishing will invoke sendNextFile & find the right file
875 // Or will be manually tx-ed at next command
876
877 // prune and rewrite the state file
878 this->pruneAndWriteStateFile();
879 }
880 }
881
882 // ----------------------------------------------------------------------
883 // Handler implementations for commands
884 // ----------------------------------------------------------------------
885
886 void DpCatalog ::BUILD_CATALOG_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) {
887 // invoke helper
888 this->cmdResponse_out(opCode, cmdSeq, this->doCatalogBuild());
889 }
890
891 void DpCatalog ::START_XMIT_CATALOG_cmdHandler(FwOpcodeType opCode,
892 U32 cmdSeq,
893 const Fw::Wait& wait,
894 bool remainActive) {
895 this->m_remainActive = remainActive;
896
897 // Arm the waited response before starting: an empty catalog completes the
898 // transmit inside doCatalogXmit and must still answer a waited command
899 if (Fw::Wait::WAIT == wait) {
900 this->m_xmitCmdWait = true;
901 this->m_xmitOpCode = opCode;
902 this->m_xmitCmdSeq = cmdSeq;
903 }
904
905 Fw::CmdResponse resp = this->doCatalogXmit();
906 FW_ASSERT(resp.isValid(), static_cast<FwAssertArgType>(resp.e));
907
908 if (resp != Fw::CmdResponse::OK) {
909 this->m_xmitCmdWait = false;
910 this->m_xmitOpCode = 0;
911 this->m_xmitCmdSeq = 0;
912 this->cmdResponse_out(opCode, cmdSeq, resp);
913 } else if (Fw::Wait::NO_WAIT == wait) {
914 this->cmdResponse_out(opCode, cmdSeq, resp);
915 }
916 }
917
918 Fw::CmdResponse DpCatalog::doCatalogXmit() {
919 // check initialization
920 if (not this->checkInit()) {
921 return Fw::CmdResponse::EXECUTION_ERROR;
922 }
923
924 // check that initialization got memory
925 if (0 == this->m_numDpSlots) {
926 this->log_WARNING_HI_NoDpMemory();
927 return Fw::CmdResponse::EXECUTION_ERROR;
928 }
929
930 // make sure a downlink is not in progress
931 if (this->m_xmitInProgress) {
932 this->log_WARNING_LO_DpXmitInProgress();
933 return Fw::CmdResponse::EXECUTION_ERROR;
934 }
935
936 // Check the catalog has been built
937 if (not this->m_catalogBuilt) {
938 this->log_WARNING_HI_XmitUnbuiltCatalog();
939 return Fw::CmdResponse::EXECUTION_ERROR;
940 }
941
942 // start transmission
943 this->m_xmitBytes = 0;
944
945 this->m_xmitInProgress = true;
946 // Step 3b - search for and send first entry
947 this->sendNextEntry();
948 return Fw::CmdResponse::OK;
949 }
950
951 void DpCatalog ::STOP_XMIT_CATALOG_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) {
952 if (not this->m_xmitInProgress) {
953 this->log_WARNING_LO_XmitNotActive();
954 // benign error, so don't fail the command
955 this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
956 } else {
957 this->log_ACTIVITY_HI_CatalogXmitStopped(this->m_xmitBytes);
958 // Disarm the flag so next sendNextEntry stops transmission
959 this->m_xmitInProgress = false;
960 // Respond to original cmd to start xmit
961 // (if we haven't already)
962 this->dispatchWaitedResponse(Fw::CmdResponse::OK);
963
964 // Respond to this command
965 this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
966 }
967 }
968
969 void DpCatalog ::CLEAR_CATALOG_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) {
970 this->resetCatalog();
971 this->resetStateFileData();
972
973 this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
974 }
975
976 void DpCatalog ::dispatchWaitedResponse(Fw::CmdResponse response) {
977 if (this->m_xmitCmdWait) {
978 this->cmdResponse_out(this->m_xmitOpCode, this->m_xmitCmdSeq, response);
979
980 // Prevent a Duplicate Cmd Response
981 this->m_xmitCmdWait = false;
982 this->m_xmitOpCode = 0;
983 this->m_xmitCmdSeq = 0;
984 }
985 }
986
987 } // namespace Svc
988