GCC Code Coverage Report


Directory: ./
File: TransactionTx.cpp
Date: 2026-09-03 22:13:22
Exec Total Coverage
Lines: 0 351 0.0%
Functions: 0 27 0.0%
Branches: 0 288 0.0%

Line Branch Exec Source
1 // ======================================================================
2 // \title TransactionTx.cpp
3 // \brief cpp file for CFDP TX Transaction state machine
4 //
5 // This file is a port of TX transaction state machine 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_s.c (send-file transaction state handling routines)
9 // - cf_cfdp_dispatch.c (TX state machine dispatch functions)
10 //
11 // This file contains various state handling routines for
12 // transactions which are sending a file, as well as dispatch
13 // functions for TX state machines and top-level transaction dispatch.
14 //
15 // ======================================================================
16 //
17 // NASA Docket No. GSC-18,447-1
18 //
19 // Copyright (c) 2019 United States Government as represented by the
20 // Administrator of the National Aeronautics and Space Administration.
21 // All Rights Reserved.
22 //
23 // Licensed under the Apache License, Version 2.0 (the "License"); you may
24 // not use this file except in compliance with the License. You may obtain
25 // a copy of the License at
26 //
27 // http://www.apache.org/licenses/LICENSE-2.0
28 //
29 // Unless required by applicable law or agreed to in writing, software
30 // distributed under the License is distributed on an "AS IS" BASIS,
31 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
32 // See the License for the specific language governing permissions and
33 // limitations under the License.
34 //
35 // ======================================================================
36
37 #include <stdio.h>
38 #include <string.h>
39
40 #include <Svc/Ccsds/CfdpManager/CfdpManager.hpp>
41 #include <Svc/Ccsds/CfdpManager/Channel.hpp>
42 #include <Svc/Ccsds/CfdpManager/Chunk.hpp>
43 #include <Svc/Ccsds/CfdpManager/Engine.hpp>
44 #include <Svc/Ccsds/CfdpManager/Timer.hpp>
45 #include <Svc/Ccsds/CfdpManager/Transaction.hpp>
46 #include <Svc/Ccsds/CfdpManager/Utils.hpp>
47
48 namespace Svc {
49 namespace Ccsds {
50 namespace Cfdp {
51
52 // ======================================================================
53 // TX State Machine - Private Helper (anonymous namespace)
54 // ======================================================================
55
56 namespace {
57
58 // Helper to build dispatch tables
59 FileDirectiveDispatchTable makeFileDirectiveTable(StateRecvFunc fin, StateRecvFunc ack, StateRecvFunc nak) {
60 FileDirectiveDispatchTable table = {};
61 memset(&table, 0, sizeof(table));
62
63 table.fdirective[static_cast<U32>(FileDirective::FILE_DIRECTIVE_FIN)] = fin;
64 table.fdirective[static_cast<U32>(FileDirective::FILE_DIRECTIVE_ACK)] = ack;
65 table.fdirective[static_cast<U32>(FileDirective::FILE_DIRECTIVE_NAK)] = nak;
66
67 return table;
68 }
69
70 } // anonymous namespace
71
72 // ======================================================================
73 // TX State Machine - Public Methods
74 // ======================================================================
75
76 void Transaction::s1Recv(const Fw::Buffer& buffer) {
77 // s1 doesn't need to receive anything
78 static const SSubstateRecvDispatchTable substate_fns = {{nullptr}};
79 this->sDispatchRecv(buffer, &substate_fns);
80 }
81
82 void Transaction::s2Recv(const Fw::Buffer& buffer) {
83 static const FileDirectiveDispatchTable s2_meta =
84 makeFileDirectiveTable(&Transaction::s2EarlyFin, nullptr, nullptr);
85
86 static const FileDirectiveDispatchTable s2_fd_or_eof =
87 makeFileDirectiveTable(&Transaction::s2EarlyFin, nullptr, &Transaction::s2Nak);
88
89 static const FileDirectiveDispatchTable s2_wait_ack =
90 makeFileDirectiveTable(&Transaction::s2Fin, &Transaction::s2EofAck, &Transaction::s2NakArm);
91
92 static const SSubstateRecvDispatchTable substate_fns = {{
93 &s2_meta, /* TxSubState::TX_SUB_STATE_METADATA */
94 &s2_fd_or_eof, /* TxSubState::TX_SUB_STATE_FILEDATA */
95 &s2_fd_or_eof, /* TxSubState::TX_SUB_STATE_EOF */
96 &s2_wait_ack /* TxSubState::TX_SUB_STATE_CLOSEOUT_SYNC */
97 }};
98
99 this->sDispatchRecv(buffer, &substate_fns);
100 }
101
102 void Transaction::initTxFile(Class::T cfdp_class, Keep::T keep, U8 chan, U8 priority) {
103 m_chan_num = chan;
104 m_priority = priority;
105 m_keep = keep;
106 m_txn_class = cfdp_class;
107 m_state = (cfdp_class == Cfdp::Class::CLASS_2) ? TxnState::TXN_STATE_S2 : TxnState::TXN_STATE_S1;
108 m_state_data.send.sub_state = TxSubState::TX_SUB_STATE_METADATA;
109 }
110
111 void Transaction::s1Tx() {
112 static const SSubstateSendDispatchTable substate_fns = {{
113 &Transaction::sSubstateSendMetadata, // TxSubState::TX_SUB_STATE_METADATA
114 &Transaction::sSubstateSendFileData, // TxSubState::TX_SUB_STATE_FILEDATA
115 &Transaction::s1SubstateSendEof, // TxSubState::TX_SUB_STATE_EOF
116 nullptr // TxSubState::TX_SUB_STATE_CLOSEOUT_SYNC
117 }};
118
119 this->sDispatchTransmit(&substate_fns);
120 }
121
122 void Transaction::s2Tx() {
123 static const SSubstateSendDispatchTable substate_fns = {{
124 &Transaction::sSubstateSendMetadata, // TxSubState::TX_SUB_STATE_METADATA
125 &Transaction::s2SubstateSendFileData, // TxSubState::TX_SUB_STATE_FILEDATA
126 &Transaction::s2SubstateSendEof, // TxSubState::TX_SUB_STATE_EOF
127 nullptr // TxSubState::TX_SUB_STATE_CLOSEOUT_SYNC
128 }};
129
130 this->sDispatchTransmit(&substate_fns);
131 }
132
133 void Transaction::sAckTimerTick() {
134 U8 ack_limit = 0;
135
136 // note: the ack timer is only ever relevant on class 2
137 if (this->m_state != TxnState::TXN_STATE_S2 || !this->m_flags.com.ack_timer_armed) {
138 // nothing to do
139 return;
140 }
141
142 if (this->m_ack_timer.getStatus() == Timer::Status::RUNNING) {
143 this->m_ack_timer.run();
144 } else if (this->m_state_data.send.sub_state == TxSubState::TX_SUB_STATE_CLOSEOUT_SYNC) {
145 // Check limit and handle if needed
146 ack_limit = this->m_cfdpManager->getAckLimitParam(this->m_chan_num);
147 if (this->m_state_data.send.s2.acknak_count >= ack_limit) {
148 this->m_cfdpManager->log_WARNING_LO_TxAckLimitReached(this->getClass(), this->m_history->src_eid,
149 this->m_history->seq_num);
150 this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_ACK_LIMIT_NO_EOF);
151 this->m_cfdpManager->incrementFaultAckLimit(this->m_chan_num);
152
153 // give up on this
154 this->m_engine->finishTransaction(this, true);
155 this->m_flags.com.ack_timer_armed = false;
156 } else {
157 // Increment acknak counter
158 ++this->m_state_data.send.s2.acknak_count;
159
160 // If the peer sent FIN that is an implicit EOF ack, it is not supposed
161 // to send it before EOF unless an error occurs, and either way we do not
162 // re-transmit anything after FIN unless we get another FIN
163 if (!this->m_flags.tx.eof_ack_recv && !this->m_flags.tx.fin_recv) {
164 this->m_flags.tx.send_eof = true;
165 } else {
166 // no response is pending
167 this->m_flags.com.ack_timer_armed = false;
168 }
169 }
170
171 // reset the ack timer if still waiting on something
172 if (this->m_flags.com.ack_timer_armed) {
173 this->m_engine->armAckTimer(this);
174 }
175 } else {
176 // if we are not waiting for anything, why is the ack timer armed?
177 this->m_flags.com.ack_timer_armed = false;
178 }
179 }
180
181 void Transaction::sTick(I32* cont /* unused */) {
182 bool pending_send;
183
184 pending_send = true; // maybe; tbd, will be reset if not
185
186 // at each tick, various timers used by S are checked
187 // first, check inactivity timer
188 if (!this->m_flags.com.inactivity_fired) {
189 if (this->m_inactivity_timer.getStatus() == Timer::Status::RUNNING) {
190 this->m_inactivity_timer.run();
191 // Check if timer just expired naturally (after run())
192 if (this->m_inactivity_timer.getStatus() == Timer::Status::EXPIRED) {
193 this->m_flags.com.inactivity_fired = true;
194
195 // HOLD state is the normal path to recycle transaction objects, not an error
196 // Canceled transactions timing out while waiting for EOF-ACK is also normal
197 // inactivity is abnormal in any other state
198 if (this->m_state != TxnState::TXN_STATE_HOLD && this->m_state == TxnState::TXN_STATE_S2 &&
199 !this->m_flags.com.canceled) {
200 this->m_cfdpManager->log_WARNING_LO_TxInactivityTimeout(this->getClass(), this->m_history->src_eid,
201 this->m_history->seq_num);
202 this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_INACTIVITY_DETECTED);
203
204 this->m_cfdpManager->incrementFaultInactivityTimer(this->m_chan_num);
205 }
206 }
207 }
208 }
209
210 // tx maintenance: possibly process send_eof, or send_fin_ack
211 // On ERROR, clear the flag so we do not retry forever. On NO_BUF_AVAIL, leave it set to retry.
212 if (this->m_flags.tx.send_eof) {
213 Status::T sret = this->sSendEof();
214 if (sret == Cfdp::Status::SUCCESS) {
215 this->m_flags.tx.send_eof = false;
216 } else if (sret == Cfdp::Status::ERROR) {
217 this->m_flags.tx.send_eof = false;
218 pending_send = false;
219 }
220 } else if (this->m_flags.tx.send_fin_ack) {
221 Status::T sret = this->sSendFinAck();
222 if (sret == Cfdp::Status::SUCCESS) {
223 this->m_flags.tx.send_fin_ack = false;
224 } else if (sret == Cfdp::Status::ERROR) {
225 this->m_flags.tx.send_fin_ack = false;
226 pending_send = false;
227 }
228 } else {
229 pending_send = false;
230 }
231
232 // if the inactivity timer ran out, then there is no sense
233 // pending for responses for anything. Send out anything
234 // that we need to send (i.e. the EOF) just in case the sender
235 // is still listening to us but do not expect any future ACKs
236 //
237 // Recycle the transaction once the inactivity timer has fired, but never while a send
238 // is still pending (e.g. a throttled FIN-ACK). The send is attempted above before this
239 // check, so if one is still queued we defer recycle a cycle to give it a chance to go
240 // out. Once the send succeeds, pending_send clears, so this cannot strand the transaction.
241 // This covers the HOLD (finished) and S2/CLOSEOUT_SYNC (stuck waiting for a FIN) cases,
242 // which by then have no pending send, without dropping a FIN-ACK that has not yet been
243 // transmitted.
244 //
245 // Bound the deferral so a send that keeps failing on a buffer shortage cannot hold the slot
246 // forever: after a small retry budget, recycle regardless. A late FIN is answered statelessly.
247 bool retries_exhausted = false;
248 if (this->m_flags.com.inactivity_fired && pending_send) {
249 if (this->m_flags.com.post_inactivity_send_retries >=
250 this->m_cfdpManager->getPostInactivitySendRetriesParam()) {
251 retries_exhausted = true;
252 } else {
253 this->m_flags.com.post_inactivity_send_retries++;
254 }
255 }
256 bool should_recycle = this->m_flags.com.inactivity_fired && (!pending_send || retries_exhausted);
257
258 if (should_recycle) {
259 // the transaction is now recyclable - this means we will
260 // no longer have a record of this transaction seq. If the sender
261 // wakes up or if the network delivers severely delayed PDUs at
262 // some future point, then they will be seen as spurious. They
263 // will no longer be associable with this transaction at all
264 this->m_chan->recycleTransaction(this);
265
266 // NOTE: this must be the last thing in here. Do not use txn after this
267 } else {
268 // transaction still valid so process the ACK timer, if relevant
269 this->sAckTimerTick();
270 }
271 }
272
273 void Transaction::sTickNak(I32* cont) {
274 bool nakProcessed = false;
275 Status::T status;
276
277 // Only Class 2 transactions should process NAKs
278 if (this->m_txn_class == Cfdp::Class::CLASS_2) {
279 status = this->sCheckAndRespondNak(&nakProcessed);
280 if ((status == Cfdp::Status::SUCCESS) && nakProcessed) {
281 *cont = 1; // cause dispatcher to re-enter this scheduler cycle
282 }
283 }
284 }
285
286 void Transaction::sCancel() {
287 if (this->m_state_data.send.sub_state < TxSubState::TX_SUB_STATE_EOF) {
288 // if state has not reached TxSubState::TX_SUB_STATE_EOF, then set it to TxSubState::TX_SUB_STATE_EOF now.
289 this->m_state_data.send.sub_state = TxSubState::TX_SUB_STATE_EOF;
290 }
291 }
292
293 // ======================================================================
294 // TX State Machine - Private Helper Methods
295 // ======================================================================
296
297 Status::T Transaction::sSendEof() {
298 // note the crc is "finalized" regardless of success or failure of the txn
299 // this is OK as we still need to put some value into the EOF
300 if (!this->m_flags.com.crc_calc) {
301 // The F' version does not have an equivalent finalize call as it
302 // - Never stores a partial word internally
303 // - Never needs to "flush" anything
304 // - Always accounts for padding at update time
305 this->m_flags.com.crc_calc = true;
306 }
307 return this->m_engine->sendEof(this);
308 }
309
310 void Transaction::s1SubstateSendEof() {
311 // set the flag, the EOF is sent by the tick handler
312 this->m_flags.tx.send_eof = true;
313
314 // In class 1 this is the end of normal operation
315 // NOTE: this is not always true, as class 1 can request an EOF ack.
316 // In this case we could change state to CLOSEOUT_SYNC instead and wait,
317 // but right now we do not request an EOF ack in S1
318 this->m_engine->finishTransaction(this, true);
319 }
320
321 void Transaction::s2SubstateSendEof() {
322 // set the flag, the EOF is sent by the tick handler
323 this->m_flags.tx.send_eof = true;
324
325 // wait for remaining responses to close out the state machine
326 this->m_state_data.send.sub_state = TxSubState::TX_SUB_STATE_CLOSEOUT_SYNC;
327
328 // always move the transaction onto the wait queue now
329 this->m_chan->dequeueTransaction(this);
330 this->m_chan->insertSortPrio(this, QueueId::TXW);
331
332 // the ack timer is armed in class 2 only
333 this->m_engine->armAckTimer(this);
334 }
335
336 Status::T Transaction::sSendFileData(FileSize foffs, FileSize bytes_to_read, U8 calc_crc, FileSize* bytes_processed) {
337 FW_ASSERT(bytes_processed != nullptr);
338 *bytes_processed = 0;
339
340 Status::T status = Cfdp::Status::SUCCESS;
341
342 // Local buffer for file data
343 U8 fileDataBuffer[MaxPduSize];
344
345 // Create File Data PDU
346 FileDataPdu fdPdu;
347 Cfdp::PduDirection direction = PduDirection::DIRECTION_TOWARD_RECEIVER;
348
349 // Calculate maximum data size we can send, accounting for PDU overhead
350 U32 maxDataCapacity = fdPdu.getMaxFileDataSize();
351
352 // Limited by: bytes_to_read, outgoing_file_chunk_size, and maxDataCapacity
353 FileSize outgoing_file_chunk_size = this->m_cfdpManager->getOutgoingFileChunkSizeParam();
354 FileSize max_data_bytes = bytes_to_read;
355 if (max_data_bytes > outgoing_file_chunk_size) {
356 max_data_bytes = outgoing_file_chunk_size;
357 }
358 if (max_data_bytes > maxDataCapacity) {
359 max_data_bytes = maxDataCapacity;
360 }
361
362 // Seek to file offset if needed
363 FwSizeType actual_bytes = max_data_bytes;
364 if (status == Cfdp::Status::SUCCESS) {
365 if (this->m_state_data.send.cached_pos != foffs) {
366 Os::File::Status fileStatus = this->m_fd.seek(foffs, Os::File::SeekType::ABSOLUTE);
367 if (fileStatus != Os::File::OP_OK) {
368 status = Cfdp::Status::ERROR;
369 }
370 }
371 }
372
373 // Read file data
374 if (status == Cfdp::Status::SUCCESS) {
375 Os::File::Status fileStatus = this->m_fd.read(fileDataBuffer, actual_bytes, Os::File::WaitType::WAIT);
376 if (fileStatus != Os::File::OP_OK) {
377 status = Cfdp::Status::ERROR;
378 }
379 }
380
381 // Initialize and send PDU
382 if (status == Cfdp::Status::SUCCESS) {
383 // File has been read successfully, update cached_pos to reflect new file position
384 // This MUST be done before attempting to send, so if send fails (throttle/error),
385 // we don't try to read the same data again on next cycle
386 this->m_state_data.send.cached_pos += static_cast<FileSize>(actual_bytes);
387
388 fdPdu.initialize(direction,
389 this->getClass(), // transmission mode
390 this->m_cfdpManager->getLocalEidParam(), // source EID
391 this->m_history->seq_num, // transaction sequence number
392 this->m_history->peer_eid, // destination EID
393 foffs, // file offset
394 static_cast<U16>(actual_bytes), // data size
395 fileDataBuffer // data pointer
396 );
397
398 status = this->m_engine->sendFd(this, fdPdu);
399 }
400
401 // Update CRC and bytes_processed
402 if (status == Cfdp::Status::SUCCESS) {
403 FW_ASSERT((foffs + actual_bytes) <= this->m_fsize, static_cast<FwAssertArgType>(foffs),
404 static_cast<FwAssertArgType>(actual_bytes), static_cast<FwAssertArgType>(this->m_fsize));
405
406 if (calc_crc) {
407 this->m_crc.update(fileDataBuffer, foffs, static_cast<U32>(actual_bytes));
408 }
409
410 *bytes_processed = static_cast<U32>(actual_bytes);
411 }
412
413 return status;
414 }
415
416 void Transaction::sSubstateSendFileData() {
417 FileSize bytes_processed = 0;
418 Status::T status = this->sSendFileData(this->m_foffs, (this->m_fsize - this->m_foffs), 1, &bytes_processed);
419
420 // When SEND_PDU_NO_BUF_AVAIL_ERROR is returned, it means either:
421 // 1) The throttle limit (max_outgoing_pdus_per_cycle) was reached, OR
422 // 2) Buffer allocation failed
423 // In either case, we should stay in FILEDATA state and retry next cycle.
424 // This is NOT a file I/O error, so we should NOT transition to EOF.
425 // We also need to break the cycleTx loop by setting m_chan->m_currentTxn.
426 if (status == Cfdp::Status::SEND_PDU_NO_BUF_AVAIL_ERROR) {
427 // Throttle limit or buffer exhaustion - stay in FILEDATA, retry next cycle
428 // Set m_currentTxn to break the cycleTx loop for this cycle
429 this->m_chan->setCurrentTxn(this);
430 } else if (status != Cfdp::Status::SUCCESS) {
431 // IO error -- change state and send EOF
432 this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_FILESTORE_REJECTION);
433 this->m_state_data.send.sub_state = TxSubState::TX_SUB_STATE_EOF;
434 } else if (bytes_processed > 0) {
435 this->m_foffs += bytes_processed;
436 if (this->m_foffs == this->m_fsize) {
437 // file is done - transition to EOF state, which will be sent in next loop iteration
438 this->m_state_data.send.sub_state = TxSubState::TX_SUB_STATE_EOF;
439 }
440 } else {
441 // don't care about other cases
442 }
443 }
444
445 Status::T Transaction::sCheckAndRespondNak(bool* nakProcessed) {
446 const Chunk* chunk;
447 Status::T sret;
448 Status::T ret = Cfdp::Status::SUCCESS;
449 FileSize bytes_processed = 0;
450
451 FW_ASSERT(nakProcessed != nullptr);
452 *nakProcessed = false;
453
454 // Class 2 transactions must have had chunks allocated
455 FW_ASSERT(this->m_chunks != nullptr);
456
457 if (this->m_flags.tx.md_need_send) {
458 sret = this->m_engine->sendMd(this);
459 if (sret == Cfdp::Status::ERROR) {
460 ret = Cfdp::Status::ERROR; // serialization failure -- fail the transaction
461 } else {
462 if (sret == Cfdp::Status::SUCCESS) {
463 this->m_flags.tx.md_need_send = false;
464 }
465 // On SUCCESS or SEND_PDU_NO_BUF_AVAIL_ERROR (throttled, retry next cycle),
466 // mark nak processed to keep caller from sending file data this cycle
467 *nakProcessed = true; // nak processed, so don't send filedata
468 }
469 } else {
470 // Get first chunk and process if available
471 chunk = this->m_chunks->chunks.getFirstChunk();
472 if (chunk != nullptr) {
473 ret = this->sSendFileData(chunk->offset, chunk->size, 0, &bytes_processed);
474 if (ret != Cfdp::Status::SUCCESS) {
475 // error occurred
476 ret = Cfdp::Status::ERROR; // error occurred
477 } else if (bytes_processed > 0) {
478 this->m_chunks->chunks.removeFromFirst(bytes_processed);
479 *nakProcessed = true; // nak processed, so caller doesn't send file data
480 }
481 }
482 }
483
484 return ret;
485 }
486
487 void Transaction::s2SubstateSendFileData() {
488 Status::T status;
489 bool nakProcessed = false;
490
491 status = this->sCheckAndRespondNak(&nakProcessed);
492 if (status != Cfdp::Status::SUCCESS) {
493 this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_NAK_RESPONSE_ERROR);
494 this->m_flags.tx.send_eof = true; /* do not leave the remote hanging */
495 this->m_engine->finishTransaction(this, true);
496 return;
497 }
498
499 if (!nakProcessed) {
500 this->sSubstateSendFileData();
501 } else {
502 // NAK was processed, so do not send filedata
503 }
504 }
505
506 void Transaction::sSubstateSendMetadata() {
507 Status::T status;
508 Os::File::Status fileStatus;
509 bool success = true;
510
511 if (false == this->m_fd.isOpen()) {
512 fileStatus = this->m_fd.open(this->m_history->fnames.src_filename.toChar(), Os::File::OPEN_READ);
513 if (fileStatus != Os::File::OP_OK) {
514 this->m_cfdpManager->log_WARNING_LO_TxFileOpenFailed(this->getClass(), this->m_history->src_eid,
515 this->m_history->seq_num,
516 this->m_history->fnames.src_filename, fileStatus);
517 this->m_cfdpManager->incrementFaultFileOpen(this->m_chan_num);
518 success = false;
519 }
520
521 if (success) {
522 FwSizeType file_size;
523 fileStatus = this->m_fd.size(file_size);
524 this->m_fsize = static_cast<FileSize>(file_size);
525 if (fileStatus != Os::File::Status::OP_OK) {
526 this->m_cfdpManager->log_WARNING_LO_TxFileSeekFailed(this->getClass(), this->m_history->src_eid,
527 this->m_history->seq_num, fileStatus);
528 this->m_cfdpManager->incrementFaultFileSeek(this->m_chan_num);
529 success = false;
530 } else if (this->m_fsize == 0) {
531 // Zero-length file - fail transaction gracefully instead of asserting
532 this->m_cfdpManager->log_WARNING_LO_TxZeroLengthFile(this->getClass(), this->m_history->src_eid,
533 this->m_history->seq_num,
534 this->m_history->fnames.src_filename);
535 this->m_cfdpManager->incrementFaultFileSizeMismatch(this->m_chan_num);
536 success = false;
537 }
538 }
539 }
540
541 if (success) {
542 status = this->m_engine->sendMd(this);
543 if (status == Cfdp::Status::ERROR) {
544 /* failed to send md (generic ERROR from a PDU serialization failure) */
545 this->m_cfdpManager->log_WARNING_LO_TxSendMetadataFailed(this->getClass(), this->m_history->src_eid,
546 this->m_history->seq_num);
547 success = false;
548 } else if (status == Cfdp::Status::SUCCESS) {
549 /* once metadata is sent, switch to filedata mode */
550 this->m_state_data.send.sub_state = TxSubState::TX_SUB_STATE_FILEDATA;
551
552 this->m_cfdpManager->log_ACTIVITY_HI_TxFileTransferStarted(
553 this->getClass(), this->m_history->seq_num, this->m_history->src_eid,
554 this->m_history->fnames.src_filename, this->m_history->peer_eid, this->m_history->fnames.dst_filename,
555 static_cast<U32>(this->m_fsize));
556 }
557 /* if status==Cfdp::Status::SEND_PDU_NO_BUF_AVAIL_ERROR, then the send buffer is throttled;
558 leave success==true and retry the metadata send on the next cycle */
559 }
560
561 if (!success) {
562 this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_FILESTORE_REJECTION);
563 this->m_engine->finishTransaction(this, true);
564 }
565
566 // don't need to reset the CRC since its taken care of by reset_cfdp()
567 }
568
569 Status::T Transaction::sSendFinAck() {
570 Status::T ret =
571 this->m_engine->sendAck(this, static_cast<AckTxnStatus>(GetTxnStatus(this)), FileDirective::FILE_DIRECTIVE_FIN,
572 static_cast<ConditionCode>(this->m_state_data.send.s2.fin_cc),
573 this->m_history->peer_eid, this->m_history->seq_num);
574 return ret;
575 }
576
577 void Transaction::s2EarlyFin(const Fw::Buffer& buffer) {
578 // received early fin, so just cancel
579 this->m_cfdpManager->log_WARNING_LO_TxEarlyFinReceived(this->getClass(), this->m_history->src_eid,
580 this->m_history->seq_num);
581 this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_EARLY_FIN);
582
583 this->m_state_data.send.sub_state = TxSubState::TX_SUB_STATE_CLOSEOUT_SYNC;
584
585 // otherwise do normal fin processing
586 this->s2Fin(buffer);
587 }
588
589 void Transaction::s2Fin(const Fw::Buffer& buffer) {
590 // Deserialize FIN PDU from buffer
591 FinPdu fin;
592 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
593 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
594 sb.setBuffLen(buffer.getSize());
595
596 Fw::SerializeStatus deserStatus = fin.deserializeFrom(sb);
597 if (deserStatus != Fw::FW_SERIALIZE_OK) {
598 // Bad FIN PDU
599 this->m_cfdpManager->log_WARNING_LO_FailFinPduDeserialization(this->getChannelId(),
600 static_cast<I32>(deserStatus));
601 return;
602 }
603
604 if (!this->m_engine->recvFin(this, fin)) {
605 // Always set flag to send FIN-ACK, even if it is a retransmit
606 this->m_flags.tx.send_fin_ack = true;
607
608 // set the CC only on the first time we get the FIN. If this is a dupe
609 // then re-ack but otherwise ignore it
610 if (!this->m_flags.tx.fin_recv) {
611 this->m_flags.tx.fin_recv = true;
612 this->m_state_data.send.s2.fin_cc = static_cast<U8>(fin.getConditionCode());
613 this->m_state_data.send.s2.acknak_count = 0; // in case retransmits had occurred
614
615 // note this is a no-op unless the status was unset previously
616 this->m_engine->setTxnStatus(this, static_cast<TxnStatus>(this->m_state_data.send.s2.fin_cc));
617
618 // Generally FIN is the last exchange in an S2 transaction, the remote is not supposed
619 // to send it until after the EOF+ACK. So at this point we stop trying to send anything
620 // to the peer, regardless of whether we got every ACK we expected.
621 this->m_engine->finishTransaction(this, true);
622 }
623 }
624 }
625
626 void Transaction::s2Nak(const Fw::Buffer& buffer) {
627 U8 counter;
628 U8 bad_sr;
629
630 bad_sr = 0;
631
632 // Deserialize NAK PDU from buffer
633 NakPdu nak;
634 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
635 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
636 sb.setBuffLen(buffer.getSize());
637
638 Fw::SerializeStatus deserStatus = nak.deserializeFrom(sb);
639 if (deserStatus != Fw::FW_SERIALIZE_OK) {
640 // Bad NAK PDU
641 this->m_cfdpManager->log_WARNING_LO_FailNakPduDeserialization(this->getChannelId(),
642 static_cast<I32>(deserStatus));
643 this->m_cfdpManager->incrementRecvErrors(this->m_chan_num);
644 return;
645 }
646
647 // this function is only invoked for NAK PDU types
648 if (this->m_engine->recvNak(this, nak) == Cfdp::Status::SUCCESS && nak.getNumSegments() > 0) {
649 for (counter = 0; counter < nak.getNumSegments(); ++counter) {
650 const Cfdp::SegmentRequest& sr = nak.getSegment(counter);
651
652 if (sr.offsetStart == 0 && sr.offsetEnd == 0) {
653 // need to re-send metadata PDU
654 this->m_flags.tx.md_need_send = true;
655 } else {
656 if (sr.offsetEnd < sr.offsetStart) {
657 ++bad_sr;
658 continue;
659 }
660
661 // overflow probably won't be an issue
662 if (sr.offsetEnd > this->m_fsize) {
663 ++bad_sr;
664 continue;
665 }
666
667 // insert gap data in chunks
668 this->m_chunks->chunks.add(sr.offsetStart, sr.offsetEnd - sr.offsetStart);
669 }
670 }
671
672 this->m_cfdpManager->addRecvNakSegmentRequests(this->m_chan_num, nak.getNumSegments());
673 if (bad_sr) {
674 this->m_cfdpManager->log_WARNING_LO_TxInvalidSegmentRequests(this->getClass(), this->m_history->src_eid,
675 this->m_history->seq_num, bad_sr);
676 }
677 } else {
678 this->m_cfdpManager->log_WARNING_LO_TxInvalidNakPdu(this->getClass(), this->m_history->src_eid,
679 this->m_history->seq_num);
680 this->m_cfdpManager->incrementRecvErrors(this->m_chan_num);
681 }
682 }
683
684 void Transaction::s2NakArm(const Fw::Buffer& buffer) {
685 this->m_engine->armAckTimer(this);
686 this->s2Nak(buffer);
687 }
688
689 void Transaction::s2EofAck(const Fw::Buffer& buffer) {
690 // Deserialize ACK PDU from buffer
691 AckPdu ack;
692 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
693 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
694 sb.setBuffLen(buffer.getSize());
695
696 Fw::SerializeStatus deserStatus = ack.deserializeFrom(sb);
697 if (deserStatus != Fw::FW_SERIALIZE_OK) {
698 // Bad ACK PDU
699 this->m_cfdpManager->log_WARNING_LO_FailAckPduDeserialization(this->getChannelId(),
700 static_cast<I32>(deserStatus));
701 return;
702 }
703
704 // ACK PDU has been validated during deserialization
705 // Check if this is an EOF acknowledgment
706 if (ack.getDirectiveCode() == FileDirective::FILE_DIRECTIVE_END_OF_FILE) {
707 this->m_flags.tx.eof_ack_recv = true;
708 this->m_flags.com.ack_timer_armed = false; // just wait for FIN now, nothing to re-send
709 this->m_state_data.send.s2.acknak_count = 0; // in case EOF retransmits had occurred
710
711 // For canceled transactions, finish immediately after receiving EOF-ACK
712 // The remote side does not send FIN for canceled transactions per CFDP protocol
713 // if FIN was also received then we are done (these can come out of order)
714 if (this->m_flags.com.canceled || this->m_flags.tx.fin_recv) {
715 this->m_engine->finishTransaction(this, true);
716 }
717 }
718 }
719
720 // ======================================================================
721 // Dispatch Methods (ported from cf_cfdp_dispatch.c)
722 // ======================================================================
723
724 void Transaction::sDispatchRecv(const Fw::Buffer& buffer, const SSubstateRecvDispatchTable* dispatch) {
725 const FileDirectiveDispatchTable* substate_tbl;
726 StateRecvFunc selected_handler;
727
728 FW_ASSERT(this->m_state_data.send.sub_state < TxSubState::TX_SUB_STATE_NUM_STATES,
729 static_cast<U8>(this->m_state_data.send.sub_state), static_cast<U8>(TxSubState::TX_SUB_STATE_NUM_STATES));
730
731 // Peek at PDU type from buffer
732 Cfdp::PduTypeEnum::T pduType = Cfdp::peekPduType(buffer);
733
734 // send state, so we only care about file directive PDU
735 selected_handler = nullptr;
736
737 if (pduType == Cfdp::PduTypeEnum::FILE_DATA) {
738 this->m_cfdpManager->log_WARNING_LO_TxNonFileDirectivePduReceived(this->getClass(), this->m_history->src_eid,
739 this->m_history->seq_num);
740 } else {
741 // Not a file-data PDU - parse as a directive PDU to get the directive code.
742 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
743 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
744 sb.setBuffLen(buffer.getSize());
745
746 Cfdp::PduHeader header;
747 if (header.fromSerialBuffer(sb) == Fw::FW_SERIALIZE_OK) {
748 // Read directive code (first byte after header for directive PDUs)
749 U8 directiveCodeByte;
750 if (sb.deserializeTo(directiveCodeByte) == Fw::FW_SERIALIZE_OK) {
751 FileDirective directiveCode = static_cast<FileDirective>(directiveCodeByte);
752
753 if (directiveCode < FileDirective::FILE_DIRECTIVE_INVALID_MAX) {
754 // This should be silent (no event) if no handler is defined in the table
755 substate_tbl = dispatch->substate[static_cast<U32>(this->m_state_data.send.sub_state)];
756 if (substate_tbl != nullptr) {
757 selected_handler = substate_tbl->fdirective[static_cast<U32>(directiveCode)];
758 }
759 } else {
760 this->m_cfdpManager->log_WARNING_LO_TxInvalidDirectiveCode(
761 this->getClass(), this->m_history->src_eid, this->m_history->seq_num, directiveCodeByte,
762 static_cast<U8>(this->m_state_data.send.sub_state));
763 }
764 }
765 }
766 }
767
768 // check that there's a valid function pointer. If there isn't,
769 // then silently ignore. We may want to discuss if it's worth
770 // shutting down the whole transaction if a PDU is received
771 // that doesn't make sense to be received (For example,
772 // class 1 CFDP receiving a NAK PDU) but for now, we silently
773 // ignore the received packet and keep chugging along.
774 if (selected_handler) {
775 (this->*selected_handler)(buffer);
776 }
777 }
778
779 void Transaction::sDispatchTransmit(const SSubstateSendDispatchTable* dispatch) {
780 StateSendFunc selected_handler;
781
782 selected_handler = dispatch->substate[static_cast<U32>(this->m_state_data.send.sub_state)];
783 if (selected_handler != nullptr) {
784 (this->*selected_handler)();
785 }
786 }
787
788 void Transaction::txStateDispatch(const TxnSendDispatchTable* dispatch) {
789 StateSendFunc selected_handler;
790
791 FW_ASSERT(this->m_state < TxnState::TXN_STATE_INVALID, static_cast<U8>(this->m_state),
792 static_cast<U8>(TxnState::TXN_STATE_INVALID));
793
794 selected_handler = dispatch->tx[static_cast<U32>(this->m_state)];
795 if (selected_handler != nullptr) {
796 (this->*selected_handler)();
797 }
798 }
799
800 } // namespace Cfdp
801 } // namespace Ccsds
802 } // namespace Svc
803