GCC Code Coverage Report


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