GCC Code Coverage Report


Directory: ./
File: DpCatalog.cpp
Date: 2026-09-23 22:12:54
Exec Total Coverage
Lines: 45 486 9.3%
Functions: 7 32 21.9%
Branches: 23 424 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 // add entry to catalog.
581 ✗ DpStateEntry entry;
582 ✗ entry.dir = static_cast<FwIndexType>(dir);
583 ✗ entry.record.set_id(container.getId());
584 ✗ entry.record.set_priority(container.getPriority());
585 ✗ entry.record.set_state(container.getState());
586 ✗ entry.record.set_tSec(container.getTimeTag().getSeconds());
587 ✗ entry.record.set_tSub(container.getTimeTag().getUSeconds());
588 ✗ entry.record.set_size(static_cast<U64>(fileSize));
589
590 // check the state file to see if there is transmit state
591 ✗ this->getFileState(entry);
592
593 // skip a file the state file records as already transmitted
594 ✗ if (entry.record.get_state() == Fw::DpState::TRANSMITTED) {
595 ✗ this->log_ACTIVITY_HI_DpFileSkipped(fullFile);
596 ✗ return ProcessFileStatus::FAILED;
597 }
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 61 void DpCatalog ::pingIn_handler(FwIndexType portNum, U32 key) {
827 // return code for health ping
828 61 this->pingOut_out(0, key);
829 61 }
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