F´ Flight Software - C/C++ Documentation
A framework for building embedded system applications to NASA flight quality standards.
TransactionRx.cpp
Go to the documentation of this file.
1 // ======================================================================
2 // \title TransactionRx.cpp
3 // \brief cpp file for CFDP RX Transaction state machine
4 //
5 // This file is a port of RX 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_r.c (receive-file transaction state handling routines)
9 // - cf_cfdp_dispatch.c (RX state machine dispatch functions)
10 //
11 // This file contains various state handling routines for
12 // transactions which are receiving a file, as well as dispatch
13 // functions for RX 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 
41 #include <Os/FileSystem.hpp>
42 
49 
50 namespace Svc {
51 namespace Ccsds {
52 namespace Cfdp {
53 
54 // ======================================================================
55 // Construction and Destruction
56 // ======================================================================
57 
58 Transaction::Transaction(Channel* channel, U8 channelId, Engine* engine, CfdpManager* manager)
59  : m_state(TxnState::TXN_STATE_UNDEF),
60  m_txn_class(Cfdp::Class::CLASS_1),
61  m_history(nullptr),
62  m_chunks(nullptr),
63  m_inactivity_timer(),
64  m_ack_timer(),
65  m_fsize(0),
66  m_foffs(0),
67  m_fd(),
68  m_crc(),
69  m_keep(Cfdp::Keep::KEEP),
70  m_chan_num(channelId), // Initialize from parameter
71  m_priority(0),
73  m_cl_node{},
74  m_pb(nullptr),
75  m_state_data{},
76  m_flags{},
77  m_cfdpManager(manager), // Initialize from parameter
78  m_chan(channel), // Initialize from parameter
79  m_engine(engine) // Initialize from parameter
80 {
81  // Fully zero the union storage
82  memset(&this->m_state_data, 0, sizeof(this->m_state_data));
83  memset(&this->m_flags, 0, sizeof(this->m_flags));
84 }
85 
87 
89  // Reset transaction state to default values
90  this->m_state = TxnState::TXN_STATE_UNDEF;
91  this->m_txn_class = Cfdp::Class::CLASS_1;
92  this->m_fsize = 0;
93  this->m_foffs = 0;
94  this->m_keep = Cfdp::Keep::KEEP;
95  this->m_priority = 0;
96  this->m_initType = TransactionInitType::INIT_BY_COMMAND;
97  this->m_crc = CFDP::Checksum(0);
98  this->m_pb = nullptr;
99 
100  // Fully zero the union storage
101  memset(&this->m_state_data, 0, sizeof(this->m_state_data));
102  memset(&this->m_flags, 0, sizeof(this->m_flags));
103 
104  // Close the file if it is open
105  if (this->m_fd.isOpen()) {
106  this->m_fd.close();
107  }
108 
109  // Disable timers to ensure clean state for next transaction
110  // This prevents stale timers from a previous transaction firing in a new context
111  this->m_inactivity_timer.disableTimer();
112  this->m_ack_timer.disableTimer();
113 
114  // The following state information is PRESERVED across reset (NOT modified):
115  // - this->m_cfdpManager // Channel binding
116  // - this->m_chan // Channel binding
117  // - this->m_engine // Channel binding
118  // - this->m_chan_num // Channel binding
119  // - this->m_history // Assigned when transaction is activated
120  // - this->m_chunks // Assigned when transaction is activated
121  // - this->m_cl_node // Managed by queue operations in freeTransaction()
122 }
123 
124 // ======================================================================
125 // RX State Machine - Public Methods
126 // ======================================================================
127 
128 void Transaction::r1Recv(const Fw::Buffer& buffer) {
129  static const FileDirectiveDispatchTable r1_fdir_handlers = {{
130  nullptr, /* CFDP_FileDirective_INVALID_MIN */
131  nullptr, /* 1 is unused in the CFDP_FileDirective_t enum */
132  nullptr, /* 2 is unused in the CFDP_FileDirective_t enum */
133  nullptr, /* 3 is unused in the CFDP_FileDirective_t enum */
134  &Transaction::r1SubstateRecvEof, /* CFDP_FileDirective_EOF */
135  nullptr, /* CFDP_FileDirective_FIN */
136  nullptr, /* CFDP_FileDirective_ACK */
137  nullptr, /* CFDP_FileDirective_METADATA */
138  nullptr, /* CFDP_FileDirective_NAK */
139  nullptr, /* CFDP_FileDirective_PROMPT */
140  nullptr, /* 10 is unused in the CFDP_FileDirective_t enum */
141  nullptr, /* 11 is unused in the CFDP_FileDirective_t enum */
142  nullptr, /* CFDP_FileDirective_KEEP_ALIVE */
143  }};
144 
145  static const RSubstateDispatchTable substate_fns = {{
146  &r1_fdir_handlers, /* RxSubState::RX_SUB_STATE_FILEDATA */
147  &r1_fdir_handlers, /* RxSubState::RX_SUB_STATE_EOF */
148  &r1_fdir_handlers, /* RxSubState::RX_SUB_STATE_CLOSEOUT_SYNC */
149  }};
150 
151  this->rDispatchRecv(buffer, &substate_fns, &Transaction::r1SubstateRecvFileData);
152 }
153 
154 void Transaction::r2Recv(const Fw::Buffer& buffer) {
155  static const FileDirectiveDispatchTable r2_fdir_handlers_normal = {{
156  nullptr, /* CFDP_FileDirective_INVALID_MIN */
157  nullptr, /* 1 is unused in the CFDP_FileDirective_t enum */
158  nullptr, /* 2 is unused in the CFDP_FileDirective_t enum */
159  nullptr, /* 3 is unused in the CFDP_FileDirective_t enum */
160  &Transaction::r2SubstateRecvEof, /* CFDP_FileDirective_EOF */
161  nullptr, /* CFDP_FileDirective_FIN */
162  nullptr, /* CFDP_FileDirective_ACK */
163  &Transaction::r2RecvMd, /* CFDP_FileDirective_METADATA */
164  nullptr, /* CFDP_FileDirective_NAK */
165  nullptr, /* CFDP_FileDirective_PROMPT */
166  nullptr, /* 10 is unused in the CFDP_FileDirective_t enum */
167  nullptr, /* 11 is unused in the CFDP_FileDirective_t enum */
168  nullptr, /* CFDP_FileDirective_KEEP_ALIVE */
169  }};
170  static const FileDirectiveDispatchTable r2_fdir_handlers_finack = {{
171  nullptr, /* CFDP_FileDirective_INVALID_MIN */
172  nullptr, /* 1 is unused in the CFDP_FileDirective_t enum */
173  nullptr, /* 2 is unused in the CFDP_FileDirective_t enum */
174  nullptr, /* 3 is unused in the CFDP_FileDirective_t enum */
175  &Transaction::r2SubstateRecvEof, /* CFDP_FileDirective_EOF */
176  nullptr, /* CFDP_FileDirective_FIN */
177  &Transaction::r2RecvFinAck, /* CFDP_FileDirective_ACK */
178  nullptr, /* CFDP_FileDirective_METADATA */
179  nullptr, /* CFDP_FileDirective_NAK */
180  nullptr, /* CFDP_FileDirective_PROMPT */
181  nullptr, /* 10 is unused in the CFDP_FileDirective_t enum */
182  nullptr, /* 11 is unused in the CFDP_FileDirective_t enum */
183  nullptr, /* CFDP_FileDirective_KEEP_ALIVE */
184  }};
185 
186  static const RSubstateDispatchTable substate_fns = {{
187  &r2_fdir_handlers_normal, /* RxSubState::RX_SUB_STATE_FILEDATA */
188  &r2_fdir_handlers_normal, /* RxSubState::RX_SUB_STATE_EOF */
189  &r2_fdir_handlers_finack, /* RxSubState::RX_SUB_STATE_CLOSEOUT_SYNC */
190  }};
191 
192  this->rDispatchRecv(buffer, &substate_fns, &Transaction::r2SubstateRecvFileData);
193 }
194 
196  U8 ack_limit = 0;
197 
198  /* note: the ack timer is only ever armed on class 2 */
199  if (this->m_state != TxnState::TXN_STATE_R2 || !this->m_flags.com.ack_timer_armed) {
200  /* nothing to do */
201  return;
202  }
203 
204  if (this->m_ack_timer.getStatus() == Timer::Status::RUNNING) {
205  this->m_ack_timer.run();
206  } else {
207  /* ACK timer expired, so check for completion */
208  if (!this->m_flags.rx.complete) {
209  this->r2Complete(true);
210  } else if (this->m_state_data.receive.sub_state == RxSubState::RX_SUB_STATE_CLOSEOUT_SYNC) {
211  /* Increment acknak counter */
212  ++this->m_state_data.receive.r2.acknak_count;
213 
214  /* Check limit and handle if needed */
215  ack_limit = this->m_cfdpManager->getAckLimitParam(this->m_chan_num);
216  if (this->m_state_data.receive.r2.acknak_count >= ack_limit) {
217  this->m_cfdpManager->log_WARNING_LO_RxAckLimitReached(this->getClass(), this->m_history->src_eid,
218  this->m_history->seq_num);
220  this->m_cfdpManager->incrementFaultAckLimit(this->m_chan_num);
221 
222  /* give up on this */
223  this->m_engine->finishTransaction(this, true);
224  this->m_flags.com.ack_timer_armed = false;
225  } else {
226  this->m_flags.rx.send_fin = true;
227  }
228  }
229 
230  /* re-arm the timer if it is still pending */
231  if (this->m_flags.com.ack_timer_armed) {
232  /* whether sending FIN or waiting for more filedata, need ACK timer armed */
233  this->m_engine->armAckTimer(this);
234  }
235  }
236 }
237 
238 void Transaction::rTick(I32* cont /* unused */) {
239  /* Steven is not real happy with this function. There should be a better way to separate out
240  * the logic by state so that it isn't a bunch of if statements for different flags
241  */
242 
243  Status::T sret;
244  bool pending_send;
245 
246  if (!this->m_flags.com.inactivity_fired) {
247  if (this->m_inactivity_timer.getStatus() == Timer::Status::RUNNING) {
248  this->m_inactivity_timer.run();
249  // Check if timer just expired naturally (after run())
250  if (this->m_inactivity_timer.getStatus() == Timer::Status::EXPIRED) {
251  this->m_flags.com.inactivity_fired = true;
252 
253  /* HOLD state is the normal path to recycle transaction objects, not an error */
254  /* Canceled transactions timing out is also normal */
255  /* inactivity is abnormal in any other state */
256  if (this->m_state != TxnState::TXN_STATE_HOLD && !this->m_flags.com.canceled) {
257  this->rSendInactivityEvent();
258 
259  /* in class 2 this also triggers sending an early FIN response */
260  if (this->m_state == TxnState::TXN_STATE_R2) {
262  }
263  }
264  }
265  }
266  }
267 
268  pending_send = true; /* maybe; tbd */
269 
270  /* rx maintenance: possibly process send_eof_ack, send_nak or send_fin */
271  if (this->m_flags.rx.send_eof_ack) {
272  sret = this->m_engine->sendAck(this, AckTxnStatus::ACK_TXN_STATUS_ACTIVE,
274  static_cast<ConditionCode>(this->m_state_data.receive.r2.eof_cc),
275  this->m_history->peer_eid, this->m_history->seq_num);
276 
277  /* if SUCCESS, move on. If NO_BUF_AVAIL, retry later. If ERROR, stop retrying. */
278  if (sret == Cfdp::Status::SUCCESS) {
279  this->m_flags.rx.send_eof_ack = false;
280  } else if (sret == Cfdp::Status::ERROR) {
281  /* Serialization failed - error already logged in serializeAndSendPdu */
282  /* Clear flag to avoid infinite retry loop */
283  this->m_flags.rx.send_eof_ack = false;
284  pending_send = false;
285  }
286  /* else NO_BUF_AVAIL: leave flag set to retry next tick */
287  } else if (this->m_flags.rx.send_nak) {
288  if (!this->rSubstateSendNak()) {
289  this->m_flags.rx.send_nak = false; /* will re-enter on error */
290  }
291  } else if (this->m_flags.rx.send_fin) {
292  if (!this->r2SubstateSendFin()) {
293  this->m_flags.rx.send_fin = false; /* will re-enter on error */
294  }
295  } else {
296  /* no pending responses to the sender */
297  pending_send = false;
298  }
299 
300  /* if the inactivity timer ran out, then there is no sense
301  * pending for responses for anything. Send out anything
302  * that we need to send (i.e. the FIN) just in case the sender
303  * is still listening to us but do not expect any future ACKs.
304  *
305  * Bound the deferral like the TX path: after a small retry budget, recycle regardless. */
306  bool retries_exhausted = false;
307  if (this->m_flags.com.inactivity_fired && pending_send) {
308  if (this->m_flags.com.post_inactivity_send_retries >=
309  this->m_cfdpManager->getPostInactivitySendRetriesParam()) {
310  retries_exhausted = true;
311  } else {
312  this->m_flags.com.post_inactivity_send_retries++;
313  }
314  }
315  if (this->m_flags.com.inactivity_fired && (!pending_send || retries_exhausted)) {
316  /* the transaction is now recyclable - this means we will
317  * no longer have a record of this transaction seq. If the sender
318  * wakes up or if the network delivers severely delayed PDUs at
319  * some future point, then they will be seen as spurious. They
320  * will no longer be associable with this transaction at all */
321  this->m_chan->recycleTransaction(this);
322 
323  /* NOTE: this must be the last thing in here. Do not use txn after this */
324  } else {
325  /* transaction still valid so process the ACK timer, if relevant */
326  this->rAckTimerTick();
327  }
328 }
329 
331  /* for cancel, only need to send FIN if R2 */
332  if ((this->m_state == TxnState::TXN_STATE_R2) &&
334  this->m_flags.rx.send_fin = true;
335  } else {
336  this->r1Reset(); /* if R1, just call it quits */
337  }
338 }
339 
341  Os::File::Status status;
342  Fw::String tmpDir;
343  Fw::String dst;
344 
345  if (this->m_state == TxnState::TXN_STATE_R2) {
346  if (!this->m_flags.rx.md_recv) {
347  tmpDir = this->m_cfdpManager->getTmpDirParam(this->m_chan_num);
348  /* we need to make a temp file and then do a NAK for md PDU */
349  /* the transaction already has a history, and that has a buffer that we can use to
350  * hold the temp filename which is defined by the sequence number and the source entity ID */
351 
352  // Create destination filepath with format: <tmpDir>/<src_eid>:<seq_num>.tmp
353  dst.format("%s/%" CFDP_PRI_ENTITY_ID ":%" CFDP_PRI_TRANSACTION_SEQ ".tmp", tmpDir.toChar(),
354  this->m_history->src_eid, this->m_history->seq_num);
355 
356  this->m_history->fnames.dst_filename = dst;
357 
358  this->m_cfdpManager->log_ACTIVITY_LO_RxTempFileCreated(this->getClass(), this->m_history->src_eid,
359  this->m_history->seq_num,
360  this->m_history->fnames.dst_filename);
361  }
362 
363  this->m_engine->armAckTimer(this);
364  }
365 
366  status = this->m_fd.open(this->m_history->fnames.dst_filename.toChar(), Os::File::OPEN_CREATE, Os::File::OVERWRITE);
367  if (status != Os::File::OP_OK) {
368  this->m_cfdpManager->log_WARNING_LO_RxFileCreateFailed(this->getClass(), this->m_history->src_eid,
369  this->m_history->seq_num,
370  this->m_history->fnames.dst_filename, status);
371  this->m_cfdpManager->incrementFaultFileOpen(this->m_chan_num);
372  if (this->m_state == TxnState::TXN_STATE_R2) {
374  } else {
375  this->r1Reset();
376  }
377  } else {
379  }
380 }
381 
383  this->m_engine->setTxnStatus(this, txn_stat);
384  this->m_flags.rx.send_fin = true;
385 }
386 
388  this->m_engine->finishTransaction(this, true);
389 }
390 
392  if ((this->m_state_data.receive.sub_state == RxSubState::RX_SUB_STATE_CLOSEOUT_SYNC) ||
393  (static_cast<U8>(this->m_state_data.receive.r2.eof_cc) !=
394  static_cast<U8>(ConditionCode::CONDITION_CODE_NO_ERROR)) ||
395  TxnStatusIsError(this->m_history->txn_stat) || this->m_flags.com.canceled) {
396  this->r1Reset(); /* it's done */
397  } else {
398  /* not waiting for FIN ACK, so trigger send FIN */
399  this->m_flags.rx.send_fin = true;
400  }
401 }
402 
405  U32 crc_result;
406 
407  // The F' version does not have an equivalent finalize call as it
408  // - Never stores a partial word internally
409  // - Never needs to "flush" anything
410  // - Always accounts for padding at update time
411  crc_result = this->m_crc.getValue();
412  if (crc_result != expected_crc) {
413  this->m_cfdpManager->log_WARNING_LO_RxCrcMismatch(this->getClass(), this->m_history->src_eid,
414  this->m_history->seq_num, expected_crc, crc_result);
415  this->m_cfdpManager->incrementFaultCrcMismatch(this->m_chan_num);
416  ret = Cfdp::Status::ERROR;
417  }
418 
419  return ret;
420 }
421 
422 void Transaction::r2Complete(I32 ok_to_send_nak) {
423  U32 ret;
424  bool send_nak = false;
425  bool send_fin = false;
426  U8 nack_limit = 0;
427  /* checking if r2 is complete. Check NAK list, and send NAK if appropriate */
428  /* if all data is present, then there will be no gaps in the chunk */
429 
430  if (!TxnStatusIsError(this->m_history->txn_stat)) {
431  /* first, check if md is received. If not, send specialized NAK */
432  if (!this->m_flags.rx.md_recv) {
433  send_nak = true;
434  } else {
435  /* only look for 1 gap, since the goal here is just to know that there are gaps */
436  ret = this->m_chunks->chunks.computeGaps(1, this->m_fsize, 0, nullptr, nullptr);
437 
438  if (ret) {
439  /* there is at least 1 gap, so send a NAK */
440  send_nak = true;
441  } else if (this->m_flags.rx.eof_recv) {
442  /* the EOF was received, and there are no NAKs -- process completion in send FIN state */
443  send_fin = true;
444  }
445  }
446 
447  if (send_nak && ok_to_send_nak) {
448  /* Increment the acknak counter */
449  ++this->m_state_data.receive.r2.acknak_count;
450 
451  /* Check limit and handle if needed */
452  nack_limit = this->m_cfdpManager->getNackLimitParam(this->m_chan_num);
453  if (this->m_state_data.receive.r2.acknak_count >= nack_limit) {
454  this->m_cfdpManager->log_WARNING_LO_RxNakLimitReached(this->getClass(), this->m_history->src_eid,
455  this->m_history->seq_num);
456  send_fin = true;
457  this->m_cfdpManager->incrementFaultNakLimit(this->m_chan_num);
458  /* don't use CFDP_R2_SetFinTxnStatus because many places in this function set send_fin */
460  this->m_state_data.receive.r2.acknak_count = 0; /* reset for fin/ack */
461  } else {
462  this->m_flags.rx.send_nak = true;
463  }
464  }
465 
466  if (send_fin) {
467  this->m_flags.rx.complete = true; /* latch completeness, since send_fin is cleared later */
468 
469  /* the transaction is now considered complete, but this will not overwrite an
470  * error status code if there was one set */
472  }
473 
474  /* always go to RxSubState::RX_SUB_STATE_FILEDATA, and let tick change state */
476  }
477 }
478 
479 // ======================================================================
480 // RX State Machine - Private Helper Methods
481 // ======================================================================
482 
483 Status::T Transaction::rProcessFd(const Fw::Buffer& buffer) {
485 
486  /* this function is only entered for data PDUs */
487  // Deserialize FileData PDU from buffer
488  FileDataPdu fd;
489  // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
490  Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
491  sb.setBuffLen(buffer.getSize());
492 
493  Fw::SerializeStatus deserStatus = fd.deserializeFrom(sb);
494  if (deserStatus != Fw::FW_SERIALIZE_OK) {
495  this->m_cfdpManager->log_WARNING_LO_FailFileDataPduDeserialization(this->getChannelId(),
496  static_cast<I32>(deserStatus));
497  ret = Cfdp::Status::ERROR;
498  }
499 
500  /*
501  * NOTE: The decode routine should have left a direct pointer to the data and actual data length
502  * within the PDU. The length has already been verified, too. Should not need to make any
503  * adjustments here, just write it.
504  */
505 
506  FileSize offset = fd.getOffset();
507  U16 dataSize = fd.getDataSize();
508  const U8* dataPtr = fd.getData();
509 
510  // Reject file data past the declared file size (subtraction form avoids offset + dataSize overflow).
511  if ((ret == Cfdp::Status::SUCCESS) && this->m_flags.rx.md_recv) {
512  if ((offset > this->m_fsize) || ((this->m_fsize - offset) < dataSize)) {
513  this->m_cfdpManager->log_WARNING_LO_RxFileDataOutOfBounds(
514  this->getClass(), this->m_history->src_eid, this->m_history->seq_num, offset, dataSize, this->m_fsize);
516  this->m_cfdpManager->incrementFaultFileSizeMismatch(this->m_chan_num);
517  ret = Cfdp::Status::ERROR;
518  }
519  }
520 
521  // Seek to file offset if needed
522  if (ret == Cfdp::Status::SUCCESS) {
523  if (this->m_state_data.receive.cached_pos != offset) {
524  Os::File::Status status = this->m_fd.seek(offset, Os::File::SeekType::ABSOLUTE);
525  if (status != Os::File::OP_OK) {
526  this->m_cfdpManager->log_WARNING_LO_RxSeekFailed(this->getClass(), this->m_history->src_eid,
527  this->m_history->seq_num, offset, status);
529  this->m_cfdpManager->incrementFaultFileSeek(this->m_chan_num);
530  ret = Cfdp::Status::ERROR;
531  }
532  }
533  }
534 
535  // Write file data
536  if (ret == Cfdp::Status::SUCCESS) {
537  FwSizeType write_size = dataSize;
538  Os::File::Status status = this->m_fd.write(dataPtr, write_size, Os::File::WaitType::WAIT);
539  if (status != Os::File::OP_OK) {
540  this->m_cfdpManager->log_WARNING_LO_RxWriteFailed(this->getClass(), this->m_history->src_eid,
541  this->m_history->seq_num, dataSize,
542  static_cast<I32>(write_size));
544  this->m_cfdpManager->incrementFaultFileWrite(this->m_chan_num);
545  ret = Cfdp::Status::ERROR;
546  } else {
547  this->m_state_data.receive.cached_pos = static_cast<FileSize>(dataSize) + offset;
548  this->m_cfdpManager->addRecvFileDataBytes(this->m_chan_num, dataSize);
549  }
550  }
551 
552  return ret;
553 }
554 
555 Status::T Transaction::rSubstateRecvEof(const Fw::Buffer& buffer) {
557 
558  // Deserialize EOF PDU from buffer
559  EofPdu eof;
560  // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
561  Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
562  sb.setBuffLen(buffer.getSize());
563 
564  Fw::SerializeStatus deserStatus = eof.deserializeFrom(sb);
565  if (deserStatus != Fw::FW_SERIALIZE_OK) {
566  this->m_cfdpManager->log_WARNING_LO_FailEofPduDeserialization(this->getChannelId(),
567  static_cast<I32>(deserStatus));
569  }
570 
571  if (ret == Cfdp::Status::SUCCESS) {
572  // NOTE: Engine::recvEof() currently always returns SUCCESS, so the failure branch is
573  // omitted here. Once recvEof() performs real EOF-level validation (see the TLV
574  // "Future enhancement" TODO in Engine::recvEof) and can return an error status, add an
575  // else branch that emits log_WARNING_LO_RxInvalidEofPdu, increments recvErrors, and sets
576  // Cfdp::Status::REC_PDU_BAD_EOF_ERROR for the failure case.
577  if (!this->m_engine->recvEof(this, eof)) {
578  /* this function is only entered for PDUs identified as EOF type */
579  ConditionCode cc = eof.getConditionCode();
580 
581  /* Only check size if MD received and EOF doesn't have a non-zero condition code (e.g., don't check size for
582  * canceled transactions) */
583  if (this->m_flags.rx.md_recv && (cc == ConditionCode::CONDITION_CODE_NO_ERROR) &&
584  (eof.getFileSize() != this->m_fsize)) {
585  this->m_cfdpManager->log_WARNING_LO_RxFileSizeMismatch(this->getClass(), this->m_history->src_eid,
586  this->m_history->seq_num, this->m_fsize,
587  eof.getFileSize());
588  this->m_cfdpManager->incrementFaultFileSizeMismatch(this->m_chan_num);
590  }
591 
592  /* Log condition code if non-zero (cancel or error) - applies to both Class 1 and Class 2 */
594  /* Set transaction status from condition code to prevent completion event */
595  this->m_engine->setTxnStatus(this, static_cast<TxnStatus>(static_cast<I32>(cc)));
596 
598  /* Increment receive EOF cancellation counter (normal operation) */
599  this->m_cfdpManager->incrementRecvEofCanceled(this->m_chan_num);
600 
601  this->m_cfdpManager->log_ACTIVITY_HI_RxEofCancelReceived(this->getClass(), this->m_history->src_eid,
602  this->m_history->seq_num);
603  } else {
604  /* Increment RX EOF error counter (protocol error) */
605  this->m_cfdpManager->incrementFaultRxEofError(this->m_chan_num);
606 
607  this->m_cfdpManager->log_WARNING_LO_RxEofWithError(this->getClass(), this->m_history->src_eid,
608  this->m_history->seq_num, static_cast<U8>(cc));
609  }
610  }
611  }
612  }
613 
614  return ret;
615 }
616 
617 void Transaction::r1SubstateRecvEof(const Fw::Buffer& buffer) {
618  // Deserialize EOF PDU from buffer
619  EofPdu eof;
620  // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
621  Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
622  sb.setBuffLen(buffer.getSize());
623 
624  Fw::SerializeStatus deserStatus = eof.deserializeFrom(sb);
625  if (deserStatus != Fw::FW_SERIALIZE_OK) {
626  // Bad EOF, reset transaction
627  this->m_cfdpManager->log_WARNING_LO_FailEofPduDeserialization(this->getChannelId(),
628  static_cast<I32>(deserStatus));
629  this->r1Reset();
630  return;
631  }
632 
633  Status::T ret = this->rSubstateRecvEof(buffer);
634  U32 crc = eof.getChecksum();
635  ConditionCode cc = eof.getConditionCode();
636 
637  if (ret == Cfdp::Status::SUCCESS) {
638  /* Only check CRC if no error condition code */
640  /* Verify CRC */
641  if (this->rCheckCrc(crc) == Cfdp::Status::SUCCESS) {
642  /* successfully processed the file */
643  this->m_keep = Cfdp::Keep::KEEP; /* save the file */
644  }
645  /* if file failed to process, there's nothing to do. CFDP_R_CheckCrc() generates an event on failure */
646  }
647  }
648 
649  /* after exit, always reset since we are done */
650  /* reset even if the EOF failed -- class 1, so it won't come again! */
651  this->r1Reset();
652 }
653 
654 void Transaction::r2SubstateRecvEof(const Fw::Buffer& buffer) {
655  Status::T ret;
656 
657  if (!this->m_flags.rx.eof_recv) {
658  // Deserialize EOF PDU from buffer
659  EofPdu eof;
660  // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
661  Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
662  sb.setBuffLen(buffer.getSize());
663 
664  Fw::SerializeStatus deserStatus = eof.deserializeFrom(sb);
665  if (deserStatus != Fw::FW_SERIALIZE_OK) {
666  // Bad EOF, return to FILEDATA substate
667  this->m_cfdpManager->log_WARNING_LO_FailEofPduDeserialization(this->getChannelId(),
668  static_cast<I32>(deserStatus));
670  return;
671  }
672 
673  ret = this->rSubstateRecvEof(buffer);
674 
675  /* did receiving EOF succeed? */
676  if (ret == Cfdp::Status::SUCCESS) {
677  this->m_flags.rx.eof_recv = true;
678 
679  /* need to remember the EOF CRC for later */
680  this->m_state_data.receive.r2.eof_crc = eof.getChecksum();
681  this->m_state_data.receive.r2.eof_size = eof.getFileSize();
682 
683  /* always ACK the EOF, even if we're not done */
684  this->m_state_data.receive.r2.eof_cc = static_cast<U8>(eof.getConditionCode());
685  this->m_flags.rx.send_eof_ack = true; /* defer sending ACK to tick handling */
686 
687  /* only check for complete if EOF with no errors */
688  if (static_cast<U8>(this->m_state_data.receive.r2.eof_cc) ==
690  this->r2Complete(true); /* CFDP_R2_Complete() will change state */
691  } else {
692  /* All CFDP CC values directly correspond to a Transaction Status of the same numeric value */
693  this->m_engine->setTxnStatus(
694  this, static_cast<TxnStatus>(static_cast<I32>(this->m_state_data.receive.r2.eof_cc)));
695  this->r2Reset();
696  }
697  } else {
698  /* bad EOF sent? */
701  } else {
702  /* can't do anything with this bad EOF, so return to FILEDATA */
704  }
705  }
706  }
707 }
708 
709 void Transaction::r1SubstateRecvFileData(const Fw::Buffer& buffer) {
710  Status::T ret;
711 
712  // Deserialize FileData PDU from buffer
713  FileDataPdu fd;
714  // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
715  Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
716  sb.setBuffLen(buffer.getSize());
717 
718  Fw::SerializeStatus deserStatus = fd.deserializeFrom(sb);
719  if (deserStatus != Fw::FW_SERIALIZE_OK) {
720  // Bad file data PDU, reset transaction
721  this->m_cfdpManager->log_WARNING_LO_FailFileDataPduDeserialization(this->getChannelId(),
722  static_cast<I32>(deserStatus));
723  this->r1Reset();
724  return;
725  }
726 
727  /* got file data PDU? */
728  ret = this->m_engine->recvFd(this, fd);
729  if (ret == Cfdp::Status::SUCCESS) {
730  ret = this->rProcessFd(buffer);
731  }
732 
733  if (ret == Cfdp::Status::SUCCESS) {
734  /* class 1 digests CRC */
735  this->m_crc.update(fd.getData(), fd.getOffset(), static_cast<U32>(fd.getDataSize()));
736  } else {
737  /* Reset transaction on failure */
738  this->r1Reset();
739  }
740 }
741 
742 void Transaction::r2SubstateRecvFileData(const Fw::Buffer& buffer) {
743  Status::T ret;
744 
745  // If CRC calculation has started (file reopened in READ mode), ignore late FileData PDUs.
746  // This can happen if retransmitted FileData arrives after EOF was received and CRC began.
747  if (this->m_state_data.receive.r2.rx_crc_calc_bytes > 0) {
748  // Silently ignore - file is complete and we're calculating CRC
749  // No EVR needed - late retransmissions are expected in CFDP Class 2
750  return;
751  }
752 
753  // Deserialize FileData PDU from buffer
754  FileDataPdu fd;
755  // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
756  Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
757  sb.setBuffLen(buffer.getSize());
758 
759  Fw::SerializeStatus deserStatus = fd.deserializeFrom(sb);
760  if (deserStatus != Fw::FW_SERIALIZE_OK) {
761  // Bad file data PDU, reset transaction
762  this->m_cfdpManager->log_WARNING_LO_FailFileDataPduDeserialization(this->getChannelId(),
763  static_cast<I32>(deserStatus));
764  this->r2Reset();
765  return;
766  }
767 
768  /* got file data PDU? */
769  ret = this->m_engine->recvFd(this, fd);
770  if (ret == Cfdp::Status::SUCCESS) {
771  ret = this->rProcessFd(buffer);
772  }
773 
774  if (ret == Cfdp::Status::SUCCESS) {
775  /* class 2 does CRC at FIN, but track gaps */
776  this->m_chunks->chunks.add(fd.getOffset(), static_cast<FileSize>(fd.getDataSize()));
777 
778  if (this->m_flags.rx.fd_nak_sent) {
779  this->r2Complete(false); /* once nak-retransmit received, start checking for completion at each fd */
780  }
781 
782  if (!this->m_flags.rx.complete) {
783  this->m_engine->armAckTimer(this); /* re-arm ACK timer, since we got data */
784  }
785 
786  this->m_state_data.receive.r2.acknak_count = 0;
787  } else {
788  /* Reset transaction on failure */
789  this->r2Reset();
790  }
791 }
792 
793 void Transaction::r2GapCompute(const Chunk* chunk, NakPdu& nak) {
794  FW_ASSERT(chunk->size > 0, static_cast<FwAssertArgType>(chunk->size));
795 
796  // Calculate segment offsets relative to scope start
797  FileSize offsetStart = chunk->offset - nak.getScopeStart();
798  FileSize offsetEnd = offsetStart + chunk->size;
799 
800  // Add segment to NAK PDU (returns false if array is full)
801  nak.addSegment(offsetStart, offsetEnd);
802 }
803 
804 void Transaction::r2GapComputeWrapper(const Chunk* chunk, void* opaque) {
805  struct GapComputeContext {
806  Transaction* txn;
807  NakPdu* nak;
808  };
809  GapComputeContext* ctx = static_cast<GapComputeContext*>(opaque);
810  ctx->txn->r2GapCompute(chunk, *ctx->nak);
811 }
812 
813 Status::T Transaction::rSubstateSendNak() {
815 
816  // Create and initialize NAK PDU
817  NakPdu nakPdu;
819 
820  if (this->m_flags.rx.md_recv) {
821  // We have metadata, so send NAK with file data gaps
822  nakPdu.initialize(direction,
823  this->getClass(), // transmission mode
824  this->m_history->peer_eid, // source EID (receiver)
825  this->m_history->seq_num, // transaction sequence number
826  this->m_cfdpManager->getLocalEidParam(), // destination EID (sender)
827  0, // scope start
828  0 // scope end
829  );
830 
831  // Compute gaps and add segments to NAK PDU
832  U32 chunkCount = this->m_chunks->chunks.getCount();
833  U32 maxChunks = this->m_chunks->chunks.getMaxChunks();
834  U32 gapLimit = (chunkCount < maxChunks) ? maxChunks : (maxChunks - 1);
835 
836  // For each gap found, add it as a segment to the NAK PDU via callback
837  struct GapComputeContext {
838  Transaction* txn;
839  NakPdu* nak;
840  } gapCtx = {this, &nakPdu};
841 
842  U32 gapCount = this->m_chunks->chunks.computeGaps(static_cast<ChunkIdx>(gapLimit), this->m_fsize, 0,
843  &Transaction::r2GapComputeWrapper, &gapCtx);
844 
845  if (!gapCount) {
846  // No gaps left, file reception is complete
847  this->m_flags.rx.complete = true;
848  status = Cfdp::Status::SUCCESS;
849  } else {
850  // Gaps are present, send the NAK PDU
851  status = this->m_engine->sendNak(this, nakPdu);
852  if (status == Cfdp::Status::SUCCESS) {
853  this->m_flags.rx.fd_nak_sent = true;
854  this->m_cfdpManager->addSentNakSegmentRequests(this->m_chan_num, gapCount);
855  }
856  }
857  } else {
858  // Need to send NAK to request metadata PDU again
859  // Special case: scope start/end and segment[0] all zeros requests metadata
860  nakPdu.initialize(direction,
861  this->getClass(), // transmission mode
862  this->m_history->peer_eid, // source EID (receiver)
863  this->m_history->seq_num, // transaction sequence number
864  this->m_cfdpManager->getLocalEidParam(), // destination EID (sender)
865  0, // scope start (special value)
866  0 // scope end (special value)
867  );
868 
869  // Add special segment [0,0] to request metadata
870  nakPdu.addSegment(0, 0);
871 
872  status = this->m_engine->sendNak(this, nakPdu);
873  }
874 
875  return status;
876 }
877 
878 Status::T Transaction::r2CalcCrcChunk() {
879  U8 buf[R2CrcChunkSize];
880  FileSize count_bytes;
881  FileSize want_offs_size;
882  FwSizeType read_size;
883  Os::File::Status fileStatus;
885  FileSize rx_crc_calc_bytes_per_cycle = 0;
886 
887  memset(buf, 0, sizeof(buf));
888 
889  count_bytes = 0;
890 
891  // Open file for CRC calculation if needed
892  if (ret == Cfdp::Status::SUCCESS) {
893  if (this->m_state_data.receive.r2.rx_crc_calc_bytes == 0) {
894  this->m_crc = CFDP::Checksum(0);
895 
896  // For Class 2 RX, the file was opened in WRITE mode for receiving FileData PDUs.
897  // Now we need to READ it for CRC calculation. Close and reopen in READ mode.
898  if (this->m_fd.isOpen()) {
899  this->m_fd.close();
900  }
901 
902  fileStatus = this->m_fd.open(this->m_history->fnames.dst_filename.toChar(), Os::File::OPEN_READ);
903  if (fileStatus != Os::File::OP_OK) {
905  ret = Cfdp::Status::ERROR;
906  } else {
907  // Reset cached position since we just reopened the file
908  this->m_state_data.receive.cached_pos = 0;
909  }
910  }
911  }
912 
913  // Process file in chunks
914  if (ret == Cfdp::Status::SUCCESS) {
915  rx_crc_calc_bytes_per_cycle = this->m_cfdpManager->getRxCrcCalcBytesPerCycleParam();
916 
917  while ((ret == Cfdp::Status::SUCCESS) && (count_bytes < rx_crc_calc_bytes_per_cycle) &&
918  (this->m_state_data.receive.r2.rx_crc_calc_bytes < this->m_fsize)) {
919  want_offs_size = this->m_state_data.receive.r2.rx_crc_calc_bytes + static_cast<FileSize>(sizeof(buf));
920 
921  if (want_offs_size > this->m_fsize) {
922  read_size = this->m_fsize - this->m_state_data.receive.r2.rx_crc_calc_bytes;
923  } else {
924  read_size = sizeof(buf);
925  }
926 
927  if (this->m_state_data.receive.cached_pos != this->m_state_data.receive.r2.rx_crc_calc_bytes) {
928  fileStatus =
929  this->m_fd.seek(this->m_state_data.receive.r2.rx_crc_calc_bytes, Os::File::SeekType::ABSOLUTE);
930  if (fileStatus != Os::File::OP_OK) {
931  this->m_cfdpManager->log_WARNING_LO_RxSeekCrcFailed(
932  this->getClass(), this->m_history->src_eid, this->m_history->seq_num,
933  this->m_state_data.receive.r2.rx_crc_calc_bytes, fileStatus);
934  // this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_FILE_SIZE_ERROR);
935  this->m_cfdpManager->incrementFaultFileSeek(this->m_chan_num);
936  ret = Cfdp::Status::ERROR;
937  }
938  }
939 
940  if (ret == Cfdp::Status::SUCCESS) {
941  FwSizeType expected_read_size = read_size;
942  fileStatus = this->m_fd.read(buf, read_size, Os::File::WaitType::WAIT);
943  if (fileStatus != Os::File::OP_OK) {
944  this->m_cfdpManager->log_WARNING_LO_RxReadCrcFailed(
945  this->getClass(), this->m_history->src_eid, this->m_history->seq_num,
946  static_cast<U32>(expected_read_size), static_cast<I32>(read_size));
948  this->m_cfdpManager->incrementFaultFileRead(this->m_chan_num);
949  ret = Cfdp::Status::ERROR;
950  } else {
951  this->m_crc.update(buf, this->m_state_data.receive.r2.rx_crc_calc_bytes,
952  static_cast<U32>(read_size));
953  this->m_state_data.receive.r2.rx_crc_calc_bytes += static_cast<FileSize>(read_size);
954  this->m_state_data.receive.cached_pos = this->m_state_data.receive.r2.rx_crc_calc_bytes;
955  count_bytes += static_cast<FileSize>(read_size);
956 
957  // Reset inactivity timer to indicate transaction is actively processing
958  this->m_engine->armInactTimer(this);
959  }
960  }
961  }
962  }
963 
964  // Check final CRC if all bytes processed
965  if (ret == Cfdp::Status::SUCCESS) {
966  if (this->m_state_data.receive.r2.rx_crc_calc_bytes == this->m_fsize) {
967  /* all bytes calculated, so now check */
968  if (this->rCheckCrc(this->m_state_data.receive.r2.eof_crc) == Cfdp::Status::SUCCESS) {
969  /* CRC matched! We are happy */
970  this->m_keep = Cfdp::Keep::KEEP; /* save the file */
971 
972  /* set FIN PDU status */
975  } else {
977  }
978 
979  this->m_flags.com.crc_calc = true;
980  } else {
981  // Not all bytes processed yet, return ERROR to signal need to continue
982  ret = Cfdp::Status::ERROR;
983  }
984  }
985 
986  return ret;
987 }
988 
989 Status::T Transaction::r2SubstateSendFin() {
990  Status::T sret;
992 
993  if (!TxnStatusIsError(this->m_history->txn_stat) && !this->m_flags.com.crc_calc) {
994  /* no error, and haven't checked CRC -- so start checking it */
995  if (this->r2CalcCrcChunk()) {
996  ret = Cfdp::Status::ERROR; /* signal to caller to re-enter next tick */
997  }
998  }
999 
1000  if (ret != Cfdp::Status::ERROR) {
1001  sret = this->m_engine->sendFin(this, this->m_state_data.receive.r2.dc, this->m_state_data.receive.r2.fs,
1002  static_cast<ConditionCode>(TxnStatusToConditionCode(this->m_history->txn_stat)));
1003 
1004  /* Serialization error already logged in serializeAndSendPdu if ERROR returned */
1005  this->m_state_data.receive.sub_state =
1006  RxSubState::RX_SUB_STATE_CLOSEOUT_SYNC; /* whether or not FIN send successful, ok to transition state */
1007  if (sret != Cfdp::Status::SUCCESS) {
1008  ret = Cfdp::Status::ERROR;
1009  }
1010  }
1011 
1012  /* if no message, then try again next time */
1013  return ret;
1014 }
1015 
1016 void Transaction::r2RecvFinAck(const Fw::Buffer& buffer) {
1017  // Deserialize ACK PDU from buffer
1018  AckPdu ack;
1019  // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
1020  Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
1021  sb.setBuffLen(buffer.getSize());
1022 
1023  Fw::SerializeStatus deserStatus = ack.deserializeFrom(sb);
1024  if (deserStatus != Fw::FW_SERIALIZE_OK) {
1025  // Bad ACK PDU
1026  this->m_cfdpManager->log_WARNING_LO_FailAckPduDeserialization(this->getChannelId(),
1027  static_cast<I32>(deserStatus));
1028  this->m_cfdpManager->incrementRecvErrors(this->m_chan_num);
1029  return;
1030  }
1031 
1032  // ACK PDU has been validated during deserialization
1033  // Got fin-ack, so time to close the state
1034  this->r2Reset();
1035 }
1036 
1037 void Transaction::r2RecvMd(const Fw::Buffer& buffer) {
1038  Fw::String fname;
1039  Os::File::Status fileStatus;
1040  Os::FileSystem::Status fileSysStatus;
1041  bool success = true;
1042 
1043  /* it isn't an error to get another MD PDU, right? */
1044  if (!this->m_flags.rx.md_recv) {
1045  /* NOTE: this->m_flags.rx.md_recv always 1 in R1, so this is R2 only */
1046  /* parse the md PDU. this will overwrite the transaction's history, which contains our filename. so let's
1047  * save the filename in a local buffer so it can be used with moveFile upon successful parsing of
1048  * the md PDU */
1049  fname = this->m_history->fnames.dst_filename;
1050 
1051  // Deserialize Metadata PDU from buffer
1052  MetadataPdu md;
1053  // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
1054  Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
1055  sb.setBuffLen(buffer.getSize());
1056 
1057  Fw::SerializeStatus deserStatus = md.deserializeFrom(sb);
1058  if (deserStatus != Fw::FW_SERIALIZE_OK) {
1059  // Bad metadata PDU
1060  this->m_cfdpManager->log_WARNING_LO_FailMetadataPduDeserialization(this->getChannelId(),
1061  static_cast<I32>(deserStatus));
1062  return;
1063  }
1064 
1065  // PDU validation already done during deserialization
1066  this->m_engine->recvMd(this, md);
1067 
1068  /* successfully obtained md PDU */
1069  if (this->m_flags.rx.eof_recv) {
1070  /* EOF was received, so check that md and EOF sizes match */
1071  if (this->m_state_data.receive.r2.eof_size != this->m_fsize) {
1072  this->m_cfdpManager->log_WARNING_LO_RxEofMdSizeMismatch(this->getClass(), this->m_history->src_eid,
1073  this->m_history->seq_num, this->m_fsize,
1074  this->m_state_data.receive.r2.eof_size);
1075  this->m_cfdpManager->incrementFaultFileSizeMismatch(this->m_chan_num);
1077  success = false;
1078  }
1079  }
1080 
1081  if (success) {
1082  /* close and rename file */
1083  this->m_fd.close();
1084 
1085  fileSysStatus = Os::FileSystem::moveFile(fname.toChar(), this->m_history->fnames.dst_filename.toChar());
1086  if (fileSysStatus != Os::FileSystem::OP_OK) {
1087  this->m_cfdpManager->log_WARNING_LO_RxFileRenameFailed(
1088  this->getClass(), this->m_history->src_eid, this->m_history->seq_num, fname,
1089  this->m_history->fnames.dst_filename, fileSysStatus);
1091  this->m_cfdpManager->incrementFaultFileRename(this->m_chan_num);
1092  success = false;
1093  } else {
1094  // File was successfully renamed, open for writing
1095  fileStatus = this->m_fd.open(this->m_history->fnames.dst_filename.toChar(), Os::File::OPEN_WRITE);
1096  if (fileStatus != Os::File::OP_OK) {
1097  this->m_cfdpManager->log_WARNING_LO_RxFileReopenFailed(
1098  this->getClass(), this->m_history->src_eid, this->m_history->seq_num,
1099  this->m_history->fnames.dst_filename, fileStatus);
1101  this->m_cfdpManager->incrementFaultFileOpen(this->m_chan_num);
1102  success = false;
1103  }
1104  }
1105 
1106  if (success) {
1107  this->m_state_data.receive.cached_pos = 0; /* reset psn due to open */
1108  this->m_flags.rx.md_recv = true;
1109  this->m_state_data.receive.r2.acknak_count = 0; /* in case part of NAK */
1110  this->r2Complete(true); /* check for completion now that md is received */
1111  }
1112  }
1113  }
1114 }
1115 
1116 void Transaction::rSendInactivityEvent() {
1117  this->m_cfdpManager->log_WARNING_LO_RxInactivityTimeout(this->getClass(), this->m_history->src_eid,
1118  this->m_history->seq_num);
1119  this->m_cfdpManager->incrementFaultInactivityTimer(this->m_chan_num);
1120 }
1121 
1122 // ======================================================================
1123 // Dispatch Methods
1124 // ======================================================================
1125 
1126 void Transaction::rDispatchRecv(const Fw::Buffer& buffer, const RSubstateDispatchTable* dispatch, StateRecvFunc fd_fn) {
1127  StateRecvFunc selected_handler;
1128 
1130  static_cast<U8>(this->m_state_data.receive.sub_state),
1131  static_cast<U8>(RxSubState::RX_SUB_STATE_NUM_STATES));
1132 
1133  selected_handler = nullptr;
1134 
1135  // Peek at PDU type from buffer
1136  Cfdp::PduTypeEnum::T pduType = Cfdp::peekPduType(buffer);
1137 
1138  // Special handling for file data PDU
1139  if (pduType == Cfdp::PduTypeEnum::FILE_DATA) {
1140  /* For file data PDU, use the provided fd_fn */
1141  if (!TxnStatusIsError(this->m_history->txn_stat)) {
1142  selected_handler = fd_fn;
1143  }
1144  } else {
1145  // Not a file-data PDU - parse as a directive PDU to get the directive code.
1146  // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
1147  Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
1148  sb.setBuffLen(buffer.getSize());
1149 
1150  Cfdp::PduHeader header;
1151  if (header.fromSerialBuffer(sb) == Fw::FW_SERIALIZE_OK) {
1152  // Read directive code (first byte after header for directive PDUs)
1153  U8 directiveCodeByte;
1154  if (sb.deserializeTo(directiveCodeByte) == Fw::FW_SERIALIZE_OK) {
1155  FileDirective directiveCode = static_cast<FileDirective>(directiveCodeByte);
1156 
1157  if (directiveCode < FileDirective::FILE_DIRECTIVE_INVALID_MAX) {
1158  /* The CFDP_R_SubstateDispatchTable_t is only used with file directive PDU */
1159  if (dispatch->state[static_cast<U32>(this->m_state_data.receive.sub_state)] != nullptr) {
1160  selected_handler = dispatch->state[static_cast<U32>(this->m_state_data.receive.sub_state)]
1161  ->fdirective[static_cast<U32>(directiveCode)];
1162  }
1163  } else {
1164  this->m_cfdpManager->incrementRecvSpurious(this->m_chan_num);
1165  this->m_cfdpManager->log_WARNING_LO_RxInvalidDirectiveCode(
1166  this->getClass(), this->m_history->src_eid, this->m_history->seq_num, directiveCodeByte,
1167  static_cast<U8>(this->m_state_data.receive.sub_state));
1168  }
1169  }
1170  }
1171  }
1172 
1173  /*
1174  * NOTE: if no handler is selected, this will drop packets on the floor here.
1175  */
1176  if (selected_handler != nullptr) {
1177  (this->*selected_handler)(buffer);
1178  } else {
1179  this->m_cfdpManager->incrementRecvDropped(this->m_chan_num);
1180  }
1181 }
1182 
1183 } // namespace Cfdp
1184 } // namespace Ccsds
1185 } // namespace Svc
Status::T recvFd(Transaction *txn, const FileDataPdu &pdu)
Unpack a file data PDU from a received message.
Definition: Engine.cpp:412
void incrementFaultFileWrite(U8 chanId)
Increment fault file write counter.
Serialization/Deserialization operation was successful.
CfdpTxnFilenames fnames
file names associated with this history entry
Definition: Types.hpp:245
CFDP Protocol Engine.
Definition: Engine.hpp:89
TransactionInitType
Transaction initiation method.
Definition: Types.hpp:165
#define CFDP_PRI_TRANSACTION_SEQ
Macro type for transaction sequences that is used in printf style formatting.
Definition: CfdpCfg.hpp:76
void incrementRecvErrors(U8 chanId)
Increment receive error counter.
CFDP Channel class.
Definition: Channel.hpp:56
Enum used to determine if a file should be kept or deleted after a CFDP transaction.
Definition: KeepEnumAc.hpp:22
A variable-length serializable buffer.
TxnState
High-level state of a transaction.
Definition: Types.hpp:116
void log_WARNING_LO_FailAckPduDeserialization(U8 channelId, I32 status) const
Log event FailAckPduDeserialization.
void log_WARNING_LO_FailFileDataPduDeserialization(U8 channelId, I32 status) const
Log event FailFileDataPduDeserialization.
void log_WARNING_LO_RxEofWithError(const Svc::Ccsds::Cfdp::Class &cfdpClass, U32 srcEid, U32 seqNum, U8 conditionCode) const
Log event RxEofWithError.
PlatformSizeType FwSizeType
static Status moveFile(const char *sourcePath, const char *destPath)
Move a file from sourcePath to destPath.
Definition: FileSystem.cpp:209
void log_ACTIVITY_LO_RxTempFileCreated(const Svc::Ccsds::Cfdp::Class &cfdpClass, U32 srcEid, U32 seqNum, const Fw::StringBase &filename) const
Log event RxTempFileCreated.
void incrementRecvEofCanceled(U8 chanId)
Increment receive EOF canceled counter.
void rCancel()
Cancel an R transaction.
Fw::SerializeStatus deserializeFrom(Fw::SerialBufferBase &buffer, Fw::Endianness mode=Fw::Endianness::BIG) override
Fw::Serializable interface - deserialize from buffer.
void recvMd(Transaction *txn, const MetadataPdu &pdu)
Handle receipt of metadata PDU.
Definition: Engine.cpp:400
void r2Reset()
CFDP R2 transaction reset function.
void r2Recv(const Fw::Buffer &buffer)
R2 receive PDU processing.
void log_WARNING_LO_RxAckLimitReached(const Svc::Ccsds::Cfdp::Class &cfdpClass, U32 srcEid, U32 seqNum) const
Log event RxAckLimitReached.
void r2Complete(I32 ok_to_send_nak)
Checks R2 transaction state for transaction completion status.
Receive PDU: Invalid EOF packet.
Transaction initiated via command interface.
void log_WARNING_LO_RxSeekCrcFailed(const Svc::Ccsds::Cfdp::Class &cfdpClass, U32 srcEid, U32 seqNum, U32 offset, I32 status) const
Log event RxSeekCrcFailed.
U8 getNackLimitParam(U8 channelIndex)
const FileDirectiveDispatchTable * state[static_cast< U32 >(RxSubState::RX_SUB_STATE_NUM_STATES)]
void disableTimer(void)
Disables a CFDP timer.
Definition: Timer.cpp:32
void incrementFaultCrcMismatch(U8 chanId)
Increment fault CRC mismatch counter.
U8 * getData() const
Definition: Buffer.cpp:56
Overwrite file when it exists and creation was requested.
Definition: File.hpp:60
Open file for writing.
Definition: File.hpp:35
void log_WARNING_LO_RxFileSizeMismatch(const Svc::Ccsds::Cfdp::Class &cfdpClass, U32 srcEid, U32 seqNum, U32 expected, U32 actual) const
Log event RxFileSizeMismatch.
void incrementFaultFileRename(U8 chanId)
Increment fault file rename counter.
void log_WARNING_LO_RxWriteFailed(const Svc::Ccsds::Cfdp::Class &cfdpClass, U32 srcEid, U32 seqNum, U32 expected, I32 actual) const
Log event RxWriteFailed.
CfdpChunkList chunks
Chunk list for gap tracking.
Definition: Types.hpp:261
T
The raw enum type.
CFDP class 1 - Unreliable transfer (Unacknowledged)
Definition: ClassEnumAc.hpp:46
Transaction(Channel *channel, U8 channelId, Engine *engine, CfdpManager *manager)
void addSentNakSegmentRequests(U8 chanId, U32 count)
Add to sent NAK segment requests.
void rTick(I32 *cont)
Perform tick (time-based) processing for R transactions.
U8 post_inactivity_send_retries
terminal-send retries attempted after inactivity fired
Definition: Types.hpp:366
Status::T sendAck(Transaction *txn, AckTxnStatus ts, FileDirective dir_code, ConditionCode cc, EntityId peer_eid, TransactionSeq tsn)
Create, encode, and send an ACK (Acknowledgment) PDU.
Definition: Engine.cpp:257
void incrementRecvDropped(U8 chanId)
Increment receive dropped counter.
TxnStatus
Values for Transaction Status code.
Definition: Types.hpp:193
#define CFDP_PRI_ENTITY_ID
Macro type for Entity id that is used in printf style formatting.
Definition: CfdpCfg.hpp:69
void log_WARNING_LO_RxNakLimitReached(const Svc::Ccsds::Cfdp::Class &cfdpClass, U32 srcEid, U32 seqNum) const
Log event RxNakLimitReached.
bool md_recv
md received for r state
Definition: Types.hpp:375
Fw::SerializeStatus fromSerialBuffer(Fw::SerialBufferBase &serialBuffer)
Initialize this Header from a SerialBufferBase.
Definition: PduHeader.cpp:163
SerializeStatus
forward declaration for string
void incrementRecvSpurious(U8 chanId)
Increment receive spurious counter.
void finishTransaction(Transaction *txn, bool keep_history)
Finish a transaction.
Definition: Engine.cpp:977
Os::FileInterface::Status open(const char *path, Mode mode)
open file with supplied path and mode
Definition: File.cpp:43
State assigned to a transaction after freeing it.
U8 getChannelId() const
Get channel ID.
void incrementFaultFileSeek(U8 chanId)
Increment fault file seek counter.
Class representing a 32-bit checksum as mandated by the CCSDS File Delivery Protocol.
Definition: Checksum.hpp:53
U8 getAckLimitParam(U8 channelIndex)
void add(FileSize offset, FileSize size)
Add a chunk (file segment) to the list.
Definition: Chunk.cpp:63
Status getStatus(void)
Get the status of a CFDP timer.
Definition: Timer.cpp:37
The type of a File Data PDU.
Definition: FileDataPdu.hpp:19
void r1Reset()
CFDP R1 transaction reset function.
Status::T sendNak(Transaction *txn, NakPdu &nakPdu)
Encode and send a NAK (Negative Acknowledgment) PDU.
Definition: Engine.cpp:327
void incrementFaultFileOpen(U8 chanId)
Increment fault file open counter.
CfdpFlagsRx rx
applies to only receive file transactions
Definition: Types.hpp:403
Status seek(FwSignedSizeType offset, SeekType seekType) override
seek the file pointer to the given offset
Definition: File.cpp:135
FileSize getOffset() const
Get the file offset.
Definition: FileDataPdu.hpp:65
void log_WARNING_LO_RxFileCreateFailed(const Svc::Ccsds::Cfdp::Class &cfdpClass, U32 srcEid, U32 seqNum, const Fw::StringBase &filename, I32 status) const
Log event RxFileCreateFailed.
File will be kept after the CFDP transaction.
Definition: KeepEnumAc.hpp:40
void armInactTimer(Transaction *txn)
Arm the inactivity timer for a transaction.
Definition: Engine.cpp:109
void reset()
Reset transaction to default state.
ConditionCode TxnStatusToConditionCode(TxnStatus txn_stat)
Converts the internal transaction status to a CFDP condition code.
Definition: Utils.cpp:120
void log_WARNING_LO_RxFileReopenFailed(const Svc::Ccsds::Cfdp::Class &cfdpClass, U32 srcEid, U32 seqNum, const Fw::StringBase &filename, I32 status) const
Log event RxFileReopenFailed.
bool fd_nak_sent
latches that at least one NAK has been sent for file data
Definition: Types.hpp:381
State assigned to an unused object on the free list.
U16 getDataSize() const
Get the data size.
Definition: FileDataPdu.hpp:68
void setTxnStatus(Transaction *txn, TxnStatus txn_stat)
Helper function to store transaction status code only.
Definition: Engine.cpp:1070
void close() override
close the file, if not opened then do nothing
Definition: File.cpp:90
Generic CFDP error return code.
void update(const U8 *const data, const U32 offset, const U32 length)
Definition: Checksum.cpp:49
void log_WARNING_LO_FailEofPduDeserialization(U8 channelId, I32 status) const
Log event FailEofPduDeserialization.
Status write(const U8 *buffer, FwSizeType &size)
write data to this file from the supplied buffer bounded by size
Definition: File.cpp:206
void incrementFaultInactivityTimer(U8 chanId)
Increment fault inactivity timer counter.
const char * toChar() const
Convert to a C-style char*.
U32 getValue() const
Get the checksum value.
Definition: Checksum.cpp:45
void log_WARNING_LO_RxSeekFailed(const Svc::Ccsds::Cfdp::Class &cfdpClass, U32 srcEid, U32 seqNum, U32 offset, I32 status) const
Log event RxSeekFailed.
FormatStatus format(const CHAR *formatString,...)
write formatted string to buffer
Definition: StringBase.cpp:58
U32 FileSize
File size and offset type.
Receive PDU: EOF file size mismatch.
void rAckTimerTick()
Perform acknowledgement timer tick (time-based) processing for R transactions.
uint8_t U8
8-bit unsigned integer
Definition: BasicTypes.h:54
void log_WARNING_LO_RxEofMdSizeMismatch(const Svc::Ccsds::Cfdp::Class &cfdpClass, U32 srcEid, U32 seqNum, U32 mdSize, U32 eofSize) const
Log event RxEofMdSizeMismatch.
CFDP operation has been successful.
void log_ACTIVITY_HI_RxEofCancelReceived(const Svc::Ccsds::Cfdp::Class &cfdpClass, U32 srcEid, U32 seqNum) const
Log event RxEofCancelReceived.
const U8 * getData() const
Get the data pointer.
Definition: FileDataPdu.hpp:71
Status read(U8 *buffer, FwSizeType &size)
read data from this file into supplied buffer bounded by size
Definition: File.cpp:187
FwSizeType getSize() const
Definition: Buffer.cpp:60
void incrementFaultNakLimit(U8 chanId)
Increment fault NAK limit counter.
void log_WARNING_LO_RxFileRenameFailed(const Svc::Ccsds::Cfdp::Class &cfdpClass, U32 srcEid, U32 seqNum, const Fw::StringBase &tempFile, const Fw::StringBase &finalFile, I32 status) const
Log event RxFileRenameFailed.
Class::T getClass() const
Get transaction class (CLASS_1 or CLASS_2)
A dispatch table for receive file transactions, receive side.
TransactionSeq seq_num
transaction identifier, stays constant for entire transfer
Definition: Types.hpp:251
void incrementFaultFileRead(U8 chanId)
Increment fault file read counter.
Operation was successful.
Definition: File.hpp:42
void rInit()
Initialize a transaction structure for R.
void log_WARNING_LO_RxCrcMismatch(const Svc::Ccsds::Cfdp::Class &cfdpClass, U32 srcEid, U32 seqNum, U32 expected, U32 actual) const
Log event RxCrcMismatch.
The type of a PDU header (common to all PDUs)
Definition: PduHeader.hpp:47
void log_WARNING_LO_RxFileDataOutOfBounds(const Svc::Ccsds::Cfdp::Class &cfdpClass, U32 srcEid, U32 seqNum, U32 offset, U32 dataSize, U32 fileSize) const
Log event RxFileDataOutOfBounds.
void run(void)
Runs a one second increment of the CFDP timers.
Definition: Timer.cpp:41
PduTypeEnum::T peekPduType(const Fw::Buffer &buffer)
Definition: PduHeader.cpp:230
Open file for reading.
Definition: File.hpp:33
void incrementFaultRxEofError(U8 chanId)
Increment receive EOF error counter (any condition code that is not no-error or cancel) ...
bool inactivity_fired
set whenever the inactivity timeout expires
Definition: Types.hpp:364
RateGroupDivider component implementation.
void log_WARNING_LO_RxInvalidDirectiveCode(const Svc::Ccsds::Cfdp::Class &cfdpClass, U32 srcEid, U32 seqNum, U8 directiveCode, U8 substate) const
Log event RxInvalidDirectiveCode.
ChunkIdx getMaxChunks() const
Get the maximum number of chunks this list can hold.
Definition: Chunk.hpp:182
void rDispatchRecv(const Fw::Buffer &buffer, const RSubstateDispatchTable *dispatch, StateRecvFunc fd_fn)
Dispatch function for received PDUs on receive-file transactions.
CfdpFlagsCommon com
applies to all transactions
Definition: Types.hpp:402
void r2SetFinTxnStatus(TxnStatus txn_stat)
Helper function to store transaction status code and set send_fin flag.
TxnStatus txn_stat
final status of operation
Definition: Types.hpp:248
Fw::String getTmpDirParam(U8 channelIndex)
Operation was successful.
Definition: FileSystem.hpp:24
A table of receive handler functions based on file directive code.
void log_WARNING_LO_RxReadCrcFailed(const Svc::Ccsds::Cfdp::Class &cfdpClass, U32 srcEid, U32 seqNum, U32 expected, I32 actual) const
Log event RxReadCrcFailed.
Status::T rCheckCrc(U32 expected_crc)
Checks that the transaction file&#39;s CRC matches expected.
EntityId src_eid
the source eid of the transaction
Definition: Types.hpp:249
void log_WARNING_LO_RxInactivityTimeout(const Svc::Ccsds::Cfdp::Class &cfdpClass, U32 srcEid, U32 seqNum) const
Log event RxInactivityTimeout.
Status::T sendFin(Transaction *txn, FinDeliveryCode dc, FinFileStatus fs, ConditionCode cc)
Create, encode, and send a FIN (Finished) PDU.
Definition: Engine.cpp:300
SerializeStatus setBuffLen(Serializable::SizeType length) override
Set buffer length manually.
void incrementFaultFileSizeMismatch(U8 chanId)
Increment fault file size mismatch counter.
CfdpRxStateData receive
applies to only receive file transactions
Definition: Types.hpp:412
U8 eof_cc
remember the cc in the received EOF PDU to echo in eof-ack
Definition: Types.hpp:341
void(Transaction::*)(const Fw::Buffer &buffer) StateRecvFunc
A member function pointer for dispatching actions to a handler, with existing PDU data...
Definition: Transaction.hpp:82
void armAckTimer(Transaction *txn)
Arm the ACK timer for a transaction.
Definition: Engine.cpp:104
ChunkIdx getCount() const
Get the current number of chunks in the list.
Definition: Chunk.hpp:176
void recycleTransaction(Transaction *txn)
Recover resources associated with a transaction.
Definition: Channel.cpp:620
#define FW_ASSERT(...)
Definition: Assert.hpp:14
void incrementFaultAckLimit(U8 chanId)
Increment fault ACK limit counter.
bool isOpen() const
determine if the file is open
Definition: File.cpp:98
bool TxnStatusIsError(TxnStatus txn_stat)
Check if the internal transaction status represents an error.
Definition: Utils.cpp:113
void r1Recv(const Fw::Buffer &buffer)
R1 receive PDU processing.
U32 computeGaps(ChunkIdx maxGaps, FileSize total, FileSize start, GapComputeCallback callback, void *opaque) const
Compute gaps between chunks and invoke callback for each.
Definition: Chunk.cpp:94
EntityId peer_eid
peer_eid is always the "other guy", same src_eid for RX
Definition: Types.hpp:250
void log_WARNING_LO_FailMetadataPduDeserialization(U8 channelId, I32 status) const
Log event FailMetadataPduDeserialization.
Status::T recvEof(Transaction *txn, const EofPdu &pdu)
Unpack an EOF PDU from a received message.
Definition: Engine.cpp:430
void addRecvFileDataBytes(U8 chanId, U32 bytes)
Add to received file data bytes.
Open file for writing and truncates file if it exists, ie same flags as creat()
Definition: File.hpp:34