GCC Code Coverage Report


Directory: Svc/Ccsds/CfdpManager/
File: Engine.cpp
Date: 2026-11-03 21:17:54
Exec Total Coverage
Lines: 547 577 94.8%
Functions: 43 44 97.7%
Branches: 328 390 84.1%

Line Branch Exec Source
1 // ======================================================================
2 // \title Engine.cpp
3 // \brief CFDP Engine implementation
4 //
5 // This file is a port of CFDP engine operations from the following files
6 // from the NASA Core Flight System (cFS) CFDP (CF) Application, version 3.0.0,
7 // adapted for use within the F-Prime (F') framework:
8 // - cf_cfdp.c (CFDP PDU validation, processing, and engine operations)
9 //
10 // This file contains two sets of functions. The first is what is needed
11 // to deal with CFDP PDUs. Specifically validating them for correctness
12 // and ensuring the byte-order is correct for the target. The second
13 // is incoming and outgoing CFDP PDUs pass through here. All receive
14 // CFDP PDU logic is performed here and the data is passed to the
15 // R (rx) and S (tx) logic.
16 //
17 // ======================================================================
18 //
19 // NASA Docket No. GSC-18,447-1
20 //
21 // Copyright (c) 2019 United States Government as represented by the
22 // Administrator of the National Aeronautics and Space Administration.
23 // All Rights Reserved.
24 //
25 // Licensed under the Apache License, Version 2.0 (the "License"); you may
26 // not use this file except in compliance with the License. You may obtain
27 // a copy of the License at
28 //
29 // http://www.apache.org/licenses/LICENSE-2.0
30 //
31 // Unless required by applicable law or agreed to in writing, software
32 // distributed under the License is distributed on an "AS IS" BASIS,
33 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
34 // See the License for the specific language governing permissions and
35 // limitations under the License.
36 //
37 // ======================================================================
38
39 #include <string.h>
40 #include <new>
41
42 #include <Fw/Types/StringUtils.hpp>
43 #include <Os/FilePathUtils.hpp>
44 #include <Os/FileSystem.hpp>
45
46 #include <Svc/Ccsds/CfdpManager/CfdpManager.hpp>
47 #include <Svc/Ccsds/CfdpManager/Channel.hpp>
48 #include <Svc/Ccsds/CfdpManager/Engine.hpp>
49 #include <Svc/Ccsds/CfdpManager/Transaction.hpp>
50 #include <Svc/Ccsds/CfdpManager/Types/PduBase.hpp>
51 #include <Svc/Ccsds/CfdpManager/Utils.hpp>
52
53 namespace Svc {
54 namespace Ccsds {
55 namespace Cfdp {
56
57 // ----------------------------------------------------------------------
58 // Construction and destruction
59 // ----------------------------------------------------------------------
60
61 158 Engine::Engine(CfdpManager* manager) : m_manager(manager), m_seqNum(0), m_allocator(nullptr), m_allocatorId(0) {
62
2/2
✓ Branch 0 taken 316 times.
✓ Branch 1 taken 158 times.
474 for (U8 i = 0; i < Cfdp::NumChannels; ++i) {
63 316 m_channels[i] = nullptr;
64 }
65 158 }
66
67 316 Engine::~Engine() {
68 316 FW_ASSERT(m_allocator != nullptr, 0); // init() must have been called
69
70
2/2
✓ Branch 0 taken 316 times.
✓ Branch 1 taken 158 times.
948 for (U8 i = 0; i < Cfdp::NumChannels; ++i) {
71
2/2
✓ Branch 5 taken 314 times.
✓ Branch 6 taken 2 times.
632 if (m_channels[i] != nullptr) {
72 // Clean up Channel's internal arrays first
73 628 m_channels[i]->cleanup(*m_allocator, m_allocatorId);
74
75 // Call destructor
76 628 m_channels[i]->~Channel();
77
78 // Deallocate the Channel object itself
79 628 m_allocator->deallocate(m_allocatorId, m_channels[i]);
80 628 m_channels[i] = nullptr;
81 }
82 }
83 316 }
84
85 // ----------------------------------------------------------------------
86 // Public interface methods
87 // ----------------------------------------------------------------------
88
89 157 void Engine::init(Fw::MemAllocator& allocator, FwEnumStoreType memId) {
90 // Store allocator for cleanup in destructor
91 157 m_allocator = &allocator;
92 157 m_allocatorId = memId;
93
94 // Allocate and construct all channels using the allocator
95
2/2
✓ Branch 0 taken 314 times.
✓ Branch 1 taken 157 times.
471 for (U8 i = 0; i < Cfdp::NumChannels; ++i) {
96 314 FwSizeType channelSize = sizeof(Channel);
97
1/1
✓ Branch 4 taken 314 times.
314 void* channelMem = allocator.allocate(memId, channelSize);
98 314 FW_ASSERT(channelMem != nullptr);
99
100 // Use placement new to construct Channel in allocated memory
101
1/3
✓ Branch 7 taken 314 times.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
314 m_channels[i] = new (channelMem) Channel(this, i, this->m_manager, allocator, memId);
102 }
103 157 }
104
105 60 void Engine::armAckTimer(Transaction* txn) {
106 60 txn->m_ack_timer.setTimer(txn->m_cfdpManager->getAckTimerParam(txn->m_chan_num));
107 60 txn->m_flags.com.ack_timer_armed = true;
108 60 }
109
110 242 void Engine::armInactTimer(Transaction* txn) {
111 242 U32 timerDuration = 0;
112
113 // select timeout based on the state
114
2/2
✓ Branch 1 taken 161 times.
✓ Branch 2 taken 81 times.
242 if (GetTxnStatus(txn) == AckTxnStatus::ACK_TXN_STATUS_ACTIVE) {
115 // in an active transaction, we expect traffic so use the normal inactivity timer
116 161 timerDuration = txn->m_cfdpManager->getInactivityTimerParam(txn->m_chan_num);
117 } else {
118 // in an inactive transaction, we do NOT expect traffic, and this timer is now used
119 // just in case any late straggler PDUs dp get delivered. In this case the
120 // time should be longer than the retransmit time (ack timer) but less than the full
121 // inactivity timer (because again, we are not expecting traffic, so waiting the full
122 // timeout would hold resources longer than needed). Using double the ack timer should
123 // ensure that if the remote retransmitted anything, we will see it, and avoids adding
124 // another config option just for this.
125 81 timerDuration = txn->m_cfdpManager->getAckTimerParam(txn->m_chan_num) * 2;
126 }
127
128 242 txn->m_inactivity_timer.setTimer(timerDuration);
129 242 }
130
131 157 void Engine::dispatchRecv(Transaction* txn, const Fw::Buffer& buffer) {
132 // Loop to handle state transitions without recursion
133 // The loop allows recvInit to transition to R2 state and re-dispatch
134 157 bool needsDispatch = true;
135
2/2
✓ Branch 0 taken 161 times.
✓ Branch 1 taken 157 times.
318 while (needsDispatch) {
136 161 needsDispatch = false; // Assume single dispatch unless state handler requests re-dispatch
137
138 // Dispatch based on transaction state
139
5/8
✓ Branch 1 taken 92 times.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 39 times.
✓ Branch 5 taken 14 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
161 switch (txn->m_state) {
140 92 case TxnState::TXN_STATE_INIT:
141 92 needsDispatch = this->recvInit(txn, buffer);
142 92 break;
143 15 case TxnState::TXN_STATE_R1:
144 15 txn->r1Recv(buffer);
145 15 break;
146 ✗ case TxnState::TXN_STATE_S1:
147 ✗ txn->s1Recv(buffer);
148 ✗ break;
149 39 case TxnState::TXN_STATE_R2:
150 39 txn->r2Recv(buffer);
151 39 break;
152 14 case TxnState::TXN_STATE_S2:
153 14 txn->s2Recv(buffer);
154 14 break;
155 ✗ case TxnState::TXN_STATE_DROP:
156 ✗ this->recvDrop(txn, buffer);
157 ✗ break;
158 1 case TxnState::TXN_STATE_HOLD:
159 1 this->recvHold(txn, buffer);
160 1 break;
161 ✗ default:
162 // Invalid or undefined state
163 ✗ break;
164 }
165 }
166
167 157 this->armInactTimer(txn); // whenever a packet was received by the other size, always arm its inactivity timer
168 157 }
169
170 67 void Engine::dispatchTx(Transaction* txn) {
171 static const TxnSendDispatchTable state_fns = {{
172 nullptr, // TxnState::TXN_STATE_UNDEF
173 nullptr, // TxnState::TXN_STATE_INIT
174 nullptr, // TxnState::TXN_STATE_R1
175 &Transaction::s1Tx, // TxnState::TXN_STATE_S1
176 nullptr, // TxnState::TXN_STATE_R2
177 &Transaction::s2Tx, // TxnState::TXN_STATE_S2
178 nullptr, // TxnState::TXN_STATE_DROP
179 nullptr // TxnState::TXN_STATE_HOLD
180 }};
181
182 67 txn->txStateDispatch(&state_fns);
183 67 }
184
185 16 Status::T Engine::sendMd(Transaction* txn) {
186 16 FW_ASSERT((txn->m_state == TxnState::TXN_STATE_S1) || (txn->m_state == TxnState::TXN_STATE_S2),
187 static_cast<U8>(txn->m_state));
188 16 FW_ASSERT(txn->m_chan != nullptr);
189
190 // Create and initialize Metadata PDU
191
1/1
✓ Branch 2 taken 16 times.
16 MetadataPdu md;
192
193 // Set closure requested flag based on transaction class
194 // Class 1: closure not requested (0), Class 2: closure requested (1)
195
2/2
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 6 times.
16 U8 closureRequested = (txn->m_state == TxnState::TXN_STATE_S2) ? 1 : 0;
196
197 // Direction is toward receiver for metadata PDU sent by sender
198 16 Cfdp::PduDirection direction = PduDirection::DIRECTION_TOWARD_RECEIVER;
199
200
2/2
✓ Branch 6 taken 16 times.
✓ Branch 10 taken 16 times.
96 md.initialize(direction,
201 16 txn->getClass(), // transmission mode (Class 1 or 2)
202 16 m_manager->getLocalEidParam(), // source EID
203 16 txn->m_history->seq_num, // transaction sequence number
204 16 txn->m_history->peer_eid, // destination EID
205 txn->m_fsize, // file size
206 16 txn->m_history->fnames.src_filename, // source filename
207 16 txn->m_history->fnames.dst_filename, // destination filename
208 ChecksumType::CHECKSUM_TYPE_MODULAR, // checksum type
209 closureRequested // closure requested flag
210 );
211
212
1/1
✓ Branch 4 taken 16 times.
32 return serializeAndSendPdu(txn, md);
213 16 }
214
215 40 Status::T Engine::sendFd(Transaction* txn, FileDataPdu& fdPdu) {
216 40 Status::T status = serializeAndSendPdu(txn, fdPdu);
217
1/2
✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
40 if (status == Cfdp::Status::SUCCESS) {
218 40 m_manager->addSentFileDataBytes(txn->getChannelId(), fdPdu.getDataSize());
219 }
220 40 return status;
221 }
222
223 20 Status::T Engine::sendEof(Transaction* txn) {
224 // Create and initialize EOF PDU
225
1/1
✓ Branch 2 taken 20 times.
20 EofPdu eof;
226
227 // Direction is toward receiver for EOF sent by sender
228 20 Cfdp::PduDirection direction = PduDirection::DIRECTION_TOWARD_RECEIVER;
229
1/1
✓ Branch 5 taken 20 times.
20 ConditionCode conditionCode = static_cast<ConditionCode>(TxnStatusToConditionCode(txn->m_history->txn_stat));
230
231 // Increment sent EOF counters based on condition code
232
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 if (conditionCode == ConditionCode::CONDITION_CODE_CANCEL_REQUEST_RECEIVED) {
233 ✗ this->m_manager->incrementSentEofCanceled(txn->getChannelId());
234
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 18 times.
20 } else if (conditionCode != ConditionCode::CONDITION_CODE_NO_ERROR) {
235
1/1
✓ Branch 10 taken 2 times.
2 this->m_manager->incrementFaultTxEofError(txn->getChannelId());
236 }
237
238
3/3
✓ Branch 4 taken 20 times.
✓ Branch 9 taken 20 times.
✓ Branch 13 taken 20 times.
120 eof.initialize(direction,
239 20 txn->getClass(), // transmission mode
240 20 m_manager->getLocalEidParam(), // source EID
241 20 txn->m_history->seq_num, // transaction sequence number
242 20 txn->m_history->peer_eid, // destination EID
243 conditionCode, // condition code
244 20 txn->m_crc.getValue(), // checksum
245 txn->m_fsize // file size
246 );
247
248 // Add entity ID TLV on error conditions (optional per CCSDS spec)
249
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 18 times.
20 if (conditionCode != ConditionCode::CONDITION_CODE_NO_ERROR) {
250
1/1
✓ Branch 2 taken 2 times.
2 Cfdp::Tlv tlv;
251
2/2
✓ Branch 8 taken 2 times.
✓ Branch 11 taken 2 times.
2 tlv.initialize(m_manager->getLocalEidParam()); // Local entity ID
252
1/1
✓ Branch 2 taken 2 times.
2 eof.appendTlv(tlv);
253 }
254
255
1/1
✓ Branch 4 taken 20 times.
40 return serializeAndSendPdu(txn, eof);
256 20 }
257
258 13 Status::T Engine::sendAck(Transaction* txn,
259 AckTxnStatus ts,
260 FileDirective dir_code,
261 ConditionCode cc,
262 EntityId peer_eid,
263 TransactionSeq tsn) {
264 13 FW_ASSERT(
265 (dir_code == FileDirective::FILE_DIRECTIVE_END_OF_FILE) || (dir_code == FileDirective::FILE_DIRECTIVE_FIN),
266 static_cast<U8>(dir_code));
267
268 // Determine source and destination EIDs based on transaction direction
269 EntityId src_eid;
270 EntityId dst_eid;
271
2/2
✓ Branch 4 taken 7 times.
✓ Branch 5 taken 6 times.
13 if (txn->getHistory()->dir == Direction::DIRECTION_TX) {
272
1/1
✓ Branch 8 taken 7 times.
7 src_eid = m_manager->getLocalEidParam();
273 7 dst_eid = peer_eid;
274 } else {
275 6 src_eid = peer_eid;
276
1/1
✓ Branch 8 taken 6 times.
6 dst_eid = m_manager->getLocalEidParam();
277 }
278
279 // Create and initialize ACK PDU
280
1/1
✓ Branch 2 taken 13 times.
13 AckPdu ack;
281
282 // Direction: toward sender for EOF ACK, toward receiver for FIN ACK
283 13 Cfdp::PduDirection direction = (dir_code == FileDirective::FILE_DIRECTIVE_END_OF_FILE)
284
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 6 times.
13 ? Cfdp::PduDirection::DIRECTION_TOWARD_SENDER
285 : Cfdp::PduDirection::DIRECTION_TOWARD_RECEIVER;
286
287
1/1
✓ Branch 3 taken 13 times.
26 ack.initialize(direction,
288 13 txn->getClass(), // transmission mode
289 src_eid, // source EID
290 tsn, // transaction sequence number
291 dst_eid, // destination EID
292 dir_code, // directive being acknowledged
293 1, // directive subtype code (always 1)
294 cc, // condition code
295 ts // transaction status
296 );
297
298
1/1
✓ Branch 4 taken 13 times.
26 return serializeAndSendPdu(txn, ack);
299 13 }
300
301 13 Status::T Engine::sendFin(Transaction* txn, FinDeliveryCode dc, FinFileStatus fs, ConditionCode cc) {
302 // Create and initialize FIN PDU
303
1/1
✓ Branch 2 taken 13 times.
13 FinPdu fin;
304
305 // Direction is toward sender for FIN sent by receiver
306 13 Cfdp::PduDirection direction = PduDirection::DIRECTION_TOWARD_SENDER;
307
308
2/2
✓ Branch 2 taken 13 times.
✓ Branch 8 taken 13 times.
65 fin.initialize(direction,
309 13 txn->getClass(), // transmission mode
310 13 txn->m_history->peer_eid, // source EID (receiver)
311 13 txn->m_history->seq_num, // transaction sequence number
312 13 m_manager->getLocalEidParam(), // destination EID (sender)
313 cc, // condition code
314 static_cast<FinDeliveryCode>(dc), // delivery code
315 static_cast<FinFileStatus>(fs) // file status
316 );
317
318 // Add entity ID TLV on error conditions (optional per CCSDS spec)
319
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 6 times.
13 if (cc != ConditionCode::CONDITION_CODE_NO_ERROR) {
320
1/1
✓ Branch 2 taken 7 times.
7 Cfdp::Tlv tlv;
321
2/2
✓ Branch 8 taken 7 times.
✓ Branch 11 taken 7 times.
7 tlv.initialize(m_manager->getLocalEidParam()); // Local entity ID
322
1/1
✓ Branch 2 taken 7 times.
7 fin.appendTlv(tlv);
323 }
324
325
1/1
✓ Branch 4 taken 13 times.
26 return serializeAndSendPdu(txn, fin);
326 13 }
327
328 2 Status::T Engine::sendNak(Transaction* txn, NakPdu& nakPdu) {
329 // Verify this is a Class 2 transaction (NAK only used in Class 2)
330 2 Class::T tx_class = txn->getClass();
331 2 FW_ASSERT(tx_class == Cfdp::Class::CLASS_2, tx_class);
332
333 2 return serializeAndSendPdu(txn, nakPdu);
334 }
335
336 105 Status::T Engine::serializeAndSendPdu(Transaction* txn, PduBase& pdu) {
337 // Delegate to the channel-based helper; a transaction always carries its channel.
338 105 return this->serializeAndSendPduOnChannel(*txn->m_chan, pdu);
339 }
340
341 106 Status::T Engine::serializeAndSendPduOnChannel(Channel& chan, PduBase& pdu) {
342
1/1
✓ Branch 2 taken 106 times.
106 Fw::Buffer buffer;
343 106 Status::T status = Cfdp::Status::SUCCESS;
344
345 // Allocate buffer with space for packet descriptor
346
1/1
✓ Branch 7 taken 106 times.
106 const FwSizeType bufferSize = pdu.getBufferSize() + CfdpManager::PACKET_DESCRIPTOR_SIZE;
347
1/1
✓ Branch 8 taken 106 times.
106 status = m_manager->getPduBuffer(buffer, chan, bufferSize);
348
349
1/2
✓ Branch 0 taken 106 times.
✗ Branch 1 not taken.
106 if (status == Cfdp::Status::SUCCESS) {
350 // Serialize to buffer at offset to leave room for descriptor
351
1/1
✓ Branch 2 taken 106 times.
212 Fw::SerialBuffer sb(buffer.getData() + CfdpManager::PACKET_DESCRIPTOR_SIZE,
352
2/2
✓ Branch 2 taken 106 times.
✓ Branch 7 taken 106 times.
212 buffer.getSize() - CfdpManager::PACKET_DESCRIPTOR_SIZE);
353
1/1
✓ Branch 6 taken 106 times.
106 Fw::SerializeStatus serStatus = pdu.serializeTo(sb);
354
355
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 105 times.
106 if (serStatus != Fw::FW_SERIALIZE_OK) {
356 // Log generic PDU serialization error with PDU type
357
2/2
✓ Branch 14 taken 1 times.
✓ Branch 20 taken 1 times.
1 m_manager->log_WARNING_LO_FailPduSerialization(chan.getChannelId(), pdu.getType(),
358 static_cast<I32>(serStatus));
359
1/1
✓ Branch 8 taken 1 times.
1 m_manager->returnPduBuffer(chan, buffer);
360 1 status = Cfdp::Status::ERROR;
361 } else {
362 // Update buffer size to actual serialized size plus descriptor
363
2/2
✓ Branch 3 taken 105 times.
✓ Branch 6 taken 105 times.
105 buffer.setSize(sb.getSize() + CfdpManager::PACKET_DESCRIPTOR_SIZE);
364
1/1
✓ Branch 8 taken 105 times.
105 m_manager->sendPduBuffer(chan, buffer);
365
1/1
✓ Branch 10 taken 105 times.
105 m_manager->incrementSentPdu(chan.getChannelId());
366 }
367 106 }
368
369 106 return status;
370 106 }
371
372 1 Status::T Engine::sendFinAckStateless(Channel& chan,
373 TransactionSeq tsn,
374 EntityId finSrcEid,
375 EntityId finDstEid,
376 ConditionCode cc) {
377 // A FIN has arrived for a downlink transaction we sourced, but no live transaction
378 // remains (it already completed and was recycled). Per CFDP the sender must still
379 // acknowledge a retransmitted FIN; we do so statelessly from the FIN header.
380 //
381 // The FIN was sent toward us (the sender), so its header carries
382 // sourceEid = the transaction source = this local entity,
383 // destEid = the peer (receiver).
384 // The ACK(FIN) we emit travels toward the receiver, mirroring Engine::sendAck's
385 // DIRECTION_TX case: src = local entity, dst = peer.
386
1/1
✓ Branch 2 taken 1 times.
1 AckPdu ack;
387
1/1
✓ Branch 2 taken 1 times.
1 ack.initialize(Cfdp::PduDirection::DIRECTION_TOWARD_RECEIVER,
388 Cfdp::Class::CLASS_2, // FIN/ACK only exist in class 2
389 finSrcEid, // source EID (this local entity)
390 tsn, // transaction sequence number
391 finDstEid, // destination EID (the peer/receiver)
392 FileDirective::FILE_DIRECTIVE_FIN, // directive being acknowledged
393 1, // directive subtype code (always 1)
394 cc, // echo the FIN's condition code
395 AckTxnStatus::ACK_TXN_STATUS_UNRECOGNIZED // we no longer recognize this transaction
396 );
397
398
1/1
✓ Branch 4 taken 1 times.
2 return this->serializeAndSendPduOnChannel(chan, ack);
399 1 }
400
401 89 bool Engine::validateRxDestPath(U8 chan_num, Fw::String& path, RxDestPathRejectReason& reason) {
402
1/1
✓ Branch 6 taken 89 times.
89 Fw::String rxDir = this->m_manager->getRxDirParam(chan_num);
403
3/3
✓ Branch 2 taken 89 times.
✓ Branch 4 taken 81 times.
✓ Branch 5 taken 8 times.
89 if (rxDir.length() == 0) {
404 // Sandbox not configured for this channel: accept path as-is (documented fail-open behavior)
405 81 return true;
406 }
407
408 // Canonicalize the receive directory (relative rx_dir resolves against CWD) into the
409 // trailing-'/' form that checkContainment requires; shared with Os::SandboxedFile
410 8 char root[Os::FilePathUtils::MAX_PATH_LENGTH];
411
3/3
✓ Branch 3 taken 8 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 7 times.
8 if (Os::FilePathUtils::resolveDirectoryFromCwd(rxDir.toChar(), root, sizeof(root)) != Os::FilePathUtils::VALID) {
412
1/1
✓ Branch 4 taken 1 times.
1 reason = RxDestPathRejectReason::RX_DIR_UNRESOLVABLE;
413 1 return false;
414 }
415
416 // Resolve the received path: relative paths land inside the receive directory, `..` segments
417 // are collapsed textually (symlinks are not followed; see Os::SandboxedFile threat model)
418 7 char resolved[Os::FilePathUtils::MAX_PATH_LENGTH];
419
3/3
✓ Branch 5 taken 7 times.
✓ Branch 7 taken 1 times.
✓ Branch 8 taken 6 times.
7 if (Os::FilePathUtils::resolvePath(path.toChar(), root, resolved, sizeof(resolved)) != Os::FilePathUtils::VALID) {
420
1/1
✓ Branch 4 taken 1 times.
1 reason = RxDestPathRejectReason::PATH_UNRESOLVABLE;
421 1 return false;
422 }
423
3/3
✓ Branch 1 taken 6 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 2 times.
6 if (Os::FilePathUtils::checkContainment(resolved, root) != Os::FilePathUtils::VALID) {
424
1/1
✓ Branch 4 taken 4 times.
4 reason = RxDestPathRejectReason::OUTSIDE_RX_DIR;
425 4 return false;
426 }
427 // The canonical path is stored in the transaction and reported in events sized to
428 // MaxFilePathSize; reject rather than silently truncate a longer result
429
3/3
✓ Branch 1 taken 2 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
2 if (Fw::StringUtils::string_length(resolved, sizeof(resolved)) > Cfdp::MaxFilePathSize) {
430
1/1
✓ Branch 4 taken 1 times.
1 reason = RxDestPathRejectReason::TOO_LONG;
431 1 return false;
432 }
433
434
1/1
✓ Branch 4 taken 1 times.
1 path = resolved;
435 1 return true;
436 89 }
437
438 89 Status::T Engine::recvMd(Transaction* txn, const MetadataPdu& md) {
439 /* the destination path comes from the remote entity: canonicalize it and confirm it lies within
440 * the configured receive directory BEFORE anything from this PDU is committed to the transaction.
441 * On rejection nothing from the PDU (file size, source name, destination) is stored; the only
442 * change to the transaction is its status, set below. */
443
1/1
✓ Branch 7 taken 89 times.
89 Fw::String dst = md.getDestFilename();
444
1/1
✓ Branch 2 taken 89 times.
89 RxDestPathRejectReason reason;
445
3/3
✓ Branch 6 taken 89 times.
✓ Branch 8 taken 7 times.
✓ Branch 9 taken 82 times.
89 if (!this->validateRxDestPath(txn->m_chan_num, dst, reason)) {
446
1/1
✓ Branch 18 taken 7 times.
21 this->m_manager->log_WARNING_HI_RxDestPathRejected(txn->m_chan_num, txn->m_history->src_eid,
447 7 txn->m_history->seq_num, dst,
448
1/1
✓ Branch 7 taken 7 times.
14 this->m_manager->getRxDirParam(txn->m_chan_num), reason);
449
1/1
✓ Branch 10 taken 7 times.
7 this->m_manager->incrementFaultFileOpen(txn->m_chan_num);
450 // Refused destination = filestore rejection, so finishTransaction reports a failed reception.
451 // Whether the sender learns of it depends on the caller; see the recvMd doc in Engine.hpp.
452
1/1
✓ Branch 4 taken 7 times.
7 this->setTxnStatus(txn, TxnStatus::TXN_STATUS_FILESTORE_REJECTION);
453 7 return Status::PDU_METADATA_ERROR;
454 }
455
456 /* store the expected file size in transaction */
457 82 txn->m_fsize = md.getFileSize();
458
459 /* structural validation (length, non-empty) already done during deserialization */
460
1/1
✓ Branch 12 taken 82 times.
82 txn->m_history->fnames.src_filename = md.getSourceFilename();
461
1/1
✓ Branch 8 taken 82 times.
82 txn->m_history->fnames.dst_filename = dst;
462
463
1/1
✓ Branch 16 taken 82 times.
246 this->m_manager->log_ACTIVITY_LO_MetadataReceived(txn->m_history->fnames.src_filename,
464 164 txn->m_history->fnames.dst_filename, txn->m_history->seq_num);
465 82 return Status::SUCCESS;
466 89 }
467
468 35 Status::T Engine::recvFd(Transaction* txn, const FileDataPdu& fd) {
469 35 Status::T ret = Cfdp::Status::SUCCESS;
470
471 // Extract header
472 35 const Cfdp::PduHeader& header = fd.asHeader();
473
474 // Check for segment metadata flag (not currently supported)
475
2/2
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 34 times.
35 if (header.hasSegmentMetadata()) {
476 /* If recv PDU has the "segment_meta_flag" set, this is not currently handled in CF. */
477 1 this->m_manager->log_WARNING_LO_FileDataSegmentMetadata();
478 1 this->setTxnStatus(txn, TxnStatus::TXN_STATUS_PROTOCOL_ERROR);
479 1 this->m_manager->incrementRecvErrors(txn->getChannelId());
480 1 ret = Cfdp::Status::ERROR;
481 }
482
483 35 return ret;
484 }
485
486 12 Status::T Engine::recvEof(Transaction* txn, const EofPdu& eofPdu) {
487 // EOF PDU has been validated during fromBuffer()
488
489 // Process TLVs if present
490 12 const Cfdp::TlvList& tlvList = eofPdu.getTlvList();
491
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
12 for (U8 i = 0; i < tlvList.getNumTlv(); i++) {
492 ✗ const Cfdp::Tlv& tlv = tlvList.getTlv(i);
493 ✗ if (tlv.getType() == Cfdp::TlvType::TLV_TYPE_ENTITY_ID) {
494 // Entity ID TLV present - validation not currently performed
495 // Future enhancement: Add validation or logging if required
496 }
497 // Other TLV types can be processed here in the future
498 }
499
500 12 return Cfdp::Status::SUCCESS;
501 }
502
503 7 Status::T Engine::recvFin(Transaction* txn, const FinPdu& finPdu) {
504 // FIN PDU has been validated during fromBuffer()
505
506 // Process TLVs if present
507 7 const Cfdp::TlvList& tlvList = finPdu.getTlvList();
508
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
7 for (U8 i = 0; i < tlvList.getNumTlv(); i++) {
509 ✗ const Cfdp::Tlv& tlv = tlvList.getTlv(i);
510 ✗ if (tlv.getType() == Cfdp::TlvType::TLV_TYPE_ENTITY_ID) {
511 // Entity ID TLV present - validation not currently performed
512 // Future enhancement: Add validation or logging if required
513 }
514 // Other TLV types can be processed here in the future
515 }
516
517 7 return Cfdp::Status::SUCCESS;
518 }
519
520 4 Status::T Engine::recvNak(Transaction* txn, const NakPdu& pdu) {
521 // NAK PDU has been validated during fromBuffer()
522 4 return Cfdp::Status::SUCCESS;
523 }
524
525 ✗ void Engine::recvDrop(Transaction* txn, const Fw::Buffer& buffer) {
526 ✗ this->m_manager->incrementRecvDropped(txn->getChannelId());
527 (void)buffer; // Unused - we're just dropping the PDU
528 ✗ }
529
530 1 void Engine::recvHold(Transaction* txn, const Fw::Buffer& buffer) {
531 // anything received in this state is considered spurious
532 1 this->m_manager->incrementRecvSpurious(txn->getChannelId());
533
534 //
535 // Normally we do not expect PDUs for a transaction in holdover, because
536 // from the local point of view it is completed and done. But the reason
537 // for the holdover is because the remote side might not have gotten all
538 // the acks and could still be [re-]sending us PDUs for anything it does
539 // not know we got already.
540 //
541 // If an R2 sent FIN, it's possible that the peer missed the
542 // FIN-ACK and is sending another FIN. In that case we need to send
543 // another ACK.
544 //
545
546 // currently the only thing we will re-ack is the FIN.
547
548 // Use peekPduType to determine the PDU type
549 1 Cfdp::PduTypeEnum::T pduType = Cfdp::peekPduType(buffer);
550
551 // Check if this is a FIN PDU for a Class 2 transaction
552
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
1 if (pduType == Cfdp::PduTypeEnum::FINISHED && txn->getClass() == Cfdp::Class::CLASS_2) {
553 // Deserialize FIN PDU
554 ✗ FinPdu fin;
555 ✗ Fw::SerialBuffer sb2(const_cast<U8*>(buffer.getData()), buffer.getSize());
556 ✗ sb2.setBuffLen(buffer.getSize());
557
558 ✗ Fw::SerializeStatus deserStatus = fin.deserializeFrom(sb2);
559 ✗ if (deserStatus == Fw::FW_SERIALIZE_OK) {
560 // Re-send the FIN-ACK
561 ✗ this->sendAck(txn, AckTxnStatus::ACK_TXN_STATUS_TERMINATED, FileDirective::FILE_DIRECTIVE_FIN,
562 ✗ fin.getConditionCode(), txn->m_history->peer_eid, txn->m_history->seq_num);
563 }
564 // Note: Deserialization errors are silently ignored in hold state
565 // as we're just trying to be helpful by re-acknowledging FIN if we can
566 ✗ }
567 1 }
568
569 92 bool Engine::recvInit(Transaction* txn, const Fw::Buffer& buffer) {
570 // Use peekPduType to determine the PDU type before deserializing
571
1/1
✓ Branch 1 taken 92 times.
92 Cfdp::PduTypeEnum::T pduType = Cfdp::peekPduType(buffer);
572
573 // First parse header to get transaction information
574 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
575
3/3
✓ Branch 5 taken 92 times.
✓ Branch 11 taken 92 times.
✓ Branch 14 taken 92 times.
92 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
576
2/2
✓ Branch 5 taken 92 times.
✓ Branch 8 taken 92 times.
92 sb.setBuffLen(buffer.getSize());
577
578 92 Cfdp::PduHeader header;
579
1/1
✓ Branch 1 taken 92 times.
92 Fw::SerializeStatus status = header.fromSerialBuffer(sb);
580
581
1/2
✓ Branch 0 taken 92 times.
✗ Branch 1 not taken.
92 if (status == Fw::FW_SERIALIZE_OK) {
582 92 TransactionSeq transactionSeq = header.getTransactionSeq();
583 92 EntityId sourceEid = header.getSourceEid();
584 92 Class::T txmMode = header.getTxmMode();
585
586 // only RX transactions dare tread here
587 92 txn->m_history->seq_num = transactionSeq;
588
589 // peer_eid is always the remote partner. src_eid is always the transaction source.
590 // in this case, they are the same
591 92 txn->m_history->peer_eid = sourceEid;
592 92 txn->m_history->src_eid = sourceEid;
593
594 // all RX transactions will need a chunk list to track file segments
595
1/2
✓ Branch 2 taken 92 times.
✗ Branch 3 not taken.
92 if (txn->m_chunks == nullptr) {
596
1/1
✓ Branch 4 taken 92 times.
92 txn->m_chunks = txn->m_chan->findUnusedChunks(Direction::DIRECTION_RX);
597 }
598
2/2
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 91 times.
92 if (txn->m_chunks == nullptr) {
599
1/1
✓ Branch 9 taken 1 times.
1 this->m_manager->log_WARNING_LO_ChunklistUnavailable(transactionSeq);
600 } else {
601
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 87 times.
91 if (pduType == Cfdp::PduTypeEnum::FILE_DATA) {
602 // file data PDU
603 // being idle and receiving a file data PDU means that no active transaction knew
604 // about the transaction in progress, so most likely PDUs were missed.
605
606
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (txmMode == Cfdp::Class::CLASS_1) {
607 // R1, can't do anything without metadata first
608 ✗ txn->m_state = TxnState::TXN_STATE_DROP; // drop all incoming
609 // use inactivity timer to ultimately free the state
610 } else {
611 // R2 can handle missing metadata, so go ahead and create a temp file
612 4 txn->m_state = TxnState::TXN_STATE_R2;
613 4 txn->m_txn_class = Cfdp::Class::CLASS_2;
614
1/1
✓ Branch 2 taken 4 times.
4 txn->rInit();
615 4 return true; // Request re-dispatch to enter r2 state handler
616 }
617
2/2
✓ Branch 0 taken 86 times.
✓ Branch 1 taken 1 times.
87 } else if (pduType == Cfdp::PduTypeEnum::METADATA) {
618 // file directive PDU with metadata - this is the expected case for starting a new RX transaction
619
1/1
✓ Branch 2 taken 86 times.
86 MetadataPdu md;
620
3/3
✓ Branch 5 taken 86 times.
✓ Branch 11 taken 86 times.
✓ Branch 14 taken 86 times.
86 Fw::SerialBuffer sb2(const_cast<U8*>(buffer.getData()), buffer.getSize());
621
2/2
✓ Branch 5 taken 86 times.
✓ Branch 8 taken 86 times.
86 sb2.setBuffLen(buffer.getSize());
622
623
1/1
✓ Branch 2 taken 86 times.
86 Fw::SerializeStatus deserStatus = md.deserializeFrom(sb2);
624
2/2
✓ Branch 0 taken 85 times.
✓ Branch 1 taken 1 times.
86 if (deserStatus == Fw::FW_SERIALIZE_OK) {
625
3/3
✓ Branch 4 taken 85 times.
✓ Branch 6 taken 79 times.
✓ Branch 7 taken 6 times.
85 if (this->recvMd(txn, md) == Status::SUCCESS) {
626 // NOTE: whether or not class 1 or 2, get a free chunks. It's cheap, and simplifies cleanup
627 // path
628 79 txn->m_state =
629
2/2
✓ Branch 0 taken 68 times.
✓ Branch 1 taken 11 times.
79 txmMode == Cfdp::Class::CLASS_1 ? TxnState::TXN_STATE_R1 : TxnState::TXN_STATE_R2;
630 79 txn->m_txn_class = txmMode;
631 79 txn->m_flags.rx.md_recv = true;
632
1/1
✓ Branch 2 taken 79 times.
79 txn->rInit(); // initialize R
633 }
634 // else: destination path rejected and the transaction marked FILESTORE_REJECTION.
635 // State stays INIT so the transaction is finished below as a failed reception
636 // (parked in HOLD until the inactivity timer recycles it) without any file
637 // ever being opened.
638 } else {
639
1/1
✓ Branch 11 taken 1 times.
1 m_manager->log_WARNING_LO_FailMetadataPduDeserialization(txn->getChannelId(),
640 static_cast<I32>(deserStatus));
641 }
642 86 } else {
643 // Unexpected PDU type in init state
644
1/1
✓ Branch 9 taken 1 times.
1 this->m_manager->log_WARNING_LO_UnhandledPduInIdleState();
645
1/1
✓ Branch 10 taken 1 times.
1 this->m_manager->incrementRecvErrors(txn->getChannelId());
646 }
647 }
648
649
2/2
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 79 times.
88 if (txn->m_state == TxnState::TXN_STATE_INIT) {
650 // state was not changed, so free the transaction
651
1/1
✓ Branch 4 taken 9 times.
9 this->finishTransaction(txn, false);
652 }
653 } else {
654 ✗ m_manager->log_WARNING_LO_FailPduHeaderDeserialization(txn->getChannelId(), status);
655 }
656 88 return false; // No re-dispatch needed
657 92 }
658
659 161 void Engine::receivePdu(U8 chan_id, const Fw::Buffer& buffer) {
660 161 Transaction* txn = nullptr;
661 161 Channel* chan = nullptr;
662
663 161 FW_ASSERT(chan_id < Cfdp::NumChannels, chan_id, Cfdp::NumChannels);
664
665 161 chan = m_channels[chan_id];
666 161 FW_ASSERT(chan != nullptr);
667
668 // Parse the header to get transaction routing info
669 // Avoid full PDU deserialization here to defer it until the appropriate handler
670 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
671
3/3
✓ Branch 5 taken 161 times.
✓ Branch 11 taken 161 times.
✓ Branch 14 taken 161 times.
161 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
672
2/2
✓ Branch 5 taken 161 times.
✓ Branch 8 taken 161 times.
161 sb.setBuffLen(buffer.getSize());
673
674 161 Cfdp::PduHeader header;
675
1/1
✓ Branch 1 taken 161 times.
161 Fw::SerializeStatus status = header.fromSerialBuffer(sb);
676
677
2/2
✓ Branch 0 taken 160 times.
✓ Branch 1 taken 1 times.
161 if (status == Fw::FW_SERIALIZE_OK) {
678 // Increment received PDU counter for PDUs with valid headers
679
1/1
✓ Branch 8 taken 160 times.
160 this->m_manager->incrementRecvPdu(chan_id);
680
681 160 TransactionSeq transactionSeq = header.getTransactionSeq();
682 160 EntityId sourceEid = header.getSourceEid();
683 160 EntityId destEid = header.getDestEid();
684
685 // Look up transaction by sequence number
686
1/1
✓ Branch 2 taken 160 times.
160 txn = chan->findTransactionBySequenceNumber(transactionSeq, sourceEid);
687
688
2/2
✓ Branch 0 taken 95 times.
✓ Branch 1 taken 65 times.
160 if (txn == nullptr) {
689 // A retransmitted FIN can arrive for a downlink transaction we sourced after
690 // that transaction has already completed and been recycled (e.g. the peer
691 // kept retransmitting FIN across a lossy/quiet link). Per CFDP the sender
692 // must still acknowledge it, so re-ACK statelessly from the FIN header.
693 // Such a FIN carries sourceEid == our local entity (we were the source) and
694 // destEid == the peer, so it would otherwise be dropped as InvalidDestinationEid.
695
5/5
✓ Branch 1 taken 95 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 94 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 94 times.
96 if (Cfdp::peekPduType(buffer) == Cfdp::PduTypeEnum::FINISHED &&
696
2/3
✓ Branch 8 taken 1 times.
✓ Branch 10 taken 1 times.
✗ Branch 11 not taken.
1 sourceEid == this->m_manager->getLocalEidParam()) {
697
1/1
✓ Branch 2 taken 1 times.
1 FinPdu fin;
698
3/3
✓ Branch 5 taken 1 times.
✓ Branch 11 taken 1 times.
✓ Branch 14 taken 1 times.
1 Fw::SerialBuffer finSb(const_cast<U8*>(buffer.getData()), buffer.getSize());
699
2/2
✓ Branch 5 taken 1 times.
✓ Branch 8 taken 1 times.
1 finSb.setBuffLen(buffer.getSize());
700
2/3
✓ Branch 2 taken 1 times.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 if (fin.deserializeFrom(finSb) == Fw::FW_SERIALIZE_OK) {
701
1/1
✓ Branch 7 taken 1 times.
1 this->sendFinAckStateless(*chan, transactionSeq, sourceEid, destEid, fin.getConditionCode());
702
1/1
✓ Branch 9 taken 1 times.
1 this->m_manager->log_DIAGNOSTIC_TxLateFinAcked(sourceEid, transactionSeq);
703 } else {
704 ✗ this->m_manager->log_WARNING_LO_FailFinPduDeserialization(
705 chan_id, static_cast<I32>(Fw::FW_DESERIALIZE_FORMAT_ERROR));
706 }
707 1 }
708 // if no match found, then it must be the case that we would be the destination entity id, so verify it
709
3/3
✓ Branch 8 taken 94 times.
✓ Branch 10 taken 93 times.
✓ Branch 11 taken 1 times.
94 else if (destEid == this->m_manager->getLocalEidParam()) {
710 // we didn't find a match, so assign it to a transaction
711 // assume this is initiating an RX transaction, as TX transactions are only commanded
712
1/1
✓ Branch 6 taken 93 times.
93 txn = this->startRxTransaction(chan->getChannelId());
713
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 92 times.
93 if (txn == nullptr) {
714
1/1
✓ Branch 9 taken 1 times.
1 this->m_manager->log_WARNING_LO_RxTransactionLimitReached(sourceEid, transactionSeq);
715 }
716 } else {
717
1/1
✓ Branch 9 taken 1 times.
1 this->m_manager->log_WARNING_LO_InvalidDestinationEid(destEid);
718 }
719 }
720
721
2/2
✓ Branch 0 taken 157 times.
✓ Branch 1 taken 3 times.
160 if (txn != nullptr) {
722 // found one! Send it to the transaction state processor
723
1/1
✓ Branch 4 taken 157 times.
157 this->dispatchRecv(txn, buffer);
724 } else {
725 // Transaction limit reached - EVR already emitted by findOrStartRxTransaction
726 }
727 } else {
728 // Invalid PDU header, drop packet
729
1/1
✓ Branch 9 taken 1 times.
1 m_manager->log_WARNING_LO_FailPduHeaderDeserialization(chan_id, static_cast<I32>(status));
730 }
731 322 }
732
733 1 void Engine::setChannelFlowState(U8 channelId, Flow::T flowState) {
734 1 FW_ASSERT(channelId < Cfdp::NumChannels, channelId, Cfdp::NumChannels);
735 1 m_channels[channelId]->setFlowState(flowState);
736 1 }
737
738 3 Status::T Engine::setSuspendResumeTransaction(U8 channelId,
739 TransactionSeq transactionSeq,
740 EntityId entityId,
741 SuspendResume::T action) {
742 3 Status::T status = Status::ERROR;
743
744 3 FW_ASSERT(channelId < Cfdp::NumChannels, channelId, Cfdp::NumChannels);
745
746 3 Channel* chan = m_channels[channelId];
747 3 Transaction* txn = chan->findTransactionBySequenceNumber(transactionSeq, entityId);
748
749
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 if (txn != nullptr) {
750 2 txn->m_flags.com.suspended = (action == SuspendResume::SUSPEND);
751 2 status = Status::SUCCESS;
752 }
753
754 3 return status;
755 }
756
757 2 Status::T Engine::cancelTransactionBySeq(U8 channelId, TransactionSeq transactionSeq, EntityId entityId) {
758 2 Status::T status = Status::ERROR;
759
760 2 FW_ASSERT(channelId < Cfdp::NumChannels, channelId, Cfdp::NumChannels);
761
762 2 Channel* chan = m_channels[channelId];
763 2 Transaction* txn = chan->findTransactionBySequenceNumber(transactionSeq, entityId);
764
765
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (txn != nullptr) {
766 1 this->cancelTransaction(txn);
767 1 status = Status::SUCCESS;
768 }
769
770 2 return status;
771 }
772
773 6 Status::T Engine::abandonTransaction(U8 channelId, TransactionSeq transactionSeq, EntityId entityId) {
774 6 Status::T status = Status::ERROR;
775
776 6 FW_ASSERT(channelId < Cfdp::NumChannels, channelId, Cfdp::NumChannels);
777
778 6 Channel* chan = m_channels[channelId];
779 6 Transaction* txn = chan->findTransactionBySequenceNumber(transactionSeq, entityId);
780
781 // A finished transaction stays findable in HOLD until its inactivity timer recycles it.
782 // There is nothing left to abandon, so report it the same as a transaction that is not found.
783
4/4
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 3 times.
6 if ((txn != nullptr) && (txn->m_state != TxnState::TXN_STATE_HOLD)) {
784 2 this->finishTransaction(txn, false);
785 2 status = Status::SUCCESS;
786 }
787
788 6 return status;
789 }
790
791 20 void Engine::txFileInitiate(Transaction* txn,
792 Class::T cfdp_class,
793 Keep::T keep,
794 U8 chan,
795 U8 priority,
796 EntityId dest_id) {
797 20 txn->initTxFile(cfdp_class, keep, chan, priority);
798
799 // Increment sequence number for new transaction
800 20 ++this->m_seqNum;
801
802 // Capture info for history
803 20 txn->m_history->seq_num = this->m_seqNum;
804 20 txn->m_history->src_eid = m_manager->getLocalEidParam();
805 20 txn->m_history->peer_eid = dest_id;
806
807 20 txn->m_chan->insertSortPrio(txn, QueueId::PEND);
808 20 }
809
810 19 Status::T Engine::txFile(const Fw::String& src_filename,
811 const Fw::String& dst_filename,
812 Class::T cfdp_class,
813 Keep::T keep,
814 U8 chan_num,
815 U8 priority,
816 EntityId dest_id,
817 TransactionInitType initType) {
818 Transaction* txn;
819 19 Channel* chan = nullptr;
820
821 19 FW_ASSERT(chan_num < Cfdp::NumChannels, chan_num, Cfdp::NumChannels);
822 19 chan = m_channels[chan_num];
823
824 19 Status::T ret = Cfdp::Status::SUCCESS;
825
826
2/2
✓ Branch 2 taken 17 times.
✓ Branch 3 taken 2 times.
19 if (chan->getNumCmdTx() < MaxCommandedPlaybackFilesPerChan) {
827 17 txn = chan->findUnusedTransaction(Direction::DIRECTION_TX);
828 } else {
829 2 txn = nullptr;
830 }
831
832
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 17 times.
19 if (txn == nullptr) {
833 2 this->m_manager->log_WARNING_LO_MaxTxTransactionsReached();
834 2 ret = Cfdp::Status::ERROR;
835 } else {
836 // NOTE: the caller of this function ensures the provided src and dst filenames are nullptr terminated
837
838 17 txn->m_history->fnames.src_filename = src_filename;
839 17 txn->m_history->fnames.dst_filename = dst_filename;
840 17 this->txFileInitiate(txn, cfdp_class, keep, chan_num, priority, dest_id);
841
842 17 chan->incrementCmdTxCounter();
843 17 txn->m_flags.tx.cmd_tx = true;
844
845 // Set transaction initiation type
846 17 txn->m_initType = initType;
847
848 // Log transaction queued event
849 17 this->m_manager->log_ACTIVITY_LO_TxFileQueued(txn->m_history->fnames.src_filename, txn->m_history->seq_num);
850 }
851
852 19 return ret;
853 }
854
855 93 Transaction* Engine::startRxTransaction(U8 chan_num) {
856 93 Channel* chan = nullptr;
857 Transaction* txn;
858
859 93 FW_ASSERT(chan_num < Cfdp::NumChannels, chan_num, Cfdp::NumChannels);
860 93 chan = m_channels[chan_num];
861
862 // if (CF_AppData.hk.Payload.channel_hk[chan_num].q_size[QueueId::RX] < CF_MAX_SIMULTANEOUS_RX)
863 // {
864 // txn = chan->findUnusedTransaction(Direction::DIRECTION_RX);
865 // }
866 // else
867 // {
868 // txn = nullptr;
869 // }
870 // Receive transactions are limited by MaxRxTransactions parameter
871 93 txn = chan->findUnusedTransaction(Direction::DIRECTION_RX);
872
873
2/2
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 1 times.
93 if (txn != nullptr) {
874 // set default FIN status
875 92 txn->m_state_data.receive.r2.dc = FinDeliveryCode::FIN_DELIVERY_CODE_INCOMPLETE;
876 92 txn->m_state_data.receive.r2.fs = FinFileStatus::FIN_FILE_STATUS_DISCARDED;
877
878 92 txn->m_flags.com.q_index = QueueId::RX;
879 92 chan->insertBackInQueue(static_cast<QueueId::T>(txn->m_flags.com.q_index), &txn->m_cl_node);
880 }
881
882 93 return txn;
883 }
884
885 10 Status::T Engine::playbackDirInitiate(Playback* pb,
886 const Fw::String& src_filename,
887 const Fw::String& dst_filename,
888 Class::T cfdp_class,
889 Keep::T keep,
890 U8 chan,
891 U8 priority,
892 EntityId dest_id) {
893 10 Status::T status = Cfdp::Status::SUCCESS;
894 Os::Directory::Status dirStatus;
895
896 // make sure the directory can be open
897 10 dirStatus = pb->dir.open(src_filename.toChar(), Os::Directory::READ);
898
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 8 times.
10 if (dirStatus != Os::Directory::OP_OK) {
899 2 this->m_manager->log_WARNING_LO_PlaybackDirOpenFailed(src_filename, dirStatus);
900 2 this->m_manager->incrementFaultDirectoryRead(chan);
901 2 status = Cfdp::Status::ERROR;
902 } else {
903 8 pb->diropen = true;
904 8 pb->busy = true;
905 8 pb->keep = keep;
906 8 pb->priority = priority;
907 8 pb->dest_id = dest_id;
908 8 pb->cfdp_class = cfdp_class;
909
910 // NOTE: the caller of this function ensures the provided src and dst filenames are nullptr terminated
911 8 pb->fnames.src_filename = src_filename;
912 8 pb->fnames.dst_filename = dst_filename;
913 }
914
915 // the executor will start the transfer next cycle
916 10 return status;
917 }
918
919 10 Status::T Engine::playbackDir(const Fw::String& src_filename,
920 const Fw::String& dst_filename,
921 Class::T cfdp_class,
922 Keep::T keep,
923 U8 chan,
924 U8 priority,
925 EntityId dest_id) {
926 U32 i;
927 Playback* pb;
928 Status::T status;
929
930 // Loop through the channel's playback directories to find an open slot
931
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1 times.
13 for (i = 0; i < MaxCommandedPlaybackDirectoriesPerChan; ++i) {
932 12 pb = m_channels[chan]->getPlayback(i);
933
3/4
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 9 times.
✓ Branch 4 taken 3 times.
12 if (!pb->busy) {
934 9 break;
935 }
936 }
937
938
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 9 times.
10 if (i == MaxCommandedPlaybackDirectoriesPerChan) {
939 1 this->m_manager->log_WARNING_LO_PlaybackDirSlotUnavailable();
940 1 status = Cfdp::Status::ERROR;
941 } else {
942 9 status = this->playbackDirInitiate(pb, src_filename, dst_filename, cfdp_class, keep, chan, priority, dest_id);
943 }
944
945 10 return status;
946 }
947
948 5 Status::T Engine::startPollDir(U8 chanId,
949 U8 pollId,
950 const Fw::String& srcDir,
951 const Fw::String& dstDir,
952 Class::T cfdp_class,
953 U8 priority,
954 EntityId destEid,
955 U32 intervalSec) {
956 5 Status::T status = Cfdp::Status::SUCCESS;
957 5 CfdpPollDir* pd = nullptr;
958
959 5 FW_ASSERT(chanId < Cfdp::NumChannels, chanId, Cfdp::NumChannels);
960 5 FW_ASSERT(pollId < MaxPollingDirPerChan, pollId, MaxPollingDirPerChan);
961
962 // First check if the poll directory is already in use
963 5 pd = m_channels[chanId]->getPollDir(pollId);
964
2/2
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 1 times.
5 if (pd->enabled == Fw::Enabled::DISABLED) {
965 // Populate arguments
966 4 pd->intervalSec = intervalSec;
967 4 pd->priority = priority;
968 4 pd->cfdpClass = cfdp_class;
969 4 pd->destEid = destEid;
970 4 pd->srcDir = srcDir;
971 4 pd->dstDir = dstDir;
972
973 // Set timer and enable polling
974 4 pd->intervalTimer.setTimer(pd->intervalSec);
975 4 pd->enabled = Fw::Enabled::ENABLED;
976 } else {
977 // Poll directory slot already in use
978 1 this->m_manager->log_WARNING_LO_PollDirBusy(chanId, pollId);
979 1 status = Cfdp::Status::ERROR;
980 }
981
982 5 return status;
983 }
984
985 5 Status::T Engine::stopPollDir(U8 chanId, U8 pollId) {
986 5 Status::T status = Cfdp::Status::SUCCESS;
987 5 CfdpPollDir* pd = nullptr;
988
989 5 FW_ASSERT(chanId < Cfdp::NumChannels, chanId, Cfdp::NumChannels);
990 5 FW_ASSERT(pollId < MaxPollingDirPerChan, pollId, MaxPollingDirPerChan);
991
992 // Check if the poll directory is in use
993 5 pd = m_channels[chanId]->getPollDir(pollId);
994
2/2
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 1 times.
5 if (pd->enabled == Fw::Enabled::ENABLED) {
995 // Clear poll directory arguments
996 4 pd->intervalSec = 0;
997 4 pd->priority = 0;
998 4 pd->cfdpClass = static_cast<Class::T>(0);
999 4 pd->destEid = static_cast<EntityId>(0);
1000 4 pd->srcDir = "";
1001 4 pd->dstDir = "";
1002
1003 // Disable timer and polling
1004 4 pd->intervalTimer.disableTimer();
1005 4 pd->enabled = Fw::Enabled::DISABLED;
1006 } else {
1007 // Poll directory not active - cannot stop
1008 1 this->m_manager->log_WARNING_LO_PollDirNotActive(chanId, pollId);
1009 1 status = Cfdp::Status::ERROR;
1010 }
1011
1012 5 return status;
1013 }
1014
1015 2322 void Engine::cycle(void) {
1016 U32 i;
1017
1018
2/2
✓ Branch 0 taken 4644 times.
✓ Branch 1 taken 2322 times.
6966 for (i = 0; i < Cfdp::NumChannels; ++i) {
1019 4644 Channel* chan = m_channels[i];
1020 4644 FW_ASSERT(chan != nullptr);
1021
1022 4644 chan->resetOutgoingCounter();
1023
1024
1/2
✓ Branch 2 taken 4644 times.
✗ Branch 3 not taken.
4644 if (chan->getFlowState() == Cfdp::Flow::NOT_FROZEN) {
1025 // handle ticks before tx cycle. Do this because there may be a limited number of TX messages available
1026 // this cycle, and it's important to respond to class 2 ACK/NAK more than it is to send new filedata
1027 // PDUs.
1028
1029 // cycle all transactions (tick)
1030 4644 chan->tickTransactions();
1031
1032 // cycle the current tx transaction
1033 4644 chan->cycleTx();
1034
1035 4644 chan->processPlaybackDirectories();
1036 4644 chan->processPollingDirectories();
1037 }
1038 }
1039 2322 }
1040
1041 51 void Engine::finishTransaction(Transaction* txn, bool keep_history) {
1042
2/2
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 50 times.
51 if (txn->m_flags.com.q_index == QueueId::FREE) {
1043 1 this->m_manager->log_DIAGNOSTIC_ResetFreedTransaction();
1044 1 return;
1045 }
1046
1047 // A transaction in HOLD has already been finished and is waiting for its inactivity timer
1048 // to recycle it. Finishing it again would repeat the completion bookkeeping (the commanded
1049 // TX and playback counters, completion events, and the port notification), so do nothing.
1050
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 47 times.
50 if (txn->m_state == TxnState::TXN_STATE_HOLD) {
1051 3 return;
1052 }
1053
1054 // this should always be
1055 47 FW_ASSERT(txn->m_chan != nullptr);
1056
1057 // If this was on the TXA queue (transmit side) then we need to move it out
1058 // so the tick processor will stop trying to actively transmit something -
1059 // it should move on to the next transaction.
1060 //
1061 // RX transactions can stay on the RX queue, that does not hurt anything
1062 // because they are only triggered when a PDU comes in matching that seq_num
1063 // (RX queue is not separated into A/W parts)
1064
2/2
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 40 times.
47 if (txn->m_flags.com.q_index == QueueId::TXA) {
1065 7 txn->m_chan->dequeueTransaction(txn);
1066 7 txn->m_chan->insertSortPrio(txn, QueueId::TXW);
1067 }
1068
1069
2/2
✓ Branch 6 taken 32 times.
✓ Branch 7 taken 15 times.
47 if (true == txn->m_fd.isOpen()) {
1070 32 txn->m_fd.close();
1071
1072
3/4
✗ Branch 1 not taken.
✓ Branch 2 taken 32 times.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 27 times.
32 if (!txn->m_keep) {
1073 5 this->handleNotKeepFile(txn);
1074 }
1075 }
1076
1077
1/2
✓ Branch 2 taken 47 times.
✗ Branch 3 not taken.
47 if (txn->m_history != nullptr) {
1078 // Emit completion events for successful transactions
1079
2/2
✓ Branch 5 taken 27 times.
✓ Branch 6 taken 20 times.
47 if (!TxnStatusIsError(txn->m_history->txn_stat)) {
1080
2/2
✓ Branch 4 taken 13 times.
✓ Branch 5 taken 14 times.
27 if (txn->m_history->dir == Direction::DIRECTION_TX) {
1081
3/4
✗ Branch 16 not taken.
✓ Branch 17 taken 13 times.
✓ Branch 19 taken 13 times.
✓ Branch 23 taken 13 times.
78 this->m_manager->log_ACTIVITY_HI_TxFileTransferCompleted(
1082 26 txn->m_txn_class, txn->m_history->seq_num, txn->m_history->src_eid,
1083 39 txn->m_history->fnames.src_filename, txn->m_history->peer_eid, txn->m_history->fnames.dst_filename,
1084 13 static_cast<U32>(txn->m_fsize));
1085
1/2
✓ Branch 4 taken 14 times.
✗ Branch 5 not taken.
14 } else if (txn->m_history->dir == Direction::DIRECTION_RX) {
1086
3/4
✗ Branch 16 not taken.
✓ Branch 17 taken 14 times.
✓ Branch 19 taken 14 times.
✓ Branch 23 taken 14 times.
84 this->m_manager->log_ACTIVITY_HI_RxFileTransferCompleted(
1087 28 txn->m_txn_class, txn->m_history->seq_num, txn->m_history->src_eid,
1088 28 txn->m_history->fnames.src_filename, m_manager->getLocalEidParam(),
1089 14 txn->m_history->fnames.dst_filename, static_cast<U32>(txn->m_fsize));
1090 }
1091 } else {
1092 // Log failure events for failed transactions
1093
2/2
✓ Branch 4 taken 6 times.
✓ Branch 5 taken 14 times.
20 if (txn->m_history->dir == Direction::DIRECTION_TX) {
1094
3/4
✗ Branch 16 not taken.
✓ Branch 17 taken 6 times.
✓ Branch 19 taken 6 times.
✓ Branch 23 taken 6 times.
36 this->m_manager->log_WARNING_LO_TxFileTransferFailed(
1095 12 txn->m_txn_class, txn->m_history->seq_num, txn->m_history->src_eid,
1096 18 txn->m_history->fnames.src_filename, txn->m_history->peer_eid, txn->m_history->fnames.dst_filename,
1097 6 static_cast<U8>(txn->m_history->txn_stat));
1098
1/2
✓ Branch 4 taken 14 times.
✗ Branch 5 not taken.
14 } else if (txn->m_history->dir == Direction::DIRECTION_RX) {
1099
3/4
✗ Branch 16 not taken.
✓ Branch 17 taken 14 times.
✓ Branch 19 taken 14 times.
✓ Branch 23 taken 14 times.
84 this->m_manager->log_WARNING_LO_RxFileTransferFailed(
1100 28 txn->m_txn_class, txn->m_history->seq_num, txn->m_history->src_eid,
1101 42 txn->m_history->fnames.src_filename, txn->m_history->peer_eid, txn->m_history->fnames.dst_filename,
1102 14 static_cast<U8>(txn->m_history->txn_stat));
1103 }
1104 }
1105
1106 // extra bookkeeping for tx direction only
1107
5/6
✓ Branch 4 taken 19 times.
✓ Branch 5 taken 28 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 19 times.
✓ Branch 9 taken 14 times.
✓ Branch 10 taken 5 times.
47 if (txn->m_history->dir == Direction::DIRECTION_TX && txn->m_flags.tx.cmd_tx) {
1108 14 txn->m_chan->decrementCmdTxCounter();
1109 }
1110
1111 // Notify via port if this was a port-initiated transfer
1112
2/2
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 44 times.
47 if (txn->m_initType == TransactionInitType::INIT_BY_PORT) {
1113 // Map transaction status to SendFileStatus
1114 Svc::SendFileStatus::T status;
1115
1/2
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
3 if (TxnStatusIsError(txn->m_history->txn_stat)) {
1116 ✗ status = Svc::SendFileStatus::STATUS_ERROR;
1117 } else {
1118 3 status = Svc::SendFileStatus::STATUS_OK;
1119 }
1120
1121 // Invoke the file complete notification
1122 3 this->m_manager->sendFileComplete(status);
1123 }
1124
1125 47 txn->m_flags.com.keep_history = keep_history;
1126 }
1127
1128
2/2
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 46 times.
47 if (txn->m_pb) {
1129 // a playback's transaction is now done, decrement the playback counter
1130 1 FW_ASSERT(txn->m_pb->num_ts);
1131 1 --txn->m_pb->num_ts;
1132 }
1133
1134 47 txn->m_chan->clearCurrentIfMatch(txn);
1135
1136 // Put this transaction into the holdover state, inactivity timer will recycle it
1137 47 txn->m_state = TxnState::TXN_STATE_HOLD;
1138 47 this->armInactTimer(txn);
1139 }
1140
1141 47 void Engine::setTxnStatus(Transaction* txn, TxnStatus txn_stat) {
1142
2/2
✓ Branch 5 taken 44 times.
✓ Branch 6 taken 3 times.
47 if (!TxnStatusIsError(txn->m_history->txn_stat)) {
1143 44 txn->m_history->txn_stat = txn_stat;
1144 }
1145 47 }
1146
1147 1 void Engine::cancelTransaction(Transaction* txn) {
1148 1 void (Transaction::* fns[static_cast<U32>(Direction::DIRECTION_NUM)])() = {nullptr};
1149
1150 1 fns[static_cast<U32>(Direction::DIRECTION_RX)] = &Transaction::rCancel;
1151 1 fns[static_cast<U32>(Direction::DIRECTION_TX)] = &Transaction::sCancel;
1152
1153
2/4
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
1 if (!txn->m_flags.com.canceled) {
1154 1 txn->m_flags.com.canceled = true;
1155
1/1
✓ Branch 4 taken 1 times.
1 this->setTxnStatus(txn, TxnStatus::TXN_STATUS_CANCEL_REQUEST_RECEIVED);
1156
1157 // this should always be true, just confirming before indexing into array
1158
1/2
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 if (txn->m_history->dir < Direction::DIRECTION_NUM) {
1159
2/3
✗ Branch 16 not taken.
✓ Branch 17 taken 1 times.
✓ Branch 38 taken 1 times.
1 (txn->*fns[static_cast<U32>(txn->m_history->dir)])();
1160 }
1161 }
1162 1 }
1163
1164 2 bool Engine::isPollingDir(const Fw::StringBase& src_file, U8 chan_num) {
1165 2 bool return_code = false;
1166
1/1
✓ Branch 2 taken 2 times.
2 Fw::String src_dir;
1167 CfdpPollDir* pd;
1168 U32 i;
1169
1170 // Extract directory portion (everything before last '/')
1171 2 FwSizeType lastSlashPos = 0;
1172 2 bool foundSlash = false;
1173
3/3
✓ Branch 11 taken 68 times.
✓ Branch 13 taken 66 times.
✓ Branch 14 taken 2 times.
68 for (FwSizeType pos = 0; pos < src_file.length(); ++pos) {
1174
3/3
✓ Branch 11 taken 66 times.
✓ Branch 15 taken 6 times.
✓ Branch 16 taken 60 times.
66 if (src_file.toChar()[pos] == '/') {
1175 6 lastSlashPos = pos;
1176 6 foundSlash = true;
1177 }
1178 }
1179
1180
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (foundSlash) {
1181
2/2
✓ Branch 12 taken 2 times.
✓ Branch 15 taken 2 times.
2 src_dir.format("%.*s", static_cast<int>(lastSlashPos), src_file.toChar());
1182 }
1183
1184
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 for (i = 0; i < MaxPollingDirPerChan; ++i) {
1185
1/1
✓ Branch 7 taken 2 times.
2 pd = m_channels[chan_num]->getPollDir(i);
1186
2/3
✓ Branch 5 taken 2 times.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
2 if (src_dir == pd->srcDir) {
1187 2 return_code = true;
1188 2 break;
1189 }
1190 }
1191
1192 2 return return_code;
1193 2 }
1194
1195 8 void Engine::handleNotKeepFile(Transaction* txn) {
1196 8 Os::FileSystem::Status fileStatus = Os::FileSystem::OTHER_ERROR;
1197
1/1
✓ Branch 2 taken 8 times.
8 Fw::String failDir;
1198
1/1
✓ Branch 2 taken 8 times.
8 Fw::String moveDir;
1199
1200 // Sender
1201
2/2
✓ Branch 4 taken 7 times.
✓ Branch 5 taken 1 times.
8 if (txn->getHistory()->dir == Direction::DIRECTION_TX) {
1202
3/3
✓ Branch 5 taken 7 times.
✓ Branch 7 taken 6 times.
✓ Branch 8 taken 1 times.
7 if (!TxnStatusIsError(txn->getHistory()->txn_stat)) {
1203 // If move directory is defined attempt move
1204
2/2
✓ Branch 8 taken 6 times.
✓ Branch 13 taken 6 times.
6 moveDir = m_manager->getMoveDirParam(txn->getChannelId());
1205
3/3
✓ Branch 2 taken 6 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 5 times.
6 if (moveDir.length() > 0) {
1206
1/1
✓ Branch 10 taken 1 times.
1 fileStatus = Os::FileSystem::moveFile(txn->m_history->fnames.src_filename.toChar(), moveDir.toChar());
1207
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (fileStatus != Os::FileSystem::OP_OK) {
1208
1/1
✓ Branch 13 taken 1 times.
1 m_manager->log_WARNING_LO_FailKeepFileMove(txn->m_history->fnames.src_filename, moveDir,
1209 fileStatus);
1210 }
1211 }
1212
1213 // If move_dir is empty or move failed, delete the file
1214
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (fileStatus != Os::FileSystem::OP_OK) {
1215
1/1
✓ Branch 8 taken 6 times.
6 fileStatus = Os::FileSystem::removeFile(txn->m_history->fnames.src_filename.toChar());
1216
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (fileStatus != Os::FileSystem::OP_OK) {
1217 ✗ m_manager->log_WARNING_LO_FileRemoveFailed(txn->m_history->fnames.src_filename, fileStatus);
1218 }
1219 }
1220 } else {
1221 // file inside a polling directory
1222
2/3
✓ Branch 10 taken 1 times.
✓ Branch 12 taken 1 times.
✗ Branch 13 not taken.
1 if (this->isPollingDir(txn->m_history->fnames.src_filename, txn->getChannelId())) {
1223 // If fail directory is defined attempt move
1224
2/2
✓ Branch 8 taken 1 times.
✓ Branch 13 taken 1 times.
1 failDir = m_manager->getFailDirParam(txn->getChannelId());
1225
2/3
✓ Branch 2 taken 1 times.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 if (failDir.length() > 0) {
1226 fileStatus =
1227
1/1
✓ Branch 10 taken 1 times.
1 Os::FileSystem::moveFile(txn->m_history->fnames.src_filename.toChar(), failDir.toChar());
1228
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (fileStatus != Os::FileSystem::OP_OK) {
1229
1/1
✓ Branch 13 taken 1 times.
1 m_manager->log_WARNING_LO_FailPollFileMove(txn->m_history->fnames.src_filename, failDir,
1230 fileStatus);
1231 }
1232 }
1233
1234 // If fail_dir is empty or move failed, delete the file
1235
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (fileStatus != Os::FileSystem::OP_OK) {
1236
1/1
✓ Branch 8 taken 1 times.
1 fileStatus = Os::FileSystem::removeFile(txn->m_history->fnames.src_filename.toChar());
1237
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (fileStatus != Os::FileSystem::OP_OK) {
1238 ✗ m_manager->log_WARNING_LO_FileRemoveFailed(txn->m_history->fnames.src_filename, fileStatus);
1239 }
1240 }
1241 }
1242 }
1243 }
1244 // Not Sender
1245 else {
1246
1/1
✓ Branch 9 taken 1 times.
1 fileStatus = Os::FileSystem::removeFile(txn->m_history->fnames.dst_filename.toChar());
1247
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (fileStatus != Os::FileSystem::OP_OK) {
1248
1/1
✓ Branch 14 taken 1 times.
1 m_manager->log_WARNING_LO_FileRemoveFailed(txn->m_history->fnames.dst_filename, fileStatus);
1249 }
1250 }
1251 16 }
1252
1253 9348 Cfdp::ChannelTelemetry& Engine::getChannelTelemetryRef(U8 channelId) {
1254 9348 return this->m_manager->getChannelTelemetryRef(channelId);
1255 }
1256
1257 } // namespace Cfdp
1258 } // namespace Ccsds
1259 } // namespace Svc
1260