GCC Code Coverage Report


Directory: ./
File: Svc/Ccsds/CfdpManager/Channel.cpp
Date: 2026-09-03 22:12:29
Exec Total Coverage
Lines: 0 418 0.0%
Functions: 0 30 0.0%
Branches: 0 234 0.0%

Line Branch Exec Source
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
38 #include <Svc/Ccsds/CfdpManager/CfdpManager.hpp>
39 #include <Svc/Ccsds/CfdpManager/Channel.hpp>
40 #include <Svc/Ccsds/CfdpManager/Engine.hpp>
41 #include <Svc/Ccsds/CfdpManager/Utils.hpp>
42
43 namespace Svc {
44 namespace Ccsds {
45 namespace Cfdp {
46
47 // ----------------------------------------------------------------------
48 // Construction
49 // ----------------------------------------------------------------------
50
51 Channel::Channel(Engine* engine,
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 =
127 (CFDP_NUM_TRANSACTIONS_PER_CHANNEL * static_cast<U32>(Direction::DIRECTION_NUM)) * sizeof(CfdpChunkWrapper);
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];
155 CfdpCListInitNode(&cw->cl_node);
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
168 Channel::~Channel() {
169 // Cleanup should have been called before destruction
170 // This is enforced by Engine::~Engine()
171 }
172
173 void Channel::cleanup(Fw::MemAllocator& allocator, FwEnumStoreType memId) {
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
212 void Channel::cycleTx() {
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
236 CfdpCListTraverse(m_qs[QueueId::TXA], &Channel::cycleTxFirstActiveWrapper, &cycleTxCtx);
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
268 void Channel::tickTransactions() {
269 bool reset = true;
270
271 void (Transaction::* fns[static_cast<U8>(CfdpTickType::CFDP_TICK_TYPE_NUM_TYPES)])(I32*) = {
272 &Transaction::rTick, &Transaction::sTick, &Transaction::sTickNak};
273 I32 qs[static_cast<U8>(CfdpTickType::CFDP_TICK_TYPE_NUM_TYPES)] = {QueueId::RX, QueueId::TXW, QueueId::TXW};
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 +
283 (MaxCommandedPlaybackDirectoriesPerChan * NumTransactionsPerPlayback) +
284 (MaxPollingDirPerChan * NumTransactionsPerPlayback);
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
335 void Channel::processPlaybackDirectories() {
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
352 void Channel::processPollingDirectories() {
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) {
374 pd->intervalTimer.setTimer(pd->intervalSec);
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
395 Transaction* Channel::findUnusedTransaction(Direction direction) {
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
456 Transaction* Channel::findTransactionBySequenceNumber(TransactionSeq transaction_sequence_number, EntityId src_eid) {
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) {
466 CfdpCListTraverse(head, Transaction::findBySequenceNumberCallback, &ctx);
467 if (ctx.txn) {
468 ret = ctx.txn;
469 break;
470 }
471 }
472
473 return ret;
474 }
475
476 I32 Channel::traverseAllTransactions(CfdpTraverseAllTransactionsFunc fn, void* context) {
477 I32 counter = 0;
478
479 // Context for static wrapper
480 struct TraverseAllContext {
481 CfdpTraverseAllTransactionsFunc fn;
482 void* userContext;
483 I32* counter;
484 } ctx = {fn, context, &counter};
485
486 for (I32 queueidx = QueueId::PEND; queueidx <= QueueId::RX; ++queueidx) {
487 CfdpCListTraverse(m_qs[queueidx], &Channel::traverseAllTransactionsWrapper, &ctx);
488 }
489
490 return counter;
491 }
492
493 void Channel::resetHistory(History* history) {
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
502 void Channel::dequeueTransaction(Transaction* txn) {
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:
530 case Cfdp::QueueId::HIST_FREE:
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
538 void Channel::moveTransaction(Transaction* txn, QueueId::T queue) {
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:
566 case Cfdp::QueueId::HIST_FREE:
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:
593 case Cfdp::QueueId::HIST_FREE:
594 // PEND and HIST_FREE queues are not tracked in telemetry
595 break;
596 default:
597 FW_ASSERT(0, queue);
598 }
599 }
600
601 void Channel::freeTransaction(Transaction* txn) {
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
619 void Channel::recycleTransaction(Transaction* txn) {
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
661 void Channel::insertSortPrio(Transaction* txn, QueueId::T queue) {
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()};
674 CfdpCListTraverseR(m_qs[queue], Transaction::prioritySearchCallback, &arg);
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
692 void Channel::decrementCmdTxCounter() {
693 FW_ASSERT(m_numCmdTx); // sanity check
694 --m_numCmdTx;
695 }
696
697 void Channel::clearCurrentIfMatch(Transaction* txn) {
698 // Done with this TX transaction
699 if (this->m_currentTxn == txn) {
700 this->m_currentTxn = nullptr;
701 }
702 }
703
704 void Channel::setCurrentTxn(const Transaction* txn) {
705 this->m_currentTxn = txn;
706 }
707
708 // ----------------------------------------------------------------------
709 // Resource Management
710 // ----------------------------------------------------------------------
711
712 CListNode** Channel::getChunkListHead(U8 direction) {
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
724 CfdpChunkWrapper* Channel::findUnusedChunks(Direction dir) {
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) {
737 ret = container_of_cpp(node, &CfdpChunkWrapper::cl_node);
738 }
739 }
740
741 return ret;
742 }
743
744 // ----------------------------------------------------------------------
745 // Private helper methods
746 // ----------------------------------------------------------------------
747
748 void Channel::processPlaybackDirectory(Playback* pb) {
749 Transaction* txn;
750 Fw::StringTemplate<MaxFilePathSize> path;
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
766 this->m_cfdpManager->log_WARNING_LO_PlaybackDirReadFailed(pb->fnames.src_filename,
767 static_cast<I32>(status));
768 pb->dir.close();
769 pb->diropen = false;
770 break;
771 }
772
773 pb->pending_file = path;
774 } else {
775 txn = this->findUnusedTransaction(Direction::DIRECTION_TX);
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
822 CListTraverseStatus Channel::cycleTxFirstActive(CListNode* node, void* context) {
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
845 CListTraverseStatus Channel::doTick(CListNode* node, void* context) {
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
869 Transaction* Channel::getTransaction(U32 index) {
870 FW_ASSERT(index < CFDP_NUM_TRANSACTIONS_PER_CHANNEL);
871 return &m_transactions[index];
872 }
873
874 History* Channel::getHistory(U32 index) {
875 FW_ASSERT(index < NumHistoriesPerChannel);
876 return &m_histories[index];
877 }
878
879 // ----------------------------------------------------------------------
880 // Static callback wrapper implementations
881 // ----------------------------------------------------------------------
882
883 CListTraverseStatus Channel::cycleTxFirstActiveWrapper(CListNode* node, void* context) {
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
892 CListTraverseStatus Channel::doTickWrapper(CListNode* node, void* context) {
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
901 CListTraverseStatus Channel::traverseAllTransactionsWrapper(CListNode* node, void* context) {
902 struct TraverseAllContext {
903 CfdpTraverseAllTransactionsFunc fn;
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);
911 return CLIST_TRAVERSE_CONTINUE;
912 }
913
914 } // namespace Cfdp
915 } // namespace Ccsds
916 } // namespace Svc
917