F´ Flight Software - C/C++ Documentation
A framework for building embedded system applications to NASA flight quality standards.
Channel.cpp
Go to the documentation of this file.
1 // ======================================================================
2 // \title Channel.cpp
3 // \brief CFDP Channel operations implementation
4 //
5 // This file is a port of channel-specific functions from the following files
6 // from the NASA Core Flight System (cFS) CFDP (CF) Application, version 3.0.0,
7 // adapted for use within the F-Prime (F') framework:
8 // - cf_cfdp.c (channel processing functions)
9 // - cf_utils.c (channel transaction and resource management)
10 //
11 // ======================================================================
12 //
13 // NASA Docket No. GSC-18,447-1
14 //
15 // Copyright (c) 2019 United States Government as represented by the
16 // Administrator of the National Aeronautics and Space Administration.
17 // All Rights Reserved.
18 //
19 // Licensed under the Apache License, Version 2.0 (the "License");
20 // you may not use this file except in compliance with the License.
21 // You may obtain a copy of the License at
22 //
23 // http://www.apache.org/licenses/LICENSE-2.0
24 //
25 // Unless required by applicable law or agreed to in writing, software
26 // distributed under the License is distributed on an "AS IS" BASIS,
27 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
28 // See the License for the specific language governing permissions and
29 // limitations under the License.
30 //
31 // ======================================================================
32 
33 #include <string.h>
34 #include <new>
35 
36 #include <Fw/FPrimeBasicTypes.hpp>
37 
42 
43 namespace Svc {
44 namespace Ccsds {
45 namespace Cfdp {
46 
47 // ----------------------------------------------------------------------
48 // Construction
49 // ----------------------------------------------------------------------
50 
52  U8 channelId,
53  CfdpManager* cfdpManager,
54  Fw::MemAllocator& allocator,
55  FwEnumStoreType memId)
56  : m_engine(engine),
57  m_numCmdTx(0),
58  m_currentTxn(nullptr),
59  m_cfdpManager(cfdpManager),
60  m_tickType(0),
61  m_channelId(channelId),
62  m_flowState(Cfdp::Flow::NOT_FROZEN),
63  m_outgoingCounter(0),
64  m_transactions(nullptr),
65  m_histories(nullptr),
66  m_chunks(nullptr),
67  m_chunkMem(nullptr) {
68  FW_ASSERT(engine != nullptr);
69  FW_ASSERT(cfdpManager != nullptr);
70 
71  // Initialize queue pointers
72  for (U32 i = 0; i < QueueId::NUM; i++) {
73  m_qs[i] = nullptr;
74  }
75 
76  // Initialize command/history lists
77  for (U32 i = 0; i < static_cast<U32>(Direction::DIRECTION_NUM); i++) {
78  m_cs[i] = nullptr;
79  }
80 
81  // Initialize poll directory playback state
82  for (U32 i = 0; i < MaxPollingDirPerChan; i++) {
83  m_polldir[i].enabled = Fw::Enabled::DISABLED;
84  m_polldir[i].pb.busy = false;
85  m_polldir[i].pb.diropen = false;
86  m_polldir[i].pb.counted = false;
87  m_polldir[i].pb.num_ts = 0;
88  m_polldir[i].pb.pending_file = "";
89  }
90 
91  // Initialize playback structures
92  for (U32 i = 0; i < MaxCommandedPlaybackDirectoriesPerChan; i++) {
93  m_playback[i].busy = false;
94  m_playback[i].diropen = false;
95  m_playback[i].counted = false;
96  m_playback[i].num_ts = 0;
97  m_playback[i].pending_file = "";
98  }
99 
100  // Allocate and initialize per-channel resources
101  U32 j, k;
102  History* history;
103  Transaction* txn;
104  CfdpChunkWrapper* cw;
105  CListNode** list_head;
106  U32 chunk_mem_offset = 0;
107  U32 total_chunks_needed;
108 
109  // Initialize chunk configuration for this channel
110  const U32 rxChunksPerChannel[] = CFDP_CHANNEL_NUM_RX_CHUNKS_PER_TRANSACTION;
111  const U32 txChunksPerChannel[] = CFDP_CHANNEL_NUM_TX_CHUNKS_PER_TRANSACTION;
112  m_dirMaxChunks[static_cast<U32>(Direction::DIRECTION_RX)] = rxChunksPerChannel[m_channelId];
113  m_dirMaxChunks[static_cast<U32>(Direction::DIRECTION_TX)] = txChunksPerChannel[m_channelId];
114 
115  // Calculate total chunks needed for this channel
116  total_chunks_needed = 0;
117  for (k = 0; k < static_cast<U32>(Direction::DIRECTION_NUM); ++k) {
118  total_chunks_needed += m_dirMaxChunks[k] * CFDP_NUM_TRANSACTIONS_PER_CHANNEL;
119  }
120 
121  // Allocate arrays using the provided allocator
122  FwSizeType transactionsSize = CFDP_NUM_TRANSACTIONS_PER_CHANNEL * sizeof(Transaction);
123  m_transactions = static_cast<Transaction*>(allocator.allocate(memId, transactionsSize));
124  FW_ASSERT(m_transactions != nullptr);
125 
126  FwSizeType chunksSize =
128  m_chunks = static_cast<CfdpChunkWrapper*>(allocator.allocate(memId, chunksSize));
129  FW_ASSERT(m_chunks != nullptr);
130 
131  FwSizeType historiesSize = NumHistoriesPerChannel * sizeof(History);
132  m_histories = static_cast<History*>(allocator.allocate(memId, historiesSize));
133  FW_ASSERT(m_histories != nullptr);
134 
135  FwSizeType chunkMemSize = total_chunks_needed * sizeof(Chunk);
136  m_chunkMem = static_cast<Chunk*>(allocator.allocate(memId, chunkMemSize));
137  FW_ASSERT(m_chunkMem != nullptr);
138 
139  // Initialize transactions using placement new with parameterized constructor
140  cw = m_chunks;
141  for (j = 0; j < CFDP_NUM_TRANSACTIONS_PER_CHANNEL; ++j) {
142  // Construct transaction in-place with parameterized constructor
143  txn = new (&m_transactions[j]) Transaction(this, m_channelId, m_engine, m_cfdpManager);
144 
145  // Put transaction on free list
146  this->freeTransaction(txn);
147 
148  // Initialize chunk wrappers for this transaction (TX and RX)
149  for (k = 0; k < static_cast<U32>(Direction::DIRECTION_NUM); ++k, ++cw) {
150  list_head = this->getChunkListHead(static_cast<U8>(k));
151 
152  // Use placement new to construct CfdpChunkWrapper with the new class-based interface
153  new (cw) CfdpChunkWrapper(static_cast<ChunkIdx>(m_dirMaxChunks[k]), &m_chunkMem[chunk_mem_offset]);
154  chunk_mem_offset += m_dirMaxChunks[k];
156  CfdpCListInsertBack(list_head, &cw->cl_node);
157  }
158  }
159 
160  // Initialize histories using placement new (History contains Fw::String which needs proper construction)
161  for (j = 0; j < NumHistoriesPerChannel; ++j) {
162  history = new (&m_histories[j]) History(); // Use placement new with default constructor
163  CfdpCListInitNode(&history->cl_node);
164  this->insertBackInQueue(QueueId::HIST_FREE, &history->cl_node);
165  }
166 }
167 
169  // Cleanup should have been called before destruction
170  // This is enforced by Engine::~Engine()
171 }
172 
174  // Call destructors and deallocate all internal arrays
175  if (m_transactions != nullptr) {
176  // Manually call destructors since we used placement new
177  for (U32 j = 0; j < CFDP_NUM_TRANSACTIONS_PER_CHANNEL; ++j) {
178  m_transactions[j].~Transaction();
179  }
180  allocator.deallocate(memId, m_transactions);
181  m_transactions = nullptr;
182  }
183 
184  if (m_chunks != nullptr) {
185  // Manually call destructors since we used placement new
186  for (U32 j = 0; j < (CFDP_NUM_TRANSACTIONS_PER_CHANNEL * static_cast<U32>(Direction::DIRECTION_NUM)); ++j) {
187  m_chunks[j].~CfdpChunkWrapper();
188  }
189  allocator.deallocate(memId, m_chunks);
190  m_chunks = nullptr;
191  }
192 
193  if (m_histories != nullptr) {
194  // Call destructors on History objects
195  for (U32 j = 0; j < NumHistoriesPerChannel; ++j) {
196  m_histories[j].~History();
197  }
198  allocator.deallocate(memId, m_histories);
199  m_histories = nullptr;
200  }
201 
202  if (m_chunkMem != nullptr) {
203  allocator.deallocate(memId, m_chunkMem);
204  m_chunkMem = nullptr;
205  }
206 }
207 
208 // ----------------------------------------------------------------------
209 // Channel Processing
210 // ----------------------------------------------------------------------
211 
213  Transaction* txn;
214  CycleTxArgs args;
215 
216  if (m_cfdpManager->getDequeueEnabledParam(m_channelId)) {
217  args.chan = this;
218  args.ran_one = 0;
219 
220  // loop through as long as there are pending transactions, and a message buffer to send their PDUs on
221 
222  // NOTE: tick processing is higher priority than sending new filedata PDUs, so only send however many
223  // PDUs that can be sent once we get to here
224  if (!this->m_currentTxn) { // don't enter if currentTxn is set, since we need to pick up where we left off on
225  // tick processing next scheduler cycle
226 
227  // Process pending transactions until queue is empty or something runs
228  while (true) {
229  // Context for static wrapper: pass both Channel* and CycleTxArgs*
230  struct CycleTxContext {
231  Channel* channel;
232  CycleTxArgs* args;
233  } cycleTxCtx = {this, &args};
234 
235  // Attempt to run something on TXA
237 
238  // Keep going until QueueId::PEND is empty or something is run
239  if (args.ran_one || m_qs[QueueId::PEND] == nullptr) {
240  break;
241  }
242 
243  txn = container_of_cpp(m_qs[QueueId::PEND], &Transaction::m_cl_node);
244 
245  // Class 2 transactions need a chunklist for NAK processing, get one now.
246  // Class 1 transactions don't need chunks since they don't support NAKs.
247  if (txn->getClass() == Cfdp::Class::CLASS_2) {
248  if (txn->m_chunks == nullptr) {
249  txn->m_chunks = this->findUnusedChunks(Direction::DIRECTION_TX);
250  }
251  if (txn->m_chunks == nullptr) {
252  // Chunklist unavailable - EVR already emitted by Engine
253  // Leave transaction pending until a chunklist is available.
254  break;
255  }
256  }
257 
258  m_engine->armInactTimer(txn);
259  this->moveTransaction(txn, QueueId::TXA);
260  }
261  }
262 
263  // in case the loop exited due to no message buffers, clear it and start from the top next time
264  this->m_currentTxn = nullptr;
265  }
266 }
267 
269  bool reset = true;
270 
271  void (Transaction::* fns[static_cast<U8>(CfdpTickType::CFDP_TICK_TYPE_NUM_TYPES)])(I32*) = {
274 
275  FW_ASSERT(m_tickType < static_cast<U8>(CfdpTickType::CFDP_TICK_TYPE_NUM_TYPES), m_tickType);
276 
277  for (; m_tickType < static_cast<U8>(CfdpTickType::CFDP_TICK_TYPE_NUM_TYPES); ++m_tickType) {
278  TickArgs args = {this, fns[m_tickType], 0, 0};
279 
280  // Safety bound: retry loop should not exceed the number of transactions in the queue
281  // Each retry processes one transaction that may request continuation
282  constexpr U32 maxRetries = MaxSimultaneousRx + MaxCommandedPlaybackFilesPerChan +
285 
286  for (U32 retry = 0; retry < maxRetries; ++retry) {
287  args.cont = 0;
288 
289  // Context for static wrapper: pass both Channel* and TickArgs*
290  struct TickContext {
291  Channel* channel;
292  TickArgs* args;
293  } tickCtx = {this, &args};
294 
295  CfdpCListTraverse(m_qs[qs[m_tickType]], &Channel::doTickWrapper, &tickCtx);
296 
297  if (args.early_exit) {
298  // early exit means we ran out of available outgoing messages this scheduler cycle.
299  // If current tick type is NAK response, then reset tick type. It would be
300  // bad to let NAK response starve out RX or TXW ticks on the next cycle.
301  //
302  // If RX ticks use up all available messages, then we pick up where we left
303  // off on the next cycle. (This causes some RX tick counts to be missed,
304  // but that's ok. Precise timing isn't required.)
305  //
306  // This scheme allows the following priority for use of outgoing messages:
307  //
308  // RX state messages
309  // TXW state messages
310  // NAK response (could be many)
311  //
312  // New file data on TXA
313  if (m_tickType != static_cast<U8>(CfdpTickType::CFDP_TICK_TYPE_TXW_NAK)) {
314  reset = false;
315  }
316 
317  break;
318  }
319 
320  if (!args.cont) {
321  break; // No continuation requested, exit retry loop
322  }
323  }
324 
325  if (!reset) {
326  break;
327  }
328  }
329 
330  if (reset) {
331  m_tickType = static_cast<U8>(CfdpTickType::CFDP_TICK_TYPE_RX); // reset tick type
332  }
333 }
334 
336  U32 i;
337  U8 playback_count = 0;
338 
339  for (i = 0; i < MaxCommandedPlaybackDirectoriesPerChan; ++i) {
340  this->processPlaybackDirectory(&m_playback[i]);
341  // Count active playback operations
342  if (m_playback[i].busy) {
343  playback_count++;
344  }
345  }
346 
347  // Update playback counter telemetry
348  Cfdp::ChannelTelemetry& tlm = m_engine->getChannelTelemetryRef(m_channelId);
349  tlm.set_playbackCounter(playback_count);
350 }
351 
353  CfdpPollDir* pd;
354  U32 i;
355  U8 poll_count = 0;
356 
357  for (i = 0; i < MaxPollingDirPerChan; ++i) {
358  pd = &m_polldir[i];
359 
360  if (pd->enabled) {
361  poll_count++;
362 
363  if ((pd->pb.busy == false) && (pd->pb.num_ts == 0)) {
364  if (pd->intervalTimer.getStatus() == Timer::Status::EXPIRED) {
365  // the timer has expired, so initiate a playback of the directory.
366  // The return status is intentionally ignored: playbackDirInitiate
367  // already emits an event on failure and the timer is re-armed
368  // below regardless so polling retries after the interval.
369  (void)m_engine->playbackDirInitiate(&pd->pb, pd->srcDir, pd->dstDir, pd->cfdpClass,
370  Cfdp::Keep::DELETE, m_channelId, pd->priority, pd->destEid);
371  // re-arm the timer for the next interval. The timer only ticks
372  // down while the playback is not busy.
373  if (pd->intervalSec > 0) {
375  }
376  } else {
377  pd->intervalTimer.run();
378  }
379  } else {
380  // playback is active, so step it
381  this->processPlaybackDirectory(&pd->pb);
382  }
383  }
384  }
385 
386  // Update poll counter telemetry
387  Cfdp::ChannelTelemetry& tlm = m_engine->getChannelTelemetryRef(m_channelId);
388  tlm.set_pollCounter(poll_count);
389 }
390 
391 // ----------------------------------------------------------------------
392 // Transaction Management
393 // ----------------------------------------------------------------------
394 
396  CListNode* node;
397  Transaction* txn;
398  QueueId::T q_index; // initialized below in if
399 
400  if (m_qs[QueueId::FREE]) {
401  node = m_qs[QueueId::FREE];
402  txn = container_of_cpp(node, &Transaction::m_cl_node);
403 
404  this->removeFromQueue(QueueId::FREE, &txn->m_cl_node);
405 
406  // now that a transaction is acquired, must also acquire a history slot to go along with it
407  if (m_qs[QueueId::HIST_FREE]) {
408  q_index = QueueId::HIST_FREE;
409  } else {
410  // no free history, so take the oldest one from the channel's history queue
411  FW_ASSERT(m_qs[QueueId::HIST]);
412  q_index = QueueId::HIST;
413  }
414 
415  txn->m_history = container_of_cpp(m_qs[q_index], &History::cl_node);
416 
417  this->removeFromQueue(q_index, &txn->m_history->cl_node);
418 
419  // Reset all history fields to initial state (matches constructor zero-init)
420  // This is necessary when recycling from HIST queue to clear stale data
421  txn->m_history->txn_stat = TxnStatus::TXN_STATUS_UNDEFINED; // Critical: prevents error status inheritance
422  txn->m_history->src_eid = 0;
423  txn->m_history->peer_eid = 0;
424  txn->m_history->seq_num = 0;
425  txn->m_history->fnames.src_filename = "";
426  txn->m_history->fnames.dst_filename = "";
427  // Note: cl_node is managed by queue operations (already handled by removeFromQueue)
428  // Note: dir is explicitly set below (already handled)
429 
430  // Indicate that this was freshly pulled from the free list
431  // notably this state is distinguishable from items still on the free list
432  txn->m_state = TxnState::TXN_STATE_INIT;
433 
434  // Clear the FREE tag now that this transaction has been taken off the FREE
435  // list. freeTransaction() marks q_index == QueueId::FREE for anything sitting
436  // on the free list; leaving that tag set on an acquired-but-not-yet-enqueued
437  // transaction would break the invariant relied on by
438  // Engine::finishTransaction()'s double-free guard (a live txn must never look
439  // FREE). The caller (startRxTransaction / txFileInitiate) will assign the real
440  // queue via insertSortPrio()/direct assignment; until then PEND (0) is the
441  // neutral, not-on-FREE-list default that matches reset()'s zeroed m_flags.
442  txn->m_flags.com.q_index = QueueId::PEND;
443 
444  txn->m_history->dir = direction;
445  txn->m_chan = this; // Set channel pointer
446 
447  // Re-initialize the linked list node to clear stale pointers from FREE list
448  CfdpCListInitNode(&txn->m_cl_node);
449  } else {
450  txn = nullptr;
451  }
452 
453  return txn;
454 }
455 
457  // need to find transaction by sequence number. It will either be the active transaction (front of Q_PEND),
458  // or on Q_TX or Q_RX. Once a transaction moves to history, then it's done.
459  //
460  // Let's put QueueId::RX up front, because most RX packets will be file data PDUs
461  CfdpTraverseTransSeqArg ctx = {transaction_sequence_number, src_eid, nullptr};
462  CListNode* ptrs[] = {m_qs[QueueId::RX], m_qs[QueueId::PEND], m_qs[QueueId::TXA], m_qs[QueueId::TXW]};
463  Transaction* ret = nullptr;
464 
465  for (CListNode* head : ptrs) {
467  if (ctx.txn) {
468  ret = ctx.txn;
469  break;
470  }
471  }
472 
473  return ret;
474 }
475 
477  I32 counter = 0;
478 
479  // Context for static wrapper
480  struct TraverseAllContext {
482  void* userContext;
483  I32* counter;
484  } ctx = {fn, context, &counter};
485 
486  for (I32 queueidx = QueueId::PEND; queueidx <= QueueId::RX; ++queueidx) {
488  }
489 
490  return counter;
491 }
492 
494  this->removeFromQueue(QueueId::HIST, &history->cl_node);
495  this->insertBackInQueue(QueueId::HIST_FREE, &history->cl_node);
496 }
497 
498 // ----------------------------------------------------------------------
499 // Transaction Queue Management
500 // ----------------------------------------------------------------------
501 
503  FW_ASSERT(txn);
504  CfdpCListRemove(&m_qs[txn->m_flags.com.q_index], &txn->m_cl_node);
505 
506  // Update queue depth telemetry
507  Cfdp::ChannelTelemetry& tlm = m_engine->getChannelTelemetryRef(m_channelId);
508  switch (txn->m_flags.com.q_index) {
509  case Cfdp::QueueId::FREE:
510 
511  tlm.set_queueFree(static_cast<U16>(tlm.get_queueFree() - 1));
512  break;
513  case Cfdp::QueueId::TXA:
514 
515  tlm.set_queueTxActive(static_cast<U16>(tlm.get_queueTxActive() - 1));
516  break;
517  case Cfdp::QueueId::TXW:
518 
519  tlm.set_queueTxWaiting(static_cast<U16>(tlm.get_queueTxWaiting() - 1));
520  break;
521  case Cfdp::QueueId::RX:
522 
523  tlm.set_queueRx(static_cast<U16>(tlm.get_queueRx() - 1));
524  break;
525  case Cfdp::QueueId::HIST:
526 
527  tlm.set_queueHistory(static_cast<U16>(tlm.get_queueHistory() - 1));
528  break;
529  case Cfdp::QueueId::PEND:
531  // PEND and HIST_FREE queues are not tracked in telemetry
532  break;
533  default:
534  FW_ASSERT(0, txn->m_flags.com.q_index);
535  }
536 }
537 
539  FW_ASSERT(txn);
540  Cfdp::ChannelTelemetry& tlm = m_engine->getChannelTelemetryRef(m_channelId);
541 
542  // Decrement old queue
543  CfdpCListRemove(&m_qs[txn->m_flags.com.q_index], &txn->m_cl_node);
544  switch (txn->m_flags.com.q_index) {
545  case Cfdp::QueueId::FREE:
546 
547  tlm.set_queueFree(static_cast<U16>(tlm.get_queueFree() - 1));
548  break;
549  case Cfdp::QueueId::TXA:
550 
551  tlm.set_queueTxActive(static_cast<U16>(tlm.get_queueTxActive() - 1));
552  break;
553  case Cfdp::QueueId::TXW:
554 
555  tlm.set_queueTxWaiting(static_cast<U16>(tlm.get_queueTxWaiting() - 1));
556  break;
557  case Cfdp::QueueId::RX:
558 
559  tlm.set_queueRx(static_cast<U16>(tlm.get_queueRx() - 1));
560  break;
561  case Cfdp::QueueId::HIST:
562 
563  tlm.set_queueHistory(static_cast<U16>(tlm.get_queueHistory() - 1));
564  break;
565  case Cfdp::QueueId::PEND:
567  // PEND and HIST_FREE queues are not tracked in telemetry
568  break;
569  default:
570  FW_ASSERT(0, txn->m_flags.com.q_index);
571  }
572 
573  // Increment new queue
574  CfdpCListInsertBack(&m_qs[queue], &txn->m_cl_node);
575  txn->m_flags.com.q_index = queue;
576  switch (queue) {
577  case Cfdp::QueueId::FREE:
578  tlm.set_queueFree(static_cast<U16>(tlm.get_queueFree() + 1));
579  break;
580  case Cfdp::QueueId::TXA:
581  tlm.set_queueTxActive(static_cast<U16>(tlm.get_queueTxActive() + 1));
582  break;
583  case Cfdp::QueueId::TXW:
584  tlm.set_queueTxWaiting(static_cast<U16>(tlm.get_queueTxWaiting() + 1));
585  break;
586  case Cfdp::QueueId::RX:
587  tlm.set_queueRx(static_cast<U16>(tlm.get_queueRx() + 1));
588  break;
589  case Cfdp::QueueId::HIST:
590  tlm.set_queueHistory(static_cast<U16>(tlm.get_queueHistory() + 1));
591  break;
592  case Cfdp::QueueId::PEND:
594  // PEND and HIST_FREE queues are not tracked in telemetry
595  break;
596  default:
597  FW_ASSERT(0, queue);
598  }
599 }
600 
602  // Reset transaction to default state (preserves channel context)
603  txn->reset();
604 
605  // Initialize the linked list node for the FREE queue
606  CfdpCListInitNode(&txn->m_cl_node);
607  this->insertBackInQueue(QueueId::FREE, &txn->m_cl_node);
608 
609  // Mark the transaction as residing on the FREE list. insertBackInQueue() only
610  // performs the list insertion (unlike insertSortPrio(), which also updates
611  // q_index), and txn->reset() zeroes m_flags so q_index would otherwise be left
612  // at 0 (== QueueId::PEND). Without this, a freed transaction is never tagged
613  // FREE, and Engine::finishTransaction()'s double-free guard
614  // (q_index == QueueId::FREE) can never fire. Setting it here upholds the
615  // invariant: "a transaction on the FREE list has q_index == FREE".
616  txn->m_flags.com.q_index = QueueId::FREE;
617 }
618 
620  CListNode** chunklist_head;
621  QueueId::T hist_destq;
622 
623  // File should have been closed by the state machine, but if
624  // it still hanging open at this point, close it now so its not leaked.
625  // This is not normal/expected so log it if this happens.
626  if (true == txn->m_fd.isOpen()) {
627  this->m_cfdpManager->log_WARNING_LO_DanglingFileHandleClosed(txn->getChannelId(), txn->m_history->seq_num);
628  txn->m_fd.close();
629  }
630 
631  this->dequeueTransaction(txn); // this makes it "float" (not in any queue)
632 
633  // this should always be
634  if (txn->m_history != nullptr) {
635  if (txn->m_chunks != nullptr) {
636  chunklist_head = this->getChunkListHead(static_cast<U8>(txn->m_history->dir));
637  if (chunklist_head != nullptr) {
638  // Reset chunk list to clear stale data from previous transaction
639  txn->m_chunks->chunks.reset();
640  CfdpCListInsertBack(chunklist_head, &txn->m_chunks->cl_node);
641  txn->m_chunks = nullptr;
642  }
643  }
644 
645  if (txn->m_flags.com.keep_history) {
646  // move transaction history to history queue
647  hist_destq = QueueId::HIST;
648  } else {
649  hist_destq = QueueId::HIST_FREE;
650  }
651  this->insertBackInQueue(hist_destq, &txn->m_history->cl_node);
652  txn->m_history = nullptr;
653  }
654 
655  // this wipes it and puts it back onto the list to be found by
656  // Channel::findUnusedTransaction(). Need to preserve the chan_num
657  // and keep it associated with this channel, though.
658  this->freeTransaction(txn);
659 }
660 
662  bool insert_back = false;
663 
664  FW_ASSERT(txn);
665 
666  // look for proper position on PEND queue for this transaction.
667  // This is a simple priority sort.
668 
669  if (!m_qs[queue]) {
670  // list is empty, so just insert
671  insert_back = true;
672  } else {
673  CfdpTraversePriorityArg arg = {nullptr, txn->getPriority()};
675  if (arg.txn) {
676  this->insertAfterInQueue(queue, &arg.txn->m_cl_node, &txn->m_cl_node);
677  } else {
678  insert_back = true;
679  }
680  }
681 
682  if (insert_back) {
683  this->insertBackInQueue(queue, &txn->m_cl_node);
684  }
685  txn->m_flags.com.q_index = queue;
686 }
687 
688 // ----------------------------------------------------------------------
689 // Channel State Management
690 // ----------------------------------------------------------------------
691 
693  FW_ASSERT(m_numCmdTx); // sanity check
694  --m_numCmdTx;
695 }
696 
698  // Done with this TX transaction
699  if (this->m_currentTxn == txn) {
700  this->m_currentTxn = nullptr;
701  }
702 }
703 
705  this->m_currentTxn = txn;
706 }
707 
708 // ----------------------------------------------------------------------
709 // Resource Management
710 // ----------------------------------------------------------------------
711 
713  CListNode** result;
714 
715  if (direction < static_cast<U32>(Direction::DIRECTION_NUM)) {
716  result = &m_cs[direction];
717  } else {
718  result = nullptr;
719  }
720 
721  return result;
722 }
723 
725  CfdpChunkWrapper* ret = nullptr;
726  CListNode* node;
727  CListNode** chunklist_head;
728 
729  chunklist_head = this->getChunkListHead(static_cast<U8>(dir));
730 
731  // this should never be null
732  FW_ASSERT(chunklist_head);
733 
734  if (*chunklist_head != nullptr) {
735  node = CfdpCListPop(chunklist_head);
736  if (node != nullptr) {
738  }
739  }
740 
741  return ret;
742 }
743 
744 // ----------------------------------------------------------------------
745 // Private helper methods
746 // ----------------------------------------------------------------------
747 
748 void Channel::processPlaybackDirectory(Playback* pb) {
749  Transaction* txn;
751  Os::Directory::Status status;
752 
753  // either there's no transaction (first one) or the last one was finished, so check for a new one
754 
755  while (pb->diropen && (pb->num_ts < NumTransactionsPerPlayback)) {
756  if (pb->pending_file.length() == 0) {
757  status = pb->dir.read(path);
758  if (status == Os::Directory::NO_MORE_FILES) {
759  // Directory playback complete - success reported via TxFileTransferCompleted EVR
760  pb->dir.close();
761  pb->diropen = false;
762  break;
763  }
764  if (status != Os::Directory::OP_OK) {
765  // Directory read error - emit EVR and close playback
767  static_cast<I32>(status));
768  pb->dir.close();
769  pb->diropen = false;
770  break;
771  }
772 
773  pb->pending_file = path;
774  } else {
776  if (txn == nullptr) {
777  // while not expected this can certainly happen, because
778  // rx transactions consume in these as well.
779  // should not need to do anything special, will come back next tick
780  break;
781  }
782 
783  // Append file name to source/destination folders
784  txn->m_history->fnames.src_filename = pb->fnames.src_filename;
785  txn->m_history->fnames.src_filename += "/";
786  txn->m_history->fnames.src_filename += pb->pending_file;
787 
788  txn->m_history->fnames.dst_filename = pb->fnames.dst_filename;
789  txn->m_history->fnames.dst_filename += "/";
790  txn->m_history->fnames.dst_filename += pb->pending_file;
791 
792  m_engine->txFileInitiate(txn, pb->cfdp_class, pb->keep, m_channelId, pb->priority, pb->dest_id);
793 
794  txn->m_pb = pb;
795  ++pb->num_ts;
796 
797  pb->pending_file = ""; // continue reading dir
798  }
799  }
800 
801  if (!pb->diropen && !pb->num_ts) {
802  // the directory has been exhausted, and there are no more active transactions
803  // for this playback -- so mark it as not busy
804  pb->busy = false;
805  }
806 }
807 
808 void Channel::updatePollPbCounted(Playback* pb, I32 up, U8* counter) {
809  if (pb->counted != up) {
810  // only handle on state change
811  pb->counted = !!up; // !! ensure 0 or 1, should be optimized out
812 
813  if (up) {
814  ++*counter;
815  } else {
816  FW_ASSERT(*counter); // sanity check it isn't zero
817  --*counter;
818  }
819  }
820 }
821 
823  CycleTxArgs* args = static_cast<CycleTxArgs*>(context);
824  Transaction* txn = container_of_cpp(node, &Transaction::m_cl_node);
825  CListTraverseStatus ret = CLIST_TRAVERSE_EXIT; // default option is exit traversal
826 
827  if (txn->m_flags.com.suspended) {
828  ret = CLIST_TRAVERSE_CONTINUE; // suspended, so move on to next
829  } else {
830  FW_ASSERT(txn->m_flags.com.q_index == QueueId::TXA); // huh?
831 
832  // if no more messages, then chan->m_currentTxn will be set.
833  // If the transaction sent the last filedata PDU and EOF, it will move itself
834  // off the active queue. Run until either of these occur.
835  while (!this->m_currentTxn && txn->m_flags.com.q_index == QueueId::TXA) {
836  m_engine->dispatchTx(txn);
837  }
838 
839  args->ran_one = 1;
840  }
841 
842  return ret;
843 }
844 
846  CListTraverseStatus ret =
847  CLIST_TRAVERSE_CONTINUE; // CLIST_TRAVERSE_CONTINUE means don't tick one, keep looking for currentTxn
848  TickArgs* args = static_cast<TickArgs*>(context);
849  Transaction* txn = container_of_cpp(node, &Transaction::m_cl_node);
850  if (!this->m_currentTxn || (this->m_currentTxn == txn)) {
851  // found where we left off, so clear that and move on
852  this->m_currentTxn = nullptr;
853  if (!txn->m_flags.com.suspended) {
854  (txn->*args->fn)(&args->cont);
855  }
856 
857  // if this->m_currentTxn was set to not-nullptr above, then exit early
858  // NOTE: if channel is frozen, then tick processing won't have been entered.
859  // so there is no need to check it here
860  if (this->m_currentTxn) {
861  ret = CLIST_TRAVERSE_EXIT;
862  args->early_exit = true;
863  }
864  }
865 
866  return ret; // don't tick one, keep looking for currentTxn
867 }
868 
871  return &m_transactions[index];
872 }
873 
876  return &m_histories[index];
877 }
878 
879 // ----------------------------------------------------------------------
880 // Static callback wrapper implementations
881 // ----------------------------------------------------------------------
882 
884  struct CycleTxContext {
885  Channel* channel;
886  CycleTxArgs* args;
887  };
888  CycleTxContext* ctx = static_cast<CycleTxContext*>(context);
889  return ctx->channel->cycleTxFirstActive(node, ctx->args);
890 }
891 
893  struct TickContext {
894  Channel* channel;
895  TickArgs* args;
896  };
897  TickContext* ctx = static_cast<TickContext*>(context);
898  return ctx->channel->doTick(node, ctx->args);
899 }
900 
902  struct TraverseAllContext {
904  void* userContext;
905  I32* counter;
906  };
907  TraverseAllContext* ctx = static_cast<TraverseAllContext*>(context);
908  Transaction* txn = container_of_cpp(node, &Transaction::m_cl_node);
909  ctx->fn(txn, ctx->userContext);
910  ++(*ctx->counter);
912 }
913 
914 } // namespace Cfdp
915 } // namespace Ccsds
916 } // namespace Svc
CListNode cl_node
for connection to a CList
Definition: Types.hpp:246
U8 getPriority() const
Get transaction priority.
CfdpTxnFilenames fnames
file names associated with this history entry
Definition: Types.hpp:245
CFDP Protocol Engine.
Definition: Engine.hpp:89
void moveTransaction(Transaction *txn, QueueId::T queue)
Move a transaction from one queue to another.
Definition: Channel.cpp:538
Fw::String dstDir
path to destination dir
Definition: Types.hpp:309
CFDP Channel class.
Definition: Channel.hpp:56
virtual void * allocate(const FwEnumStoreType identifier, FwSizeType &size, bool &recoverable, FwSizeType alignment=alignof(std::max_align_t))=0
Structure for use with the Channel::doTick() function.
Definition: Engine.hpp:70
~Channel()
Destruct a Channel.
Definition: Channel.cpp:168
Argument structure for use with CfdpCListTraverseR()
Definition: Utils.hpp:73
Transaction * txn
output transaction pointer
Definition: Utils.hpp:54
History * getHistory(U32 index)
Get a history by index (for testing)
Definition: Channel.cpp:874
bool early_exit
early exit result
Definition: Engine.hpp:73
CFDP Transaction state machine class.
PlatformSizeType FwSizeType
void removeFromQueue(QueueId::T queueidx, CListNode *node)
Remove a node from a channel queue.
Definition: Channel.hpp:520
I32 FwEnumStoreType
CListTraverseStatus
Traverse status for circular list operations.
Definition: Clist.hpp:45
void(Transaction::* fn)(I32 *)
member function pointer
Definition: Engine.hpp:72
U16 num_ts
number of transactions
Definition: Types.hpp:282
void(*)(Transaction *txn, void *context) CfdpTraverseAllTransactionsFunc
Callback function type for use with Channel::traverseAllTransactions()
Definition: Types.hpp:421
void setCurrentTxn(const Transaction *txn)
Set current transaction.
Definition: Channel.cpp:704
State assigned to a newly allocated transaction object.
U32 EntityId
Entity id size.
first one on this list is active
void set_queueTxActive(U16 queueTxActive)
Set member queueTxActive.
void sTickNak(I32 *cont)
Perform NAK response for TX transactions.
I32 cont
if 1, then re-traverse the list
Definition: Engine.hpp:74
I32 traverseAllTransactions(CfdpTraverseAllTransactionsFunc fn, void *context)
Traverses all transactions on all active queues and performs an operation on them.
Definition: Channel.cpp:476
U8 priority
priority to use when placing transactions on the pending queue
Definition: Types.hpp:304
Wrapper around a CfdpChunkList object.
Definition: Types.hpp:260
void reset()
Reset the chunk list to empty state.
Definition: Chunk.cpp:58
Fw::StringTemplate< MaxFilePathSize > pending_file
Definition: Types.hpp:285
CfdpChunkList chunks
Chunk list for gap tracking.
Definition: Types.hpp:261
File will be deleted after the CFDP transaction.
Definition: KeepEnumAc.hpp:38
CFDP class 2 - Reliable transfer (Acknowledged)
Definition: ClassEnumAc.hpp:44
Channel * chan
channel object
Definition: Engine.hpp:63
static CListTraverseStatus prioritySearchCallback(CListNode *node, void *context)
Static callback for priority search.
Definition: Utils.cpp:89
Continue traversing the list.
Definition: Clist.hpp:46
void rTick(I32 *cont)
Perform tick (time-based) processing for R transactions.
void set_pollCounter(U8 pollCounter)
Set member pollCounter.
void processPollingDirectories()
Process all polling directories for this channel.
Definition: Channel.cpp:352
U32 intervalSec
number of seconds to wait before trying a new directory
Definition: Types.hpp:302
CFDP Playback entry.
Definition: Types.hpp:278
I32 ran_one
should be set to 1 if a transaction was cycled
Definition: Engine.hpp:64
CListTraverseStatus cycleTxFirstActive(CListNode *node, void *context)
Traverse callback for cycling the first active transaction.
Definition: Channel.cpp:822
void set_queueTxWaiting(U16 queueTxWaiting)
Set member queueTxWaiting.
U16 get_queueTxActive() const
Get member queueTxActive.
U32 TransactionSeq
transaction sequence number size
U8 getChannelId() const
Get channel ID.
Fw::Enabled getDequeueEnabledParam(U8 channelIndex)
bool keep_history
whether history should be preserved during recycle
Definition: Types.hpp:365
Status getStatus(void)
Get the status of a CFDP timer.
Definition: Timer.cpp:37
void set_queueRx(U16 queueRx)
Set member queueRx.
static constexpr U32 CFDP_NUM_TRANSACTIONS_PER_CHANNEL
Maximum possible number of transactions that may exist on a single CFDP channel.
Definition: Types.hpp:42
void cycleTx()
Cycle the TX side of this channel.
Definition: Channel.cpp:212
#define CFDP_CHANNEL_NUM_TX_CHUNKS_PER_TRANSACTION
TX chunks per transaction (per channel)
Definition: CfdpCfg.hpp:58
U16 get_queueTxWaiting() const
Get member queueTxWaiting.
static CListTraverseStatus cycleTxFirstActiveWrapper(CListNode *node, void *context)
Static wrapper for cycleTxFirstActive callback.
Definition: Channel.cpp:883
void close() override
Close directory.
Definition: Directory.cpp:74
Argument structure for use with CList_Traverse()
Definition: Utils.hpp:51
void insertAfterInQueue(QueueId::T queueidx, CListNode *start, CListNode *after)
Insert a node after another in a channel queue.
Definition: Channel.hpp:524
Structure for use with the Channel::cycleTx() function.
Definition: Engine.hpp:62
Status read(char *fileNameBuffer, FwSizeType buffSize) override
Get next filename from directory stream.
Definition: Directory.cpp:54
void armInactTimer(Transaction *txn)
Arm the inactivity timer for a transaction.
Definition: Engine.cpp:109
void reset()
Reset transaction to default state.
Channel(Engine *engine, U8 channelId, CfdpManager *cfdpManager, Fw::MemAllocator &allocator, FwEnumStoreType memId)
Construct a Channel.
Definition: Channel.cpp:51
void clearCurrentIfMatch(Transaction *txn)
Check if current transaction matches and clear if so.
Definition: Channel.cpp:697
void tickTransactions()
Tick all transactions on this channel.
Definition: Channel.cpp:268
U16 get_queueFree() const
Get member queueFree.
Direction dir
direction of this history entry
Definition: Types.hpp:247
void freeTransaction(Transaction *txn)
Frees and resets a transaction and returns it for later use.
Definition: Channel.cpp:601
void dequeueTransaction(Transaction *txn)
Free a transaction from the queue it&#39;s on.
Definition: Channel.cpp:502
Playback pb
State of the current playback requests.
Definition: Types.hpp:299
void dispatchTx(Transaction *txn)
Dispatch TX state machine for a transaction.
Definition: Engine.cpp:169
void setTimer(U32 timerDuration)
Initialize a CFDP timer and start its execution.
Definition: Timer.cpp:27
void txFileInitiate(Transaction *txn, Class::T cfdp_class, Keep::T keep, U8 chan, U8 priority, EntityId dest_id)
Initiate a file transfer transaction.
Definition: Engine.cpp:727
Stop traversing the list.
Definition: Clist.hpp:47
void close() override
close the file, if not opened then do nothing
Definition: File.cpp:97
Directory stream has no more files.
Definition: Directory.hpp:27
static CListTraverseStatus findBySequenceNumberCallback(CListNode *node, void *context)
Static callback for finding transaction by sequence number.
Definition: Utils.cpp:74
void CfdpCListTraverse(CListNode *start, CListFunc fn, void *context)
Traverse the entire list, calling the given function on all nodes.
Definition: Clist.cpp:139
Status::T playbackDirInitiate(Playback *pb, const Fw::String &src_filename, const Fw::String &dst_filename, Class::T cfdp_class, Keep::T keep, U8 chan, U8 priority, EntityId dest_id)
Initiate playback of a directory.
Definition: Engine.cpp:821
Fw::String srcDir
path to source dir
Definition: Types.hpp:308
CListNode * CfdpCListPop(CListNode **head)
Remove the first node from a list and return it.
Definition: Clist.cpp:89
void decrementCmdTxCounter()
Decrement the command TX counter for this channel.
Definition: Channel.cpp:692
void CfdpCListInsertBack(CListNode **head, CListNode *node)
Insert the given node into the back of a list.
Definition: Clist.cpp:69
Transaction * txn
OUT: holds value of transaction with which to call CfdpCListInsertAfter on.
Definition: Utils.hpp:74
static CListTraverseStatus doTickWrapper(CListNode *node, void *context)
Static wrapper for doTick callback.
Definition: Channel.cpp:892
uint8_t U8
8-bit unsigned integer
Definition: BasicTypes.h:54
void processPlaybackDirectories()
Process all playback directories for this channel.
Definition: Channel.cpp:335
void insertSortPrio(Transaction *txn, QueueId::T queue)
Insert a transaction into a priority sorted transaction queue.
Definition: Channel.cpp:661
void sTick(I32 *cont)
Perform tick (time-based) processing for S transactions.
void set_playbackCounter(U8 playbackCounter)
Set member playbackCounter.
CfdpTxnFilenames fnames
Definition: Types.hpp:281
Cfdp::ChannelTelemetry & getChannelTelemetryRef(U8 channelId)
Get reference to channel telemetry for Channel class.
Definition: Engine.cpp:1182
Transaction * findUnusedTransaction(Direction direction)
Find an unused transaction on this channel.
Definition: Channel.cpp:395
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 cleanup(Fw::MemAllocator &allocator, FwEnumStoreType memId)
Clean up dynamically allocated resources.
Definition: Channel.cpp:173
Memory Allocation base class.
void log_WARNING_LO_PlaybackDirReadFailed(const Fw::StringBase &directory, I32 status) const
Log event PlaybackDirReadFailed.
Transaction * getTransaction(U32 index)
Get a transaction by index (for testing)
Definition: Channel.cpp:869
void insertBackInQueue(QueueId::T queueidx, CListNode *node)
Insert a node at the back of a channel queue.
Definition: Channel.hpp:528
Class::T cfdpClass
the CFDP class to send
Definition: Types.hpp:305
constexpr Container * container_of_cpp(Member *member_ptr, Member Container::*member)
Obtains a pointer to the parent structure.
Definition: Clist.hpp:79
void CfdpCListRemove(CListNode **head, CListNode *node)
Remove the given node from the list.
Definition: Clist.cpp:102
Operation was successful.
Definition: Directory.hpp:22
void run(void)
Runs a one second increment of the CFDP timers.
Definition: Timer.cpp:41
Fw::Enabled enabled
Enabled flag.
Definition: Types.hpp:311
CListTraverseStatus doTick(CListNode *node, void *context)
Traverse callback for ticking a transaction.
Definition: Channel.cpp:845
Circular linked list node structure.
Definition: Clist.hpp:66
Transaction * findTransactionBySequenceNumber(TransactionSeq transaction_sequence_number, EntityId src_eid)
Finds an active transaction by sequence number.
Definition: Channel.cpp:456
Timer intervalTimer
Timer object used to poll the directory.
Definition: Types.hpp:300
Structure for the telemetry array of CFDP channels.
RateGroupDivider component implementation.
virtual SizeType length() const
Get the length of the string.
CfdpFlagsCommon com
applies to all transactions
Definition: Types.hpp:402
TxnStatus txn_stat
final status of operation
Definition: Types.hpp:248
void resetHistory(History *history)
Returns a history structure back to its unused state.
Definition: Channel.cpp:493
EntityId src_eid
the source eid of the transaction
Definition: Types.hpp:249
void log_WARNING_LO_DanglingFileHandleClosed(U8 channelId, U32 transactionSeq) const
Log event DanglingFileHandleClosed.
virtual void deallocate(const FwEnumStoreType identifier, void *ptr)=0
#define CFDP_CHANNEL_NUM_RX_CHUNKS_PER_TRANSACTION
RX chunks per transaction (per channel)
Definition: CfdpCfg.hpp:37
Directory poll entry.
Definition: Types.hpp:298
EntityId destEid
destination entity id
Definition: Types.hpp:306
U8 q_index
Q index this is in.
Definition: Types.hpp:359
void CfdpCListTraverseR(CListNode *end, CListFunc fn, void *context)
Reverse list traversal, starting from end, calling given function on all nodes.
Definition: Clist.cpp:173
void set_queueHistory(U16 queueHistory)
Set member queueHistory.
CListNode ** getChunkListHead(U8 direction)
Gets the head of the chunk list for this channel + direction.
Definition: Channel.cpp:712
U16 get_queueHistory() const
Get member queueHistory.
CfdpChunkWrapper * findUnusedChunks(Direction dir)
Find unused chunks for this channel.
Definition: Channel.cpp:724
T
The raw enum type.
void set_queueFree(U16 queueFree)
Set member queueFree.
void recycleTransaction(Transaction *txn)
Recover resources associated with a transaction.
Definition: Channel.cpp:619
Direction
Direction identifier.
Definition: Types.hpp:154
#define FW_ASSERT(...)
Definition: Assert.hpp:14
Disabled state.
bool isOpen() const
determine if the file is open
Definition: File.cpp:105
CListNode cl_node
Circular list node for pooling.
Definition: Types.hpp:262
static CListTraverseStatus traverseAllTransactionsWrapper(CListNode *node, void *context)
Static wrapper for traverseAllTransactions callback.
Definition: Channel.cpp:901
Pairs an offset with a size to identify a specific piece of a file.
Definition: Chunk.hpp:49
EntityId peer_eid
peer_eid is always the "other guy", same src_eid for RX
Definition: Types.hpp:250
void CfdpCListInitNode(CListNode *node)
Initialize a clist node.
Definition: Clist.cpp:43
CFDP History entry.
Definition: Types.hpp:244