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