GCC Code Coverage Report


Directory: Svc/Ccsds/CfdpManager/
File: Channel.cpp
Date: 2026-09-03 21:16:27
Exec Total Coverage
Lines: 313 419 74.7%
Functions: 25 30 83.3%
Branches: 143 258 55.4%

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 262 Channel::Channel(Engine* engine,
52 U8 channelId,
53 CfdpManager* cfdpManager,
54 Fw::MemAllocator& allocator,
55 262 FwEnumStoreType memId)
56 262 : m_engine(engine),
57 262 m_numCmdTx(0),
58 262 m_currentTxn(nullptr),
59 262 m_cfdpManager(cfdpManager),
60 262 m_tickType(0),
61 262 m_channelId(channelId),
62 262 m_flowState(Cfdp::Flow::NOT_FROZEN),
63 262 m_outgoingCounter(0),
64 262 m_transactions(nullptr),
65 262 m_histories(nullptr),
66 262 m_chunks(nullptr),
67
6/18
✓ Branch 4 taken 524 times.
✓ Branch 7 taken 524 times.
✓ Branch 8 taken 262 times.
✓ Branch 14 taken 1310 times.
✓ Branch 17 taken 1310 times.
✓ Branch 18 taken 262 times.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 30 not taken.
✗ Branch 31 not taken.
✗ Branch 33 not taken.
✗ Branch 34 not taken.
✗ Branch 40 not taken.
✗ Branch 41 not taken.
✗ Branch 52 not taken.
✗ Branch 53 not taken.
2358 m_chunkMem(nullptr) {
68 262 FW_ASSERT(engine != nullptr);
69 262 FW_ASSERT(cfdpManager != nullptr);
70
71 // Initialize queue pointers
72
2/2
✓ Branch 0 taken 1834 times.
✓ Branch 1 taken 262 times.
2096 for (U32 i = 0; i < QueueId::NUM; i++) {
73 1834 m_qs[i] = nullptr;
74 }
75
76 // Initialize command/history lists
77
2/2
✓ Branch 0 taken 524 times.
✓ Branch 1 taken 262 times.
786 for (U32 i = 0; i < static_cast<U32>(Direction::DIRECTION_NUM); i++) {
78 524 m_cs[i] = nullptr;
79 }
80
81 // Initialize poll directory playback state
82
2/2
✓ Branch 0 taken 1310 times.
✓ Branch 1 taken 262 times.
1572 for (U32 i = 0; i < MaxPollingDirPerChan; i++) {
83
1/1
✓ Branch 7 taken 1310 times.
1310 m_polldir[i].enabled = Fw::Enabled::DISABLED;
84 1310 m_polldir[i].pb.busy = false;
85 1310 m_polldir[i].pb.diropen = false;
86 1310 m_polldir[i].pb.counted = false;
87 1310 m_polldir[i].pb.num_ts = 0;
88
1/1
✓ Branch 7 taken 1310 times.
1310 m_polldir[i].pb.pending_file = "";
89 }
90
91 // Initialize playback structures
92
2/2
✓ Branch 0 taken 524 times.
✓ Branch 1 taken 262 times.
786 for (U32 i = 0; i < MaxCommandedPlaybackDirectoriesPerChan; i++) {
93 524 m_playback[i].busy = false;
94 524 m_playback[i].diropen = false;
95 524 m_playback[i].counted = false;
96 524 m_playback[i].num_ts = 0;
97
1/1
✓ Branch 7 taken 524 times.
524 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 262 U32 chunk_mem_offset = 0;
107 U32 total_chunks_needed;
108
109 // Initialize chunk configuration for this channel
110 262 const U32 rxChunksPerChannel[] = CFDP_CHANNEL_NUM_RX_CHUNKS_PER_TRANSACTION;
111 262 const U32 txChunksPerChannel[] = CFDP_CHANNEL_NUM_TX_CHUNKS_PER_TRANSACTION;
112 262 m_dirMaxChunks[static_cast<U32>(Direction::DIRECTION_RX)] = rxChunksPerChannel[m_channelId];
113 262 m_dirMaxChunks[static_cast<U32>(Direction::DIRECTION_TX)] = txChunksPerChannel[m_channelId];
114
115 // Calculate total chunks needed for this channel
116 262 total_chunks_needed = 0;
117
2/2
✓ Branch 0 taken 524 times.
✓ Branch 1 taken 262 times.
786 for (k = 0; k < static_cast<U32>(Direction::DIRECTION_NUM); ++k) {
118 524 total_chunks_needed += m_dirMaxChunks[k] * CFDP_NUM_TRANSACTIONS_PER_CHANNEL;
119 }
120
121 // Allocate arrays using the provided allocator
122 262 FwSizeType transactionsSize = CFDP_NUM_TRANSACTIONS_PER_CHANNEL * sizeof(Transaction);
123
1/1
✓ Branch 4 taken 262 times.
262 m_transactions = static_cast<Transaction*>(allocator.allocate(memId, transactionsSize));
124 262 FW_ASSERT(m_transactions != nullptr);
125
126 262 FwSizeType chunksSize =
127 (CFDP_NUM_TRANSACTIONS_PER_CHANNEL * static_cast<U32>(Direction::DIRECTION_NUM)) * sizeof(CfdpChunkWrapper);
128
1/1
✓ Branch 4 taken 262 times.
262 m_chunks = static_cast<CfdpChunkWrapper*>(allocator.allocate(memId, chunksSize));
129 262 FW_ASSERT(m_chunks != nullptr);
130
131 262 FwSizeType historiesSize = NumHistoriesPerChannel * sizeof(History);
132
1/1
✓ Branch 4 taken 262 times.
262 m_histories = static_cast<History*>(allocator.allocate(memId, historiesSize));
133 262 FW_ASSERT(m_histories != nullptr);
134
135 262 FwSizeType chunkMemSize = total_chunks_needed * sizeof(Chunk);
136
1/1
✓ Branch 4 taken 262 times.
262 m_chunkMem = static_cast<Chunk*>(allocator.allocate(memId, chunkMemSize));
137 262 FW_ASSERT(m_chunkMem != nullptr);
138
139 // Initialize transactions using placement new with parameterized constructor
140 262 cw = m_chunks;
141
2/2
✓ Branch 0 taken 13100 times.
✓ Branch 1 taken 262 times.
13362 for (j = 0; j < CFDP_NUM_TRANSACTIONS_PER_CHANNEL; ++j) {
142 // Construct transaction in-place with parameterized constructor
143
1/3
✓ Branch 11 taken 13100 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
13100 txn = new (&m_transactions[j]) Transaction(this, m_channelId, m_engine, m_cfdpManager);
144
145 // Put transaction on free list
146
1/1
✓ Branch 2 taken 13100 times.
13100 this->freeTransaction(txn);
147
148 // Initialize chunk wrappers for this transaction (TX and RX)
149
2/2
✓ Branch 1 taken 26200 times.
✓ Branch 2 taken 13100 times.
39300 for (k = 0; k < static_cast<U32>(Direction::DIRECTION_NUM); ++k, ++cw) {
150 26200 list_head = this->getChunkListHead(static_cast<U8>(k));
151
152 // Use placement new to construct CfdpChunkWrapper with the new class-based interface
153
1/3
✓ Branch 8 taken 26200 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
26200 new (cw) CfdpChunkWrapper(static_cast<ChunkIdx>(m_dirMaxChunks[k]), &m_chunkMem[chunk_mem_offset]);
154 26200 chunk_mem_offset += m_dirMaxChunks[k];
155
1/1
✓ Branch 3 taken 26200 times.
26200 CfdpCListInitNode(&cw->cl_node);
156
1/1
✓ Branch 3 taken 26200 times.
26200 CfdpCListInsertBack(list_head, &cw->cl_node);
157 }
158 }
159
160 // Initialize histories using placement new (History contains Fw::String which needs proper construction)
161
2/2
✓ Branch 0 taken 67072 times.
✓ Branch 1 taken 262 times.
67334 for (j = 0; j < NumHistoriesPerChannel; ++j) {
162
1/3
✓ Branch 6 taken 67072 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
67072 history = new (&m_histories[j]) History(); // Use placement new with default constructor
163
1/1
✓ Branch 3 taken 67072 times.
67072 CfdpCListInitNode(&history->cl_node);
164
1/1
✓ Branch 4 taken 67072 times.
67072 this->insertBackInQueue(QueueId::HIST_FREE, &history->cl_node);
165 }
166 262 }
167
168
2/4
✓ Branch 2 taken 262 times.
✗ Branch 3 not taken.
✓ Branch 14 taken 262 times.
✗ Branch 15 not taken.
4192 Channel::~Channel() {
169 // Cleanup should have been called before destruction
170 // This is enforced by Engine::~Engine()
171 2358 }
172
173 262 void Channel::cleanup(Fw::MemAllocator& allocator, FwEnumStoreType memId) {
174 // Call destructors and deallocate all internal arrays
175
1/2
✓ Branch 2 taken 262 times.
✗ Branch 3 not taken.
262 if (m_transactions != nullptr) {
176 // Manually call destructors since we used placement new
177
2/2
✓ Branch 0 taken 13100 times.
✓ Branch 1 taken 262 times.
13362 for (U32 j = 0; j < CFDP_NUM_TRANSACTIONS_PER_CHANNEL; ++j) {
178 13100 m_transactions[j].~Transaction();
179 }
180 262 allocator.deallocate(memId, m_transactions);
181 262 m_transactions = nullptr;
182 }
183
184
1/2
✓ Branch 2 taken 262 times.
✗ Branch 3 not taken.
262 if (m_chunks != nullptr) {
185 // Manually call destructors since we used placement new
186
2/2
✓ Branch 0 taken 26200 times.
✓ Branch 1 taken 262 times.
26462 for (U32 j = 0; j < (CFDP_NUM_TRANSACTIONS_PER_CHANNEL * static_cast<U32>(Direction::DIRECTION_NUM)); ++j) {
187 26200 m_chunks[j].~CfdpChunkWrapper();
188 }
189 262 allocator.deallocate(memId, m_chunks);
190 262 m_chunks = nullptr;
191 }
192
193
1/2
✓ Branch 2 taken 262 times.
✗ Branch 3 not taken.
262 if (m_histories != nullptr) {
194 // Call destructors on History objects
195
2/2
✓ Branch 0 taken 67072 times.
✓ Branch 1 taken 262 times.
67334 for (U32 j = 0; j < NumHistoriesPerChannel; ++j) {
196 67072 m_histories[j].~History();
197 }
198 262 allocator.deallocate(memId, m_histories);
199 262 m_histories = nullptr;
200 }
201
202
1/2
✓ Branch 2 taken 262 times.
✗ Branch 3 not taken.
262 if (m_chunkMem != nullptr) {
203 262 allocator.deallocate(memId, m_chunkMem);
204 262 m_chunkMem = nullptr;
205 }
206 262 }
207
208 // ----------------------------------------------------------------------
209 // Channel Processing
210 // ----------------------------------------------------------------------
211
212 3894 void Channel::cycleTx() {
213 Transaction* txn;
214 3894 CycleTxArgs args;
215
216
2/3
✓ Branch 6 taken 3894 times.
✓ Branch 15 taken 3894 times.
✗ Branch 16 not taken.
3894 if (m_cfdpManager->getDequeueEnabledParam(m_channelId)) {
217 3894 args.chan = this;
218 3894 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
1/2
✓ Branch 2 taken 3894 times.
✗ Branch 3 not taken.
3894 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 3908 } cycleTxCtx = {this, &args};
234
235 // Attempt to run something on TXA
236
1/1
✓ Branch 3 taken 3908 times.
3908 CfdpCListTraverse(m_qs[QueueId::TXA], &Channel::cycleTxFirstActiveWrapper, &cycleTxCtx);
237
238 // Keep going until QueueId::PEND is empty or something is run
239
4/4
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 3894 times.
✓ Branch 4 taken 3880 times.
✓ Branch 5 taken 14 times.
3908 if (args.ran_one || m_qs[QueueId::PEND] == nullptr) {
240 break;
241 }
242
243 14 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
2/2
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 5 times.
14 if (txn->getClass() == Cfdp::Class::CLASS_2) {
248
1/2
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
9 if (txn->m_chunks == nullptr) {
249
1/1
✓ Branch 2 taken 9 times.
9 txn->m_chunks = this->findUnusedChunks(Direction::DIRECTION_TX);
250 }
251
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
9 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
1/1
✓ Branch 5 taken 14 times.
14 m_engine->armInactTimer(txn);
259
1/1
✓ Branch 2 taken 14 times.
14 this->moveTransaction(txn, QueueId::TXA);
260 14 }
261 }
262
263 // in case the loop exited due to no message buffers, clear it and start from the top next time
264 3894 this->m_currentTxn = nullptr;
265 }
266 3894 }
267
268 3894 void Channel::tickTransactions() {
269 3894 bool reset = true;
270
271 3894 void (Transaction::* fns[static_cast<U8>(CfdpTickType::CFDP_TICK_TYPE_NUM_TYPES)])(I32*) = {
272 &Transaction::rTick, &Transaction::sTick, &Transaction::sTickNak};
273 3894 I32 qs[static_cast<U8>(CfdpTickType::CFDP_TICK_TYPE_NUM_TYPES)] = {QueueId::RX, QueueId::TXW, QueueId::TXW};
274
275 3894 FW_ASSERT(m_tickType < static_cast<U8>(CfdpTickType::CFDP_TICK_TYPE_NUM_TYPES), m_tickType);
276
277
2/2
✓ Branch 6 taken 11682 times.
✓ Branch 7 taken 3894 times.
15576 for (; m_tickType < static_cast<U8>(CfdpTickType::CFDP_TICK_TYPE_NUM_TYPES); ++m_tickType) {
278 11682 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 11682 constexpr U32 maxRetries = MaxSimultaneousRx + MaxCommandedPlaybackFilesPerChan +
283 (MaxCommandedPlaybackDirectoriesPerChan * NumTransactionsPerPlayback) +
284 (MaxPollingDirPerChan * NumTransactionsPerPlayback);
285
286
1/2
✓ Branch 0 taken 11686 times.
✗ Branch 1 not taken.
11686 for (U32 retry = 0; retry < maxRetries; ++retry) {
287 11686 args.cont = 0;
288
289 // Context for static wrapper: pass both Channel* and TickArgs*
290 struct TickContext {
291 Channel* channel;
292 TickArgs* args;
293 11686 } tickCtx = {this, &args};
294
295
1/1
✓ Branch 9 taken 11686 times.
11686 CfdpCListTraverse(m_qs[qs[m_tickType]], &Channel::doTickWrapper, &tickCtx);
296
297
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 11686 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 11686 times.
11686 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
2/2
✓ Branch 0 taken 11682 times.
✓ Branch 1 taken 4 times.
11686 if (!args.cont) {
321 11682 break; // No continuation requested, exit retry loop
322 }
323 }
324
325
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11682 times.
11682 if (!reset) {
326 break;
327 }
328 }
329
330
1/2
✓ Branch 0 taken 3894 times.
✗ Branch 1 not taken.
3894 if (reset) {
331 3894 m_tickType = static_cast<U8>(CfdpTickType::CFDP_TICK_TYPE_RX); // reset tick type
332 }
333 3894 }
334
335 3894 void Channel::processPlaybackDirectories() {
336 U32 i;
337 3894 U8 playback_count = 0;
338
339
2/2
✓ Branch 0 taken 7788 times.
✓ Branch 1 taken 3894 times.
11682 for (i = 0; i < MaxCommandedPlaybackDirectoriesPerChan; ++i) {
340 7788 this->processPlaybackDirectory(&m_playback[i]);
341 // Count active playback operations
342
2/4
✗ Branch 2 not taken.
✓ Branch 3 taken 7788 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 7788 times.
7788 if (m_playback[i].busy) {
343 playback_count++;
344 }
345 }
346
347 // Update playback counter telemetry
348 3894 Cfdp::ChannelTelemetry& tlm = m_engine->getChannelTelemetryRef(m_channelId);
349 3894 tlm.set_playbackCounter(playback_count);
350 3894 }
351
352 3894 void Channel::processPollingDirectories() {
353 CfdpPollDir* pd;
354 U32 i;
355 3894 U8 poll_count = 0;
356
357
2/2
✓ Branch 0 taken 19470 times.
✓ Branch 1 taken 3894 times.
23364 for (i = 0; i < MaxPollingDirPerChan; ++i) {
358 19470 pd = &m_polldir[i];
359
360
2/2
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 19466 times.
19470 if (pd->enabled) {
361 4 poll_count++;
362
363
3/6
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 4 times.
✗ Branch 8 not taken.
4 if ((pd->pb.busy == false) && (pd->pb.num_ts == 0)) {
364
2/2
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 3 times.
8 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
1/2
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
2 (void)m_engine->playbackDirInitiate(&pd->pb, pd->srcDir, pd->dstDir, pd->cfdpClass,
370 1 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
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 if (pd->intervalSec > 0) {
374 1 pd->intervalTimer.setTimer(pd->intervalSec);
375 }
376 } else {
377 3 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 3894 Cfdp::ChannelTelemetry& tlm = m_engine->getChannelTelemetryRef(m_channelId);
388 3894 tlm.set_pollCounter(poll_count);
389 3894 }
390
391 // ----------------------------------------------------------------------
392 // Transaction Management
393 // ----------------------------------------------------------------------
394
395 97 Transaction* Channel::findUnusedTransaction(Direction direction) {
396 CListNode* node;
397 Transaction* txn;
398 QueueId::T q_index; // initialized below in if
399
400
2/2
✓ Branch 2 taken 96 times.
✓ Branch 3 taken 1 times.
97 if (m_qs[QueueId::FREE]) {
401 96 node = m_qs[QueueId::FREE];
402 96 txn = container_of_cpp(node, &Transaction::m_cl_node);
403
404 96 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
1/2
✓ Branch 2 taken 96 times.
✗ Branch 3 not taken.
96 if (m_qs[QueueId::HIST_FREE]) {
408 96 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 96 txn->m_history = container_of_cpp(m_qs[q_index], &History::cl_node);
416
417 96 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 96 txn->m_history->txn_stat = TxnStatus::TXN_STATUS_UNDEFINED; // Critical: prevents error status inheritance
422 96 txn->m_history->src_eid = 0;
423 96 txn->m_history->peer_eid = 0;
424 96 txn->m_history->seq_num = 0;
425 96 txn->m_history->fnames.src_filename = "";
426 96 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 96 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 96 txn->m_flags.com.q_index = QueueId::PEND;
443
444 96 txn->m_history->dir = direction;
445 96 txn->m_chan = this; // Set channel pointer
446
447 // Re-initialize the linked list node to clear stale pointers from FREE list
448 96 CfdpCListInitNode(&txn->m_cl_node);
449 } else {
450 1 txn = nullptr;
451 }
452
453 97 return txn;
454 }
455
456 153 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 153 CfdpTraverseTransSeqArg ctx = {transaction_sequence_number, src_eid, nullptr};
462 153 CListNode* ptrs[] = {m_qs[QueueId::RX], m_qs[QueueId::PEND], m_qs[QueueId::TXA], m_qs[QueueId::TXW]};
463 153 Transaction* ret = nullptr;
464
465
2/2
✓ Branch 3 taken 468 times.
✓ Branch 4 taken 87 times.
555 for (CListNode* head : ptrs) {
466
1/1
✓ Branch 1 taken 468 times.
468 CfdpCListTraverse(head, Transaction::findBySequenceNumberCallback, &ctx);
467
2/2
✓ Branch 0 taken 66 times.
✓ Branch 1 taken 402 times.
468 if (ctx.txn) {
468 66 ret = ctx.txn;
469 66 break;
470 }
471 }
472
473 306 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 37 void Channel::dequeueTransaction(Transaction* txn) {
503 37 FW_ASSERT(txn);
504 37 CfdpCListRemove(&m_qs[txn->m_flags.com.q_index], &txn->m_cl_node);
505
506 // Update queue depth telemetry
507 37 Cfdp::ChannelTelemetry& tlm = m_engine->getChannelTelemetryRef(m_channelId);
508
3/7
✗ Branch 2 not taken.
✓ Branch 3 taken 14 times.
✓ Branch 4 taken 11 times.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
37 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 14 case Cfdp::QueueId::TXA:
514
515 14 tlm.set_queueTxActive(static_cast<U16>(tlm.get_queueTxActive() - 1));
516 14 break;
517 11 case Cfdp::QueueId::TXW:
518
519 11 tlm.set_queueTxWaiting(static_cast<U16>(tlm.get_queueTxWaiting() - 1));
520 11 break;
521 12 case Cfdp::QueueId::RX:
522
523 12 tlm.set_queueRx(static_cast<U16>(tlm.get_queueRx() - 1));
524 12 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 37 }
537
538 14 void Channel::moveTransaction(Transaction* txn, QueueId::T queue) {
539 14 FW_ASSERT(txn);
540 14 Cfdp::ChannelTelemetry& tlm = m_engine->getChannelTelemetryRef(m_channelId);
541
542 // Decrement old queue
543 14 CfdpCListRemove(&m_qs[txn->m_flags.com.q_index], &txn->m_cl_node);
544
1/7
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 14 times.
✗ Branch 8 not taken.
14 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 14 case Cfdp::QueueId::PEND:
566 case Cfdp::QueueId::HIST_FREE:
567 // PEND and HIST_FREE queues are not tracked in telemetry
568 14 break;
569 default:
570 FW_ASSERT(0, txn->m_flags.com.q_index);
571 }
572
573 // Increment new queue
574 14 CfdpCListInsertBack(&m_qs[queue], &txn->m_cl_node);
575 14 txn->m_flags.com.q_index = queue;
576
1/7
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
14 switch (queue) {
577 case Cfdp::QueueId::FREE:
578 tlm.set_queueFree(static_cast<U16>(tlm.get_queueFree() + 1));
579 break;
580 14 case Cfdp::QueueId::TXA:
581 14 tlm.set_queueTxActive(static_cast<U16>(tlm.get_queueTxActive() + 1));
582 14 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 14 }
600
601 13124 void Channel::freeTransaction(Transaction* txn) {
602 // Reset transaction to default state (preserves channel context)
603 13124 txn->reset();
604
605 // Initialize the linked list node for the FREE queue
606 13124 CfdpCListInitNode(&txn->m_cl_node);
607 13124 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 13124 txn->m_flags.com.q_index = QueueId::FREE;
617 13124 }
618
619 23 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
2/2
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 22 times.
23 if (true == txn->m_fd.isOpen()) {
627 1 this->m_cfdpManager->log_WARNING_LO_DanglingFileHandleClosed(txn->getChannelId(), txn->m_history->seq_num);
628 1 txn->m_fd.close();
629 }
630
631 23 this->dequeueTransaction(txn); // this makes it "float" (not in any queue)
632
633 // this should always be
634
1/2
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
23 if (txn->m_history != nullptr) {
635
2/2
✓ Branch 2 taken 18 times.
✓ Branch 3 taken 5 times.
23 if (txn->m_chunks != nullptr) {
636 18 chunklist_head = this->getChunkListHead(static_cast<U8>(txn->m_history->dir));
637
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 if (chunklist_head != nullptr) {
638 // Reset chunk list to clear stale data from previous transaction
639 18 txn->m_chunks->chunks.reset();
640 18 CfdpCListInsertBack(chunklist_head, &txn->m_chunks->cl_node);
641 18 txn->m_chunks = nullptr;
642 }
643 }
644
645
3/4
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
✓ Branch 3 taken 22 times.
✓ Branch 4 taken 1 times.
23 if (txn->m_flags.com.keep_history) {
646 // move transaction history to history queue
647 22 hist_destq = QueueId::HIST;
648 } else {
649 1 hist_destq = QueueId::HIST_FREE;
650 }
651 23 this->insertBackInQueue(hist_destq, &txn->m_history->cl_node);
652 23 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 23 this->freeTransaction(txn);
659 23 }
660
661 29 void Channel::insertSortPrio(Transaction* txn, QueueId::T queue) {
662 29 bool insert_back = false;
663
664 29 FW_ASSERT(txn);
665
666 // look for proper position on PEND queue for this transaction.
667 // This is a simple priority sort.
668
669
1/2
✓ Branch 3 taken 29 times.
✗ Branch 4 not taken.
29 if (!m_qs[queue]) {
670 // list is empty, so just insert
671 29 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
1/2
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
29 if (insert_back) {
683 29 this->insertBackInQueue(queue, &txn->m_cl_node);
684 }
685 29 txn->m_flags.com.q_index = queue;
686 29 }
687
688 // ----------------------------------------------------------------------
689 // Channel State Management
690 // ----------------------------------------------------------------------
691
692 12 void Channel::decrementCmdTxCounter() {
693 12 FW_ASSERT(m_numCmdTx); // sanity check
694 12 --m_numCmdTx;
695 12 }
696
697 37 void Channel::clearCurrentIfMatch(Transaction* txn) {
698 // Done with this TX transaction
699
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 37 times.
37 if (this->m_currentTxn == txn) {
700 this->m_currentTxn = nullptr;
701 }
702 37 }
703
704 void Channel::setCurrentTxn(const Transaction* txn) {
705 this->m_currentTxn = txn;
706 }
707
708 // ----------------------------------------------------------------------
709 // Resource Management
710 // ----------------------------------------------------------------------
711
712 26360 CListNode** Channel::getChunkListHead(U8 direction) {
713 CListNode** result;
714
715
1/2
✓ Branch 0 taken 26360 times.
✗ Branch 1 not taken.
26360 if (direction < static_cast<U32>(Direction::DIRECTION_NUM)) {
716 26360 result = &m_cs[direction];
717 } else {
718 result = nullptr;
719 }
720
721 26360 return result;
722 }
723
724 142 CfdpChunkWrapper* Channel::findUnusedChunks(Direction dir) {
725 142 CfdpChunkWrapper* ret = nullptr;
726 CListNode* node;
727 CListNode** chunklist_head;
728
729 142 chunklist_head = this->getChunkListHead(static_cast<U8>(dir));
730
731 // this should never be null
732 142 FW_ASSERT(chunklist_head);
733
734
2/2
✓ Branch 1 taken 140 times.
✓ Branch 2 taken 2 times.
142 if (*chunklist_head != nullptr) {
735 140 node = CfdpCListPop(chunklist_head);
736
1/2
✓ Branch 0 taken 140 times.
✗ Branch 1 not taken.
140 if (node != nullptr) {
737 140 ret = container_of_cpp(node, &CfdpChunkWrapper::cl_node);
738 }
739 }
740
741 142 return ret;
742 }
743
744 // ----------------------------------------------------------------------
745 // Private helper methods
746 // ----------------------------------------------------------------------
747
748 7788 void Channel::processPlaybackDirectory(Playback* pb) {
749 Transaction* txn;
750
1/1
✓ Branch 2 taken 7788 times.
7788 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
4/6
✗ Branch 1 not taken.
✓ Branch 2 taken 7788 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 7787 times.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
7788 while (pb->diropen && (pb->num_ts < NumTransactionsPerPlayback)) {
756
2/3
✓ Branch 6 taken 1 times.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
1 if (pb->pending_file.length() == 0) {
757
1/1
✓ Branch 5 taken 1 times.
1 status = pb->dir.read(path);
758
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 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
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (status != Os::Directory::OP_OK) {
765 // Directory read error - emit EVR and close playback
766
1/1
✓ Branch 10 taken 1 times.
1 this->m_cfdpManager->log_WARNING_LO_PlaybackDirReadFailed(pb->fnames.src_filename,
767 static_cast<I32>(status));
768
1/1
✓ Branch 5 taken 1 times.
1 pb->dir.close();
769 1 pb->diropen = false;
770 1 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
3/6
✗ Branch 1 not taken.
✓ Branch 2 taken 7788 times.
✓ Branch 3 taken 7788 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 7788 times.
✗ Branch 8 not taken.
7788 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 7788 pb->busy = false;
805 }
806 15576 }
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 14 CListTraverseStatus Channel::cycleTxFirstActive(CListNode* node, void* context) {
823 14 CycleTxArgs* args = static_cast<CycleTxArgs*>(context);
824 14 Transaction* txn = container_of_cpp(node, &Transaction::m_cl_node);
825 14 CListTraverseStatus ret = CLIST_TRAVERSE_EXIT; // default option is exit traversal
826
827
2/4
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 14 times.
14 if (txn->m_flags.com.suspended) {
828 ret = CLIST_TRAVERSE_CONTINUE; // suspended, so move on to next
829 } else {
830 14 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
3/4
✓ Branch 2 taken 72 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 58 times.
✓ Branch 7 taken 14 times.
72 while (!this->m_currentTxn && txn->m_flags.com.q_index == QueueId::TXA) {
836 58 m_engine->dispatchTx(txn);
837 }
838
839 14 args->ran_one = 1;
840 }
841
842 14 return ret;
843 }
844
845 201 CListTraverseStatus Channel::doTick(CListNode* node, void* context) {
846 201 CListTraverseStatus ret =
847 CLIST_TRAVERSE_CONTINUE; // CLIST_TRAVERSE_CONTINUE means don't tick one, keep looking for currentTxn
848 201 TickArgs* args = static_cast<TickArgs*>(context);
849 201 Transaction* txn = container_of_cpp(node, &Transaction::m_cl_node);
850
1/4
✗ Branch 2 not taken.
✓ Branch 3 taken 201 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
201 if (!this->m_currentTxn || (this->m_currentTxn == txn)) {
851 // found where we left off, so clear that and move on
852 201 this->m_currentTxn = nullptr;
853
2/4
✗ Branch 1 not taken.
✓ Branch 2 taken 201 times.
✓ Branch 3 taken 201 times.
✗ Branch 4 not taken.
201 if (!txn->m_flags.com.suspended) {
854
1/2
✗ Branch 6 not taken.
✓ Branch 7 taken 201 times.
201 (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
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 201 times.
201 if (this->m_currentTxn) {
861 ret = CLIST_TRAVERSE_EXIT;
862 args->early_exit = true;
863 }
864 }
865
866 201 return ret; // don't tick one, keep looking for currentTxn
867 }
868
869 32 Transaction* Channel::getTransaction(U32 index) {
870 32 FW_ASSERT(index < CFDP_NUM_TRANSACTIONS_PER_CHANNEL);
871 32 return &m_transactions[index];
872 }
873
874 32 History* Channel::getHistory(U32 index) {
875 32 FW_ASSERT(index < NumHistoriesPerChannel);
876 32 return &m_histories[index];
877 }
878
879 // ----------------------------------------------------------------------
880 // Static callback wrapper implementations
881 // ----------------------------------------------------------------------
882
883 14 CListTraverseStatus Channel::cycleTxFirstActiveWrapper(CListNode* node, void* context) {
884 struct CycleTxContext {
885 Channel* channel;
886 CycleTxArgs* args;
887 };
888 14 CycleTxContext* ctx = static_cast<CycleTxContext*>(context);
889 14 return ctx->channel->cycleTxFirstActive(node, ctx->args);
890 }
891
892 201 CListTraverseStatus Channel::doTickWrapper(CListNode* node, void* context) {
893 struct TickContext {
894 Channel* channel;
895 TickArgs* args;
896 };
897 201 TickContext* ctx = static_cast<TickContext*>(context);
898 201 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