GCC Code Coverage Report


Directory: ./
File: Svc/Ccsds/CfdpManager/Engine.cpp
Date: 2026-09-03 22:12:29
Exec Total Coverage
Lines: 0 536 0.0%
Functions: 0 43 0.0%
Branches: 0 335 0.0%

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 Engine::Engine(CfdpManager* manager) : m_manager(manager), m_seqNum(0), m_allocator(nullptr), m_allocatorId(0) {
61 for (U8 i = 0; i < Cfdp::NumChannels; ++i) {
62 m_channels[i] = nullptr;
63 }
64 }
65
66 Engine::~Engine() {
67 FW_ASSERT(m_allocator != nullptr, 0); // init() must have been called
68
69 for (U8 i = 0; i < Cfdp::NumChannels; ++i) {
70 if (m_channels[i] != nullptr) {
71 // Clean up Channel's internal arrays first
72 m_channels[i]->cleanup(*m_allocator, m_allocatorId);
73
74 // Call destructor
75 m_channels[i]->~Channel();
76
77 // Deallocate the Channel object itself
78 m_allocator->deallocate(m_allocatorId, m_channels[i]);
79 m_channels[i] = nullptr;
80 }
81 }
82 }
83
84 // ----------------------------------------------------------------------
85 // Public interface methods
86 // ----------------------------------------------------------------------
87
88 void Engine::init(Fw::MemAllocator& allocator, FwEnumStoreType memId) {
89 // Store allocator for cleanup in destructor
90 m_allocator = &allocator;
91 m_allocatorId = memId;
92
93 // Allocate and construct all channels using the allocator
94 for (U8 i = 0; i < Cfdp::NumChannels; ++i) {
95 FwSizeType channelSize = sizeof(Channel);
96 void* channelMem = allocator.allocate(memId, channelSize);
97 FW_ASSERT(channelMem != nullptr);
98
99 // Use placement new to construct Channel in allocated memory
100 m_channels[i] = new (channelMem) Channel(this, i, this->m_manager, allocator, memId);
101 }
102 }
103
104 void Engine::armAckTimer(Transaction* txn) {
105 txn->m_ack_timer.setTimer(txn->m_cfdpManager->getAckTimerParam(txn->m_chan_num));
106 txn->m_flags.com.ack_timer_armed = true;
107 }
108
109 void Engine::armInactTimer(Transaction* txn) {
110 U32 timerDuration = 0;
111
112 // select timeout based on the state
113 if (GetTxnStatus(txn) == AckTxnStatus::ACK_TXN_STATUS_ACTIVE) {
114 // in an active transaction, we expect traffic so use the normal inactivity timer
115 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 timerDuration = txn->m_cfdpManager->getAckTimerParam(txn->m_chan_num) * 2;
125 }
126
127 txn->m_inactivity_timer.setTimer(timerDuration);
128 }
129
130 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 bool needsDispatch = true;
134 while (needsDispatch) {
135 needsDispatch = false; // Assume single dispatch unless state handler requests re-dispatch
136
137 // Dispatch based on transaction state
138 switch (txn->m_state) {
139 case TxnState::TXN_STATE_INIT:
140 needsDispatch = this->recvInit(txn, buffer);
141 break;
142 case TxnState::TXN_STATE_R1:
143 txn->r1Recv(buffer);
144 break;
145 case TxnState::TXN_STATE_S1:
146 txn->s1Recv(buffer);
147 break;
148 case TxnState::TXN_STATE_R2:
149 txn->r2Recv(buffer);
150 break;
151 case TxnState::TXN_STATE_S2:
152 txn->s2Recv(buffer);
153 break;
154 case TxnState::TXN_STATE_DROP:
155 this->recvDrop(txn, buffer);
156 break;
157 case TxnState::TXN_STATE_HOLD:
158 this->recvHold(txn, buffer);
159 break;
160 default:
161 // Invalid or undefined state
162 break;
163 }
164 }
165
166 this->armInactTimer(txn); // whenever a packet was received by the other size, always arm its inactivity timer
167 }
168
169 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 txn->txStateDispatch(&state_fns);
182 }
183
184 Status::T Engine::sendMd(Transaction* txn) {
185 FW_ASSERT((txn->m_state == TxnState::TXN_STATE_S1) || (txn->m_state == TxnState::TXN_STATE_S2),
186 static_cast<U8>(txn->m_state));
187 FW_ASSERT(txn->m_chan != nullptr);
188
189 // Create and initialize Metadata PDU
190 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 U8 closureRequested = (txn->m_state == TxnState::TXN_STATE_S2) ? 1 : 0;
195
196 // Direction is toward receiver for metadata PDU sent by sender
197 Cfdp::PduDirection direction = PduDirection::DIRECTION_TOWARD_RECEIVER;
198
199 md.initialize(direction,
200 txn->getClass(), // transmission mode (Class 1 or 2)
201 m_manager->getLocalEidParam(), // source EID
202 txn->m_history->seq_num, // transaction sequence number
203 txn->m_history->peer_eid, // destination EID
204 txn->m_fsize, // file size
205 txn->m_history->fnames.src_filename, // source filename
206 txn->m_history->fnames.dst_filename, // destination filename
207 ChecksumType::CHECKSUM_TYPE_MODULAR, // checksum type
208 closureRequested // closure requested flag
209 );
210
211 return serializeAndSendPdu(txn, md);
212 }
213
214 Status::T Engine::sendFd(Transaction* txn, FileDataPdu& fdPdu) {
215 Status::T status = serializeAndSendPdu(txn, fdPdu);
216 if (status == Cfdp::Status::SUCCESS) {
217 m_manager->addSentFileDataBytes(txn->getChannelId(), fdPdu.getDataSize());
218 }
219 return status;
220 }
221
222 Status::T Engine::sendEof(Transaction* txn) {
223 // Create and initialize EOF PDU
224 EofPdu eof;
225
226 // Direction is toward receiver for EOF sent by sender
227 Cfdp::PduDirection direction = PduDirection::DIRECTION_TOWARD_RECEIVER;
228 ConditionCode conditionCode = static_cast<ConditionCode>(TxnStatusToConditionCode(txn->m_history->txn_stat));
229
230 // Increment sent EOF counters based on condition code
231 if (conditionCode == ConditionCode::CONDITION_CODE_CANCEL_REQUEST_RECEIVED) {
232 this->m_manager->incrementSentEofCanceled(txn->getChannelId());
233 } else if (conditionCode != ConditionCode::CONDITION_CODE_NO_ERROR) {
234 this->m_manager->incrementFaultTxEofError(txn->getChannelId());
235 }
236
237 eof.initialize(direction,
238 txn->getClass(), // transmission mode
239 m_manager->getLocalEidParam(), // source EID
240 txn->m_history->seq_num, // transaction sequence number
241 txn->m_history->peer_eid, // destination EID
242 conditionCode, // condition code
243 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 if (conditionCode != ConditionCode::CONDITION_CODE_NO_ERROR) {
249 Cfdp::Tlv tlv;
250 tlv.initialize(m_manager->getLocalEidParam()); // Local entity ID
251 eof.appendTlv(tlv);
252 }
253
254 return serializeAndSendPdu(txn, eof);
255 }
256
257 Status::T Engine::sendAck(Transaction* txn,
258 AckTxnStatus ts,
259 FileDirective dir_code,
260 ConditionCode cc,
261 EntityId peer_eid,
262 TransactionSeq tsn) {
263 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 if (txn->getHistory()->dir == Direction::DIRECTION_TX) {
271 src_eid = m_manager->getLocalEidParam();
272 dst_eid = peer_eid;
273 } else {
274 src_eid = peer_eid;
275 dst_eid = m_manager->getLocalEidParam();
276 }
277
278 // Create and initialize ACK PDU
279 AckPdu ack;
280
281 // Direction: toward sender for EOF ACK, toward receiver for FIN ACK
282 Cfdp::PduDirection direction = (dir_code == FileDirective::FILE_DIRECTIVE_END_OF_FILE)
283 ? Cfdp::PduDirection::DIRECTION_TOWARD_SENDER
284 : Cfdp::PduDirection::DIRECTION_TOWARD_RECEIVER;
285
286 ack.initialize(direction,
287 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 return serializeAndSendPdu(txn, ack);
298 }
299
300 Status::T Engine::sendFin(Transaction* txn, FinDeliveryCode dc, FinFileStatus fs, ConditionCode cc) {
301 // Create and initialize FIN PDU
302 FinPdu fin;
303
304 // Direction is toward sender for FIN sent by receiver
305 Cfdp::PduDirection direction = PduDirection::DIRECTION_TOWARD_SENDER;
306
307 fin.initialize(direction,
308 txn->getClass(), // transmission mode
309 txn->m_history->peer_eid, // source EID (receiver)
310 txn->m_history->seq_num, // transaction sequence number
311 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 if (cc != ConditionCode::CONDITION_CODE_NO_ERROR) {
319 Cfdp::Tlv tlv;
320 tlv.initialize(m_manager->getLocalEidParam()); // Local entity ID
321 fin.appendTlv(tlv);
322 }
323
324 return serializeAndSendPdu(txn, fin);
325 }
326
327 Status::T Engine::sendNak(Transaction* txn, NakPdu& nakPdu) {
328 // Verify this is a Class 2 transaction (NAK only used in Class 2)
329 Class::T tx_class = txn->getClass();
330 FW_ASSERT(tx_class == Cfdp::Class::CLASS_2, tx_class);
331
332 return serializeAndSendPdu(txn, nakPdu);
333 }
334
335 Status::T Engine::serializeAndSendPdu(Transaction* txn, PduBase& pdu) {
336 // Delegate to the channel-based helper; a transaction always carries its channel.
337 return this->serializeAndSendPduOnChannel(*txn->m_chan, pdu);
338 }
339
340 Status::T Engine::serializeAndSendPduOnChannel(Channel& chan, PduBase& pdu) {
341 Fw::Buffer buffer;
342 Status::T status = Cfdp::Status::SUCCESS;
343
344 // Allocate buffer with space for packet descriptor
345 const FwSizeType bufferSize = pdu.getBufferSize() + CfdpManager::PACKET_DESCRIPTOR_SIZE;
346 status = m_manager->getPduBuffer(buffer, chan, bufferSize);
347
348 if (status == Cfdp::Status::SUCCESS) {
349 // Serialize to buffer at offset to leave room for descriptor
350 Fw::SerialBuffer sb(buffer.getData() + CfdpManager::PACKET_DESCRIPTOR_SIZE,
351 buffer.getSize() - CfdpManager::PACKET_DESCRIPTOR_SIZE);
352 Fw::SerializeStatus serStatus = pdu.serializeTo(sb);
353
354 if (serStatus != Fw::FW_SERIALIZE_OK) {
355 // Log generic PDU serialization error with PDU type
356 m_manager->log_WARNING_LO_FailPduSerialization(chan.getChannelId(), pdu.getType(),
357 static_cast<I32>(serStatus));
358 m_manager->returnPduBuffer(chan, buffer);
359 status = Cfdp::Status::ERROR;
360 } else {
361 // Update buffer size to actual serialized size plus descriptor
362 buffer.setSize(sb.getSize() + CfdpManager::PACKET_DESCRIPTOR_SIZE);
363 m_manager->sendPduBuffer(chan, buffer);
364 m_manager->incrementSentPdu(chan.getChannelId());
365 }
366 }
367
368 return status;
369 }
370
371 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 AckPdu ack;
386 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 return this->serializeAndSendPduOnChannel(chan, ack);
398 }
399
400 void Engine::recvMd(Transaction* txn, const MetadataPdu& md) {
401 /* store the expected file size in transaction */
402 txn->m_fsize = md.getFileSize();
403
404 /* store the filenames in transaction - validation already done during deserialization */
405 txn->m_history->fnames.src_filename = md.getSourceFilename();
406 txn->m_history->fnames.dst_filename = md.getDestFilename();
407
408 this->m_manager->log_ACTIVITY_LO_MetadataReceived(txn->m_history->fnames.src_filename,
409 txn->m_history->fnames.dst_filename, txn->m_history->seq_num);
410 }
411
412 Status::T Engine::recvFd(Transaction* txn, const FileDataPdu& fd) {
413 Status::T ret = Cfdp::Status::SUCCESS;
414
415 // Extract header
416 const Cfdp::PduHeader& header = fd.asHeader();
417
418 // Check for segment metadata flag (not currently supported)
419 if (header.hasSegmentMetadata()) {
420 /* If recv PDU has the "segment_meta_flag" set, this is not currently handled in CF. */
421 this->m_manager->log_WARNING_LO_FileDataSegmentMetadata();
422 this->setTxnStatus(txn, TxnStatus::TXN_STATUS_PROTOCOL_ERROR);
423 this->m_manager->incrementRecvErrors(txn->getChannelId());
424 ret = Cfdp::Status::ERROR;
425 }
426
427 return ret;
428 }
429
430 Status::T Engine::recvEof(Transaction* txn, const EofPdu& eofPdu) {
431 // EOF PDU has been validated during fromBuffer()
432
433 // Process TLVs if present
434 const Cfdp::TlvList& tlvList = eofPdu.getTlvList();
435 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 return Cfdp::Status::SUCCESS;
445 }
446
447 Status::T Engine::recvFin(Transaction* txn, const FinPdu& finPdu) {
448 // FIN PDU has been validated during fromBuffer()
449
450 // Process TLVs if present
451 const Cfdp::TlvList& tlvList = finPdu.getTlvList();
452 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 return Cfdp::Status::SUCCESS;
462 }
463
464 Status::T Engine::recvNak(Transaction* txn, const NakPdu& pdu) {
465 // NAK PDU has been validated during fromBuffer()
466 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 void Engine::recvHold(Transaction* txn, const Fw::Buffer& buffer) {
475 // anything received in this state is considered spurious
476 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 Cfdp::PduTypeEnum::T pduType = Cfdp::peekPduType(buffer);
494
495 // Check if this is a FIN PDU for a Class 2 transaction
496 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 }
512
513 bool Engine::recvInit(Transaction* txn, const Fw::Buffer& buffer) {
514 // Use peekPduType to determine the PDU type before deserializing
515 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 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
520 sb.setBuffLen(buffer.getSize());
521
522 Cfdp::PduHeader header;
523 Fw::SerializeStatus status = header.fromSerialBuffer(sb);
524
525 if (status == Fw::FW_SERIALIZE_OK) {
526 TransactionSeq transactionSeq = header.getTransactionSeq();
527 EntityId sourceEid = header.getSourceEid();
528 Class::T txmMode = header.getTxmMode();
529
530 // only RX transactions dare tread here
531 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 txn->m_history->peer_eid = sourceEid;
536 txn->m_history->src_eid = sourceEid;
537
538 // all RX transactions will need a chunk list to track file segments
539 if (txn->m_chunks == nullptr) {
540 txn->m_chunks = txn->m_chan->findUnusedChunks(Direction::DIRECTION_RX);
541 }
542 if (txn->m_chunks == nullptr) {
543 this->m_manager->log_WARNING_LO_ChunklistUnavailable(transactionSeq);
544 } else {
545 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 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 txn->m_state = TxnState::TXN_STATE_R2;
557 txn->m_txn_class = Cfdp::Class::CLASS_2;
558 txn->rInit();
559 return true; // Request re-dispatch to enter r2 state handler
560 }
561 } else if (pduType == Cfdp::PduTypeEnum::METADATA) {
562 // file directive PDU with metadata - this is the expected case for starting a new RX transaction
563 MetadataPdu md;
564 Fw::SerialBuffer sb2(const_cast<U8*>(buffer.getData()), buffer.getSize());
565 sb2.setBuffLen(buffer.getSize());
566
567 Fw::SerializeStatus deserStatus = md.deserializeFrom(sb2);
568 if (deserStatus == Fw::FW_SERIALIZE_OK) {
569 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 txn->m_state = txmMode == Cfdp::Class::CLASS_1 ? TxnState::TXN_STATE_R1 : TxnState::TXN_STATE_R2;
573 txn->m_txn_class = txmMode;
574 txn->m_flags.rx.md_recv = true;
575 txn->rInit(); // initialize R
576 } else {
577 m_manager->log_WARNING_LO_FailMetadataPduDeserialization(txn->getChannelId(),
578 static_cast<I32>(deserStatus));
579 }
580 } else {
581 // Unexpected PDU type in init state
582 this->m_manager->log_WARNING_LO_UnhandledPduInIdleState();
583 this->m_manager->incrementRecvErrors(txn->getChannelId());
584 }
585 }
586
587 if (txn->m_state == TxnState::TXN_STATE_INIT) {
588 // state was not changed, so free the transaction
589 this->finishTransaction(txn, false);
590 }
591 } else {
592 m_manager->log_WARNING_LO_FailPduHeaderDeserialization(txn->getChannelId(), status);
593 }
594 return false; // No re-dispatch needed
595 }
596
597 void Engine::receivePdu(U8 chan_id, const Fw::Buffer& buffer) {
598 Transaction* txn = nullptr;
599 Channel* chan = nullptr;
600
601 FW_ASSERT(chan_id < Cfdp::NumChannels, chan_id, Cfdp::NumChannels);
602
603 chan = m_channels[chan_id];
604 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 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
610 sb.setBuffLen(buffer.getSize());
611
612 Cfdp::PduHeader header;
613 Fw::SerializeStatus status = header.fromSerialBuffer(sb);
614
615 if (status == Fw::FW_SERIALIZE_OK) {
616 // Increment received PDU counter for PDUs with valid headers
617 this->m_manager->incrementRecvPdu(chan_id);
618
619 TransactionSeq transactionSeq = header.getTransactionSeq();
620 EntityId sourceEid = header.getSourceEid();
621 EntityId destEid = header.getDestEid();
622
623 // Look up transaction by sequence number
624 txn = chan->findTransactionBySequenceNumber(transactionSeq, sourceEid);
625
626 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 if (Cfdp::peekPduType(buffer) == Cfdp::PduTypeEnum::FINISHED &&
634 sourceEid == this->m_manager->getLocalEidParam()) {
635 FinPdu fin;
636 Fw::SerialBuffer finSb(const_cast<U8*>(buffer.getData()), buffer.getSize());
637 finSb.setBuffLen(buffer.getSize());
638 if (fin.deserializeFrom(finSb) == Fw::FW_SERIALIZE_OK) {
639 this->sendFinAckStateless(*chan, transactionSeq, sourceEid, destEid, fin.getConditionCode());
640 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 }
646 // if no match found, then it must be the case that we would be the destination entity id, so verify it
647 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 txn = this->startRxTransaction(chan->getChannelId());
651 if (txn == nullptr) {
652 this->m_manager->log_WARNING_LO_RxTransactionLimitReached(sourceEid, transactionSeq);
653 }
654 } else {
655 this->m_manager->log_WARNING_LO_InvalidDestinationEid(destEid);
656 }
657 }
658
659 if (txn != nullptr) {
660 // found one! Send it to the transaction state processor
661 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 m_manager->log_WARNING_LO_FailPduHeaderDeserialization(chan_id, static_cast<I32>(status));
668 }
669 }
670
671 void Engine::setChannelFlowState(U8 channelId, Flow::T flowState) {
672 FW_ASSERT(channelId < Cfdp::NumChannels, channelId, Cfdp::NumChannels);
673 m_channels[channelId]->setFlowState(flowState);
674 }
675
676 Status::T Engine::setSuspendResumeTransaction(U8 channelId,
677 TransactionSeq transactionSeq,
678 EntityId entityId,
679 SuspendResume::T action) {
680 Status::T status = Status::ERROR;
681
682 FW_ASSERT(channelId < Cfdp::NumChannels, channelId, Cfdp::NumChannels);
683
684 Channel* chan = m_channels[channelId];
685 Transaction* txn = chan->findTransactionBySequenceNumber(transactionSeq, entityId);
686
687 if (txn != nullptr) {
688 txn->m_flags.com.suspended = (action == SuspendResume::SUSPEND);
689 status = Status::SUCCESS;
690 }
691
692 return status;
693 }
694
695 Status::T Engine::cancelTransactionBySeq(U8 channelId, TransactionSeq transactionSeq, EntityId entityId) {
696 Status::T status = Status::ERROR;
697
698 FW_ASSERT(channelId < Cfdp::NumChannels, channelId, Cfdp::NumChannels);
699
700 Channel* chan = m_channels[channelId];
701 Transaction* txn = chan->findTransactionBySequenceNumber(transactionSeq, entityId);
702
703 if (txn != nullptr) {
704 this->cancelTransaction(txn);
705 status = Status::SUCCESS;
706 }
707
708 return status;
709 }
710
711 Status::T Engine::abandonTransaction(U8 channelId, TransactionSeq transactionSeq, EntityId entityId) {
712 Status::T status = Status::ERROR;
713
714 FW_ASSERT(channelId < Cfdp::NumChannels, channelId, Cfdp::NumChannels);
715
716 Channel* chan = m_channels[channelId];
717 Transaction* txn = chan->findTransactionBySequenceNumber(transactionSeq, entityId);
718
719 if (txn != nullptr) {
720 this->finishTransaction(txn, false);
721 status = Status::SUCCESS;
722 }
723
724 return status;
725 }
726
727 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 txn->initTxFile(cfdp_class, keep, chan, priority);
734
735 // Increment sequence number for new transaction
736 ++this->m_seqNum;
737
738 // Capture info for history
739 txn->m_history->seq_num = this->m_seqNum;
740 txn->m_history->src_eid = m_manager->getLocalEidParam();
741 txn->m_history->peer_eid = dest_id;
742
743 txn->m_chan->insertSortPrio(txn, QueueId::PEND);
744 }
745
746 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 Channel* chan = nullptr;
756
757 FW_ASSERT(chan_num < Cfdp::NumChannels, chan_num, Cfdp::NumChannels);
758 chan = m_channels[chan_num];
759
760 Status::T ret = Cfdp::Status::SUCCESS;
761
762 if (chan->getNumCmdTx() < MaxCommandedPlaybackFilesPerChan) {
763 txn = chan->findUnusedTransaction(Direction::DIRECTION_TX);
764 } else {
765 txn = nullptr;
766 }
767
768 if (txn == nullptr) {
769 this->m_manager->log_WARNING_LO_MaxTxTransactionsReached();
770 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 txn->m_history->fnames.src_filename = src_filename;
775 txn->m_history->fnames.dst_filename = dst_filename;
776 this->txFileInitiate(txn, cfdp_class, keep, chan_num, priority, dest_id);
777
778 chan->incrementCmdTxCounter();
779 txn->m_flags.tx.cmd_tx = true;
780
781 // Set transaction initiation type
782 txn->m_initType = initType;
783
784 // Log transaction queued event
785 this->m_manager->log_ACTIVITY_LO_TxFileQueued(txn->m_history->fnames.src_filename, txn->m_history->seq_num);
786 }
787
788 return ret;
789 }
790
791 Transaction* Engine::startRxTransaction(U8 chan_num) {
792 Channel* chan = nullptr;
793 Transaction* txn;
794
795 FW_ASSERT(chan_num < Cfdp::NumChannels, chan_num, Cfdp::NumChannels);
796 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 txn = chan->findUnusedTransaction(Direction::DIRECTION_RX);
808
809 if (txn != nullptr) {
810 // set default FIN status
811 txn->m_state_data.receive.r2.dc = FinDeliveryCode::FIN_DELIVERY_CODE_INCOMPLETE;
812 txn->m_state_data.receive.r2.fs = FinFileStatus::FIN_FILE_STATUS_DISCARDED;
813
814 txn->m_flags.com.q_index = QueueId::RX;
815 chan->insertBackInQueue(static_cast<QueueId::T>(txn->m_flags.com.q_index), &txn->m_cl_node);
816 }
817
818 return txn;
819 }
820
821 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 Status::T status = Cfdp::Status::SUCCESS;
830 Os::Directory::Status dirStatus;
831
832 // make sure the directory can be open
833 dirStatus = pb->dir.open(src_filename.toChar(), Os::Directory::READ);
834 if (dirStatus != Os::Directory::OP_OK) {
835 this->m_manager->log_WARNING_LO_PlaybackDirOpenFailed(src_filename, dirStatus);
836 this->m_manager->incrementFaultDirectoryRead(chan);
837 status = Cfdp::Status::ERROR;
838 } else {
839 pb->diropen = true;
840 pb->busy = true;
841 pb->keep = keep;
842 pb->priority = priority;
843 pb->dest_id = dest_id;
844 pb->cfdp_class = cfdp_class;
845
846 // NOTE: the caller of this function ensures the provided src and dst filenames are nullptr terminated
847 pb->fnames.src_filename = src_filename;
848 pb->fnames.dst_filename = dst_filename;
849 }
850
851 // the executor will start the transfer next cycle
852 return status;
853 }
854
855 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 for (i = 0; i < MaxCommandedPlaybackDirectoriesPerChan; ++i) {
868 pb = m_channels[chan]->getPlayback(i);
869 if (!pb->busy) {
870 break;
871 }
872 }
873
874 if (i == MaxCommandedPlaybackDirectoriesPerChan) {
875 this->m_manager->log_WARNING_LO_PlaybackDirSlotUnavailable();
876 status = Cfdp::Status::ERROR;
877 } else {
878 status = this->playbackDirInitiate(pb, src_filename, dst_filename, cfdp_class, keep, chan, priority, dest_id);
879 }
880
881 return status;
882 }
883
884 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 Status::T status = Cfdp::Status::SUCCESS;
893 CfdpPollDir* pd = nullptr;
894
895 FW_ASSERT(chanId < Cfdp::NumChannels, chanId, Cfdp::NumChannels);
896 FW_ASSERT(pollId < MaxPollingDirPerChan, pollId, MaxPollingDirPerChan);
897
898 // First check if the poll directory is already in use
899 pd = m_channels[chanId]->getPollDir(pollId);
900 if (pd->enabled == Fw::Enabled::DISABLED) {
901 // Populate arguments
902 pd->intervalSec = intervalSec;
903 pd->priority = priority;
904 pd->cfdpClass = cfdp_class;
905 pd->destEid = destEid;
906 pd->srcDir = srcDir;
907 pd->dstDir = dstDir;
908
909 // Set timer and enable polling
910 pd->intervalTimer.setTimer(pd->intervalSec);
911 pd->enabled = Fw::Enabled::ENABLED;
912 } else {
913 // Poll directory slot already in use
914 this->m_manager->log_WARNING_LO_PollDirBusy(chanId, pollId);
915 status = Cfdp::Status::ERROR;
916 }
917
918 return status;
919 }
920
921 Status::T Engine::stopPollDir(U8 chanId, U8 pollId) {
922 Status::T status = Cfdp::Status::SUCCESS;
923 CfdpPollDir* pd = nullptr;
924
925 FW_ASSERT(chanId < Cfdp::NumChannels, chanId, Cfdp::NumChannels);
926 FW_ASSERT(pollId < MaxPollingDirPerChan, pollId, MaxPollingDirPerChan);
927
928 // Check if the poll directory is in use
929 pd = m_channels[chanId]->getPollDir(pollId);
930 if (pd->enabled == Fw::Enabled::ENABLED) {
931 // Clear poll directory arguments
932 pd->intervalSec = 0;
933 pd->priority = 0;
934 pd->cfdpClass = static_cast<Class::T>(0);
935 pd->destEid = static_cast<EntityId>(0);
936 pd->srcDir = "";
937 pd->dstDir = "";
938
939 // Disable timer and polling
940 pd->intervalTimer.disableTimer();
941 pd->enabled = Fw::Enabled::DISABLED;
942 } else {
943 // Poll directory not active - cannot stop
944 this->m_manager->log_WARNING_LO_PollDirNotActive(chanId, pollId);
945 status = Cfdp::Status::ERROR;
946 }
947
948 return status;
949 }
950
951 void Engine::cycle(void) {
952 U32 i;
953
954 for (i = 0; i < Cfdp::NumChannels; ++i) {
955 Channel* chan = m_channels[i];
956 FW_ASSERT(chan != nullptr);
957
958 chan->resetOutgoingCounter();
959
960 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 chan->tickTransactions();
967
968 // cycle the current tx transaction
969 chan->cycleTx();
970
971 chan->processPlaybackDirectories();
972 chan->processPollingDirectories();
973 }
974 }
975 }
976
977 void Engine::finishTransaction(Transaction* txn, bool keep_history) {
978 if (txn->m_flags.com.q_index == QueueId::FREE) {
979 this->m_manager->log_DIAGNOSTIC_ResetFreedTransaction();
980 return;
981 }
982
983 // this should always be
984 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 if (txn->m_flags.com.q_index == QueueId::TXA) {
994 txn->m_chan->dequeueTransaction(txn);
995 txn->m_chan->insertSortPrio(txn, QueueId::TXW);
996 }
997
998 if (true == txn->m_fd.isOpen()) {
999 txn->m_fd.close();
1000
1001 if (!txn->m_keep) {
1002 this->handleNotKeepFile(txn);
1003 }
1004 }
1005
1006 if (txn->m_history != nullptr) {
1007 // Emit completion events for successful transactions
1008 if (!TxnStatusIsError(txn->m_history->txn_stat)) {
1009 if (txn->m_history->dir == Direction::DIRECTION_TX) {
1010 this->m_manager->log_ACTIVITY_HI_TxFileTransferCompleted(
1011 txn->m_txn_class, txn->m_history->seq_num, txn->m_history->src_eid,
1012 txn->m_history->fnames.src_filename, txn->m_history->peer_eid, txn->m_history->fnames.dst_filename,
1013 static_cast<U32>(txn->m_fsize));
1014 } else if (txn->m_history->dir == Direction::DIRECTION_RX) {
1015 this->m_manager->log_ACTIVITY_HI_RxFileTransferCompleted(
1016 txn->m_txn_class, txn->m_history->seq_num, txn->m_history->src_eid,
1017 txn->m_history->fnames.src_filename, m_manager->getLocalEidParam(),
1018 txn->m_history->fnames.dst_filename, static_cast<U32>(txn->m_fsize));
1019 }
1020 } else {
1021 // Log failure events for failed transactions
1022 if (txn->m_history->dir == Direction::DIRECTION_TX) {
1023 this->m_manager->log_WARNING_LO_TxFileTransferFailed(
1024 txn->m_txn_class, txn->m_history->seq_num, txn->m_history->src_eid,
1025 txn->m_history->fnames.src_filename, txn->m_history->peer_eid, txn->m_history->fnames.dst_filename,
1026 static_cast<U8>(txn->m_history->txn_stat));
1027 } else if (txn->m_history->dir == Direction::DIRECTION_RX) {
1028 this->m_manager->log_WARNING_LO_RxFileTransferFailed(
1029 txn->m_txn_class, txn->m_history->seq_num, txn->m_history->src_eid,
1030 txn->m_history->fnames.src_filename, txn->m_history->peer_eid, txn->m_history->fnames.dst_filename,
1031 static_cast<U8>(txn->m_history->txn_stat));
1032 }
1033 }
1034
1035 // extra bookkeeping for tx direction only
1036 if (txn->m_history->dir == Direction::DIRECTION_TX && txn->m_flags.tx.cmd_tx) {
1037 txn->m_chan->decrementCmdTxCounter();
1038 }
1039
1040 // Notify via port if this was a port-initiated transfer
1041 if (txn->m_initType == TransactionInitType::INIT_BY_PORT) {
1042 // Map transaction status to SendFileStatus
1043 Svc::SendFileStatus::T status;
1044 if (TxnStatusIsError(txn->m_history->txn_stat)) {
1045 status = Svc::SendFileStatus::STATUS_ERROR;
1046 } else {
1047 status = Svc::SendFileStatus::STATUS_OK;
1048 }
1049
1050 // Invoke the file complete notification
1051 this->m_manager->sendFileComplete(status);
1052 }
1053
1054 txn->m_flags.com.keep_history = keep_history;
1055 }
1056
1057 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 txn->m_chan->clearCurrentIfMatch(txn);
1064
1065 // Put this transaction into the holdover state, inactivity timer will recycle it
1066 txn->m_state = TxnState::TXN_STATE_HOLD;
1067 this->armInactTimer(txn);
1068 }
1069
1070 void Engine::setTxnStatus(Transaction* txn, TxnStatus txn_stat) {
1071 if (!TxnStatusIsError(txn->m_history->txn_stat)) {
1072 txn->m_history->txn_stat = txn_stat;
1073 }
1074 }
1075
1076 void Engine::cancelTransaction(Transaction* txn) {
1077 void (Transaction::* fns[static_cast<U32>(Direction::DIRECTION_NUM)])() = {nullptr};
1078
1079 fns[static_cast<U32>(Direction::DIRECTION_RX)] = &Transaction::rCancel;
1080 fns[static_cast<U32>(Direction::DIRECTION_TX)] = &Transaction::sCancel;
1081
1082 if (!txn->m_flags.com.canceled) {
1083 txn->m_flags.com.canceled = true;
1084 this->setTxnStatus(txn, TxnStatus::TXN_STATUS_CANCEL_REQUEST_RECEIVED);
1085
1086 // this should always be true, just confirming before indexing into array
1087 if (txn->m_history->dir < Direction::DIRECTION_NUM) {
1088 (txn->*fns[static_cast<U32>(txn->m_history->dir)])();
1089 }
1090 }
1091 }
1092
1093 bool Engine::isPollingDir(const Fw::StringBase& src_file, U8 chan_num) {
1094 bool return_code = false;
1095 Fw::String src_dir;
1096 CfdpPollDir* pd;
1097 U32 i;
1098
1099 // Extract directory portion (everything before last '/')
1100 FwSizeType lastSlashPos = 0;
1101 bool foundSlash = false;
1102 for (FwSizeType pos = 0; pos < src_file.length(); ++pos) {
1103 if (src_file.toChar()[pos] == '/') {
1104 lastSlashPos = pos;
1105 foundSlash = true;
1106 }
1107 }
1108
1109 if (foundSlash) {
1110 src_dir.format("%.*s", static_cast<int>(lastSlashPos), src_file.toChar());
1111 }
1112
1113 for (i = 0; i < MaxPollingDirPerChan; ++i) {
1114 pd = m_channels[chan_num]->getPollDir(i);
1115 if (src_dir == pd->srcDir) {
1116 return_code = true;
1117 break;
1118 }
1119 }
1120
1121 return return_code;
1122 }
1123
1124 void Engine::handleNotKeepFile(Transaction* txn) {
1125 Os::FileSystem::Status fileStatus = Os::FileSystem::OTHER_ERROR;
1126 Fw::String failDir;
1127 Fw::String moveDir;
1128
1129 // Sender
1130 if (txn->getHistory()->dir == Direction::DIRECTION_TX) {
1131 if (!TxnStatusIsError(txn->getHistory()->txn_stat)) {
1132 // If move directory is defined attempt move
1133 moveDir = m_manager->getMoveDirParam(txn->getChannelId());
1134 if (moveDir.length() > 0) {
1135 fileStatus = Os::FileSystem::moveFile(txn->m_history->fnames.src_filename.toChar(), moveDir.toChar());
1136 if (fileStatus != Os::FileSystem::OP_OK) {
1137 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 if (fileStatus != Os::FileSystem::OP_OK) {
1144 fileStatus = Os::FileSystem::removeFile(txn->m_history->fnames.src_filename.toChar());
1145 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 if (this->isPollingDir(txn->m_history->fnames.src_filename, txn->getChannelId())) {
1152 // If fail directory is defined attempt move
1153 failDir = m_manager->getFailDirParam(txn->getChannelId());
1154 if (failDir.length() > 0) {
1155 fileStatus =
1156 Os::FileSystem::moveFile(txn->m_history->fnames.src_filename.toChar(), failDir.toChar());
1157 if (fileStatus != Os::FileSystem::OP_OK) {
1158 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 if (fileStatus != Os::FileSystem::OP_OK) {
1165 fileStatus = Os::FileSystem::removeFile(txn->m_history->fnames.src_filename.toChar());
1166 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 fileStatus = Os::FileSystem::removeFile(txn->m_history->fnames.dst_filename.toChar());
1176 if (fileStatus != Os::FileSystem::OP_OK) {
1177 m_manager->log_WARNING_LO_FileRemoveFailed(txn->m_history->fnames.dst_filename, fileStatus);
1178 }
1179 }
1180 }
1181
1182 Cfdp::ChannelTelemetry& Engine::getChannelTelemetryRef(U8 channelId) {
1183 return this->m_manager->getChannelTelemetryRef(channelId);
1184 }
1185
1186 } // namespace Cfdp
1187 } // namespace Ccsds
1188 } // namespace Svc
1189