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