GCC Code Coverage Report


Directory: Svc/Ccsds/CfdpManager/
File: Channel.cpp
Date: 2026-11-03 21:17:54
Exec Total Coverage
Lines: 342 431 79.4%
Functions: 25 30 83.3%
Branches: 177 273 64.8%

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 314 Channel::Channel(Engine* engine,
52 U8 channelId,
53 CfdpManager* cfdpManager,
54 Fw::MemAllocator& allocator,
55 314 FwEnumStoreType memId)
56 314 : m_engine(engine),
57 314 m_numCmdTx(0),
58 314 m_currentTxn(nullptr),
59 314 m_cfdpManager(cfdpManager),
60 314 m_tickType(0),
61 314 m_channelId(channelId),
62 314 m_flowState(Cfdp::Flow::NOT_FROZEN),
63 314 m_outgoingCounter(0),
64 314 m_transactions(nullptr),
65 314 m_histories(nullptr),
66 314 m_chunks(nullptr),
67
6/18
✓ Branch 4 taken 628 times.
✓ Branch 7 taken 628 times.
✓ Branch 8 taken 314 times.
✓ Branch 14 taken 1570 times.
✓ Branch 17 taken 1570 times.
✓ Branch 18 taken 314 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.
2826 m_chunkMem(nullptr) {
68 314 FW_ASSERT(engine != nullptr);
69 314 FW_ASSERT(cfdpManager != nullptr);
70
71 // Initialize queue pointers
72
2/2
✓ Branch 0 taken 2198 times.
✓ Branch 1 taken 314 times.
2512 for (U32 i = 0; i < QueueId::NUM; i++) {
73 2198 m_qs[i] = nullptr;
74 }
75
76 // Initialize command/history lists
77
2/2
✓ Branch 0 taken 628 times.
✓ Branch 1 taken 314 times.
942 for (U32 i = 0; i < static_cast<U32>(Direction::DIRECTION_NUM); i++) {
78 628 m_cs[i] = nullptr;
79 }
80
81 // Initialize poll directory playback state
82
2/2
✓ Branch 0 taken 1570 times.
✓ Branch 1 taken 314 times.
1884 for (U32 i = 0; i < MaxPollingDirPerChan; i++) {
83
1/1
✓ Branch 7 taken 1570 times.
1570 m_polldir[i].enabled = Fw::Enabled::DISABLED;
84 1570 m_polldir[i].pb.busy = false;
85 1570 m_polldir[i].pb.diropen = false;
86 1570 m_polldir[i].pb.counted = false;
87 1570 m_polldir[i].pb.num_ts = 0;
88
1/1
✓ Branch 7 taken 1570 times.
1570 m_polldir[i].pb.pending_file = "";
89 }
90
91 // Initialize playback structures
92
2/2
✓ Branch 0 taken 628 times.
✓ Branch 1 taken 314 times.
942 for (U32 i = 0; i < MaxCommandedPlaybackDirectoriesPerChan; i++) {
93 628 m_playback[i].busy = false;
94 628 m_playback[i].diropen = false;
95 628 m_playback[i].counted = false;
96 628 m_playback[i].num_ts = 0;
97
1/1
✓ Branch 7 taken 628 times.
628 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 314 U32 chunk_mem_offset = 0;
107 U32 total_chunks_needed;
108
109 // Initialize chunk configuration for this channel
110 314 const U32 rxChunksPerChannel[] = CFDP_CHANNEL_NUM_RX_CHUNKS_PER_TRANSACTION;
111 314 const U32 txChunksPerChannel[] = CFDP_CHANNEL_NUM_TX_CHUNKS_PER_TRANSACTION;
112 314 m_dirMaxChunks[static_cast<U32>(Direction::DIRECTION_RX)] = rxChunksPerChannel[m_channelId];
113 314 m_dirMaxChunks[static_cast<U32>(Direction::DIRECTION_TX)] = txChunksPerChannel[m_channelId];
114
115 // Calculate total chunks needed for this channel
116 314 total_chunks_needed = 0;
117
2/2
✓ Branch 0 taken 628 times.
✓ Branch 1 taken 314 times.
942 for (k = 0; k < static_cast<U32>(Direction::DIRECTION_NUM); ++k) {
118 628 total_chunks_needed += m_dirMaxChunks[k] * CFDP_NUM_TRANSACTIONS_PER_CHANNEL;
119 }
120
121 // Allocate arrays using the provided allocator
122 314 FwSizeType transactionsSize = CFDP_NUM_TRANSACTIONS_PER_CHANNEL * sizeof(Transaction);
123
1/1
✓ Branch 4 taken 314 times.
314 m_transactions = static_cast<Transaction*>(allocator.allocate(memId, transactionsSize));
124 314 FW_ASSERT(m_transactions != nullptr);
125
126 314 FwSizeType chunksSize =
127 (CFDP_NUM_TRANSACTIONS_PER_CHANNEL * static_cast<U32>(Direction::DIRECTION_NUM)) * sizeof(CfdpChunkWrapper);
128
1/1
✓ Branch 4 taken 314 times.
314 m_chunks = static_cast<CfdpChunkWrapper*>(allocator.allocate(memId, chunksSize));
129 314 FW_ASSERT(m_chunks != nullptr);
130
131 314 FwSizeType historiesSize = NumHistoriesPerChannel * sizeof(History);
132
1/1
✓ Branch 4 taken 314 times.
314 m_histories = static_cast<History*>(allocator.allocate(memId, historiesSize));
133 314 FW_ASSERT(m_histories != nullptr);
134
135 314 FwSizeType chunkMemSize = total_chunks_needed * sizeof(Chunk);
136
1/1
✓ Branch 4 taken 314 times.
314 m_chunkMem = static_cast<Chunk*>(allocator.allocate(memId, chunkMemSize));
137 314 FW_ASSERT(m_chunkMem != nullptr);
138
139 // Initialize transactions using placement new with parameterized constructor
140 314 cw = m_chunks;
141
2/2
✓ Branch 0 taken 15700 times.
✓ Branch 1 taken 314 times.
16014 for (j = 0; j < CFDP_NUM_TRANSACTIONS_PER_CHANNEL; ++j) {
142 // Construct transaction in-place with parameterized constructor
143
1/3
✓ Branch 11 taken 15700 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
15700 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 15700 times.
15700 this->freeTransaction(txn);
147
148 // Initialize chunk wrappers for this transaction (TX and RX)
149
2/2
✓ Branch 1 taken 31400 times.
✓ Branch 2 taken 15700 times.
47100 for (k = 0; k < static_cast<U32>(Direction::DIRECTION_NUM); ++k, ++cw) {
150 31400 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 31400 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
31400 new (cw) CfdpChunkWrapper(static_cast<ChunkIdx>(m_dirMaxChunks[k]), &m_chunkMem[chunk_mem_offset]);
154 31400 chunk_mem_offset += m_dirMaxChunks[k];
155
1/1
✓ Branch 3 taken 31400 times.
31400 CfdpCListInitNode(&cw->cl_node);
156
1/1
✓ Branch 3 taken 31400 times.
31400 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 80384 times.
✓ Branch 1 taken 314 times.
80698 for (j = 0; j < NumHistoriesPerChannel; ++j) {
162
1/3
✓ Branch 6 taken 80384 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
80384 history = new (&m_histories[j]) History(); // Use placement new with default constructor
163
1/1
✓ Branch 3 taken 80384 times.
80384 CfdpCListInitNode(&history->cl_node);
164
1/1
✓ Branch 4 taken 80384 times.
80384 this->insertBackInQueue(QueueId::HIST_FREE, &history->cl_node);
165 }
166 314 }
167
168
2/4
✓ Branch 2 taken 314 times.
✗ Branch 3 not taken.
✓ Branch 14 taken 314 times.
✗ Branch 15 not taken.
5024 Channel::~Channel() {
169 // Cleanup should have been called before destruction
170 // This is enforced by Engine::~Engine()
171 2826 }
172
173 314 void Channel::cleanup(Fw::MemAllocator& allocator, FwEnumStoreType memId) {
174 // Call destructors and deallocate all internal arrays
175
1/2
✓ Branch 2 taken 314 times.
✗ Branch 3 not taken.
314 if (m_transactions != nullptr) {
176 // Manually call destructors since we used placement new
177
2/2
✓ Branch 0 taken 15700 times.
✓ Branch 1 taken 314 times.
16014 for (U32 j = 0; j < CFDP_NUM_TRANSACTIONS_PER_CHANNEL; ++j) {
178 15700 m_transactions[j].~Transaction();
179 }
180 314 allocator.deallocate(memId, m_transactions);
181 314 m_transactions = nullptr;
182 }
183
184
1/2
✓ Branch 2 taken 314 times.
✗ Branch 3 not taken.
314 if (m_chunks != nullptr) {
185 // Manually call destructors since we used placement new
186
2/2
✓ Branch 0 taken 31400 times.
✓ Branch 1 taken 314 times.
31714 for (U32 j = 0; j < (CFDP_NUM_TRANSACTIONS_PER_CHANNEL * static_cast<U32>(Direction::DIRECTION_NUM)); ++j) {
187 31400 m_chunks[j].~CfdpChunkWrapper();
188 }
189 314 allocator.deallocate(memId, m_chunks);
190 314 m_chunks = nullptr;
191 }
192
193
1/2
✓ Branch 2 taken 314 times.
✗ Branch 3 not taken.
314 if (m_histories != nullptr) {
194 // Call destructors on History objects
195
2/2
✓ Branch 0 taken 80384 times.
✓ Branch 1 taken 314 times.
80698 for (U32 j = 0; j < NumHistoriesPerChannel; ++j) {
196 80384 m_histories[j].~History();
197 }
198 314 allocator.deallocate(memId, m_histories);
199 314 m_histories = nullptr;
200 }
201
202
1/2
✓ Branch 2 taken 314 times.
✗ Branch 3 not taken.
314 if (m_chunkMem != nullptr) {
203 314 allocator.deallocate(memId, m_chunkMem);
204 314 m_chunkMem = nullptr;
205 }
206 314 }
207
208 // ----------------------------------------------------------------------
209 // Channel Processing
210 // ----------------------------------------------------------------------
211
212 4644 void Channel::cycleTx() {
213 Transaction* txn;
214 4644 CycleTxArgs args;
215
216
2/3
✓ Branch 6 taken 4644 times.
✓ Branch 15 taken 4644 times.
✗ Branch 16 not taken.
4644 if (m_cfdpManager->getDequeueEnabledParam(m_channelId)) {
217 4644 args.chan = this;
218 4644 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 4644 times.
✗ Branch 3 not taken.
4644 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 4661 } cycleTxCtx = {this, &args};
234
235 // Attempt to run something on TXA
236
1/1
✓ Branch 3 taken 4661 times.
4661 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 17 times.
✓ Branch 1 taken 4644 times.
✓ Branch 4 taken 4627 times.
✓ Branch 5 taken 17 times.
4661 if (args.ran_one || m_qs[QueueId::PEND] == nullptr) {
240 break;
241 }
242
243 17 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 10 times.
✓ Branch 3 taken 7 times.
17 if (txn->getClass() == Cfdp::Class::CLASS_2) {
248
1/2
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
10 if (txn->m_chunks == nullptr) {
249
1/1
✓ Branch 2 taken 10 times.
10 txn->m_chunks = this->findUnusedChunks(Direction::DIRECTION_TX);
250 }
251
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
10 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 17 times.
17 m_engine->armInactTimer(txn);
259
1/1
✓ Branch 2 taken 17 times.
17 this->moveTransaction(txn, QueueId::TXA);
260 17 }
261 }
262
263 // in case the loop exited due to no message buffers, clear it and start from the top next time
264 4644 this->m_currentTxn = nullptr;
265 }
266 4644 }
267
268 4644 void Channel::tickTransactions() {
269 4644 bool reset = true;
270
271 4644 void (Transaction::* fns[static_cast<U8>(CfdpTickType::CFDP_TICK_TYPE_NUM_TYPES)])(I32*) = {
272 &Transaction::rTick, &Transaction::sTick, &Transaction::sTickNak};
273 4644 I32 qs[static_cast<U8>(CfdpTickType::CFDP_TICK_TYPE_NUM_TYPES)] = {QueueId::RX, QueueId::TXW, QueueId::TXW};
274
275 4644 FW_ASSERT(m_tickType < static_cast<U8>(CfdpTickType::CFDP_TICK_TYPE_NUM_TYPES), m_tickType);
276
277
2/2
✓ Branch 6 taken 13932 times.
✓ Branch 7 taken 4644 times.
18576 for (; m_tickType < static_cast<U8>(CfdpTickType::CFDP_TICK_TYPE_NUM_TYPES); ++m_tickType) {
278 13932 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 13932 constexpr U32 maxRetries = MaxSimultaneousRx + MaxCommandedPlaybackFilesPerChan +
283 (MaxCommandedPlaybackDirectoriesPerChan * NumTransactionsPerPlayback) +
284 (MaxPollingDirPerChan * NumTransactionsPerPlayback);
285
286
1/2
✓ Branch 0 taken 13936 times.
✗ Branch 1 not taken.
13936 for (U32 retry = 0; retry < maxRetries; ++retry) {
287 13936 args.cont = 0;
288
289 // Context for static wrapper: pass both Channel* and TickArgs*
290 struct TickContext {
291 Channel* channel;
292 TickArgs* args;
293 13936 } tickCtx = {this, &args};
294
295
1/1
✓ Branch 9 taken 13936 times.
13936 CfdpCListTraverse(m_qs[qs[m_tickType]], &Channel::doTickWrapper, &tickCtx);
296
297
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 13936 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 13936 times.
13936 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 13932 times.
✓ Branch 1 taken 4 times.
13936 if (!args.cont) {
321 13932 break; // No continuation requested, exit retry loop
322 }
323 }
324
325
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13932 times.
13932 if (!reset) {
326 ✗ break;
327 }
328 }
329
330
1/2
✓ Branch 0 taken 4644 times.
✗ Branch 1 not taken.
4644 if (reset) {
331 4644 m_tickType = static_cast<U8>(CfdpTickType::CFDP_TICK_TYPE_RX); // reset tick type
332 }
333 4644 }
334
335 4644 void Channel::processPlaybackDirectories() {
336 U32 i;
337 4644 U8 playback_count = 0;
338
339
2/2
✓ Branch 0 taken 9288 times.
✓ Branch 1 taken 4644 times.
13932 for (i = 0; i < MaxCommandedPlaybackDirectoriesPerChan; ++i) {
340 9288 this->processPlaybackDirectory(&m_playback[i]);
341 // Count active playback operations
342
3/4
✗ Branch 2 not taken.
✓ Branch 3 taken 9288 times.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 9285 times.
9288 if (m_playback[i].busy) {
343 3 playback_count++;
344 }
345 }
346
347 // Update playback counter telemetry
348 4644 Cfdp::ChannelTelemetry& tlm = m_engine->getChannelTelemetryRef(m_channelId);
349 4644 tlm.set_playbackCounter(playback_count);
350 4644 }
351
352 4644 void Channel::processPollingDirectories() {
353 CfdpPollDir* pd;
354 U32 i;
355 4644 U8 poll_count = 0;
356
357
2/2
✓ Branch 0 taken 23220 times.
✓ Branch 1 taken 4644 times.
27864 for (i = 0; i < MaxPollingDirPerChan; ++i) {
358 23220 pd = &m_polldir[i];
359
360
2/2
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 23216 times.
23220 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 4644 Cfdp::ChannelTelemetry& tlm = m_engine->getChannelTelemetryRef(m_channelId);
388 4644 tlm.set_pollCounter(poll_count);
389 4644 }
390
391 // ----------------------------------------------------------------------
392 // Transaction Management
393 // ----------------------------------------------------------------------
394
395 113 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 112 times.
✓ Branch 3 taken 1 times.
113 if (m_qs[QueueId::FREE]) {
401 112 node = m_qs[QueueId::FREE];
402 112 txn = container_of_cpp(node, &Transaction::m_cl_node);
403
404 112 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 112 times.
✗ Branch 3 not taken.
112 if (m_qs[QueueId::HIST_FREE]) {
408 112 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 112 txn->m_history = container_of_cpp(m_qs[q_index], &History::cl_node);
416
417 112 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 112 txn->m_history->txn_stat = TxnStatus::TXN_STATUS_UNDEFINED; // Critical: prevents error status inheritance
422 112 txn->m_history->src_eid = 0;
423 112 txn->m_history->peer_eid = 0;
424 112 txn->m_history->seq_num = 0;
425 112 txn->m_history->fnames.src_filename = "";
426 112 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 112 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 112 txn->m_flags.com.q_index = QueueId::PEND;
443
444 112 txn->m_history->dir = direction;
445 112 txn->m_chan = this; // Set channel pointer
446
447 // Re-initialize the linked list node to clear stale pointers from FREE list
448 112 CfdpCListInitNode(&txn->m_cl_node);
449 } else {
450 1 txn = nullptr;
451 }
452
453 113 return txn;
454 }
455
456 171 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 171 CfdpTraverseTransSeqArg ctx = {transaction_sequence_number, src_eid, nullptr};
462 171 CListNode* ptrs[] = {m_qs[QueueId::RX], m_qs[QueueId::PEND], m_qs[QueueId::TXA], m_qs[QueueId::TXW]};
463 171 Transaction* ret = nullptr;
464
465
2/2
✓ Branch 3 taken 531 times.
✓ Branch 4 taken 98 times.
629 for (CListNode* head : ptrs) {
466
1/1
✓ Branch 1 taken 531 times.
531 CfdpCListTraverse(head, Transaction::findBySequenceNumberCallback, &ctx);
467
2/2
✓ Branch 0 taken 73 times.
✓ Branch 1 taken 458 times.
531 if (ctx.txn) {
468 73 ret = ctx.txn;
469 73 break;
470 }
471 }
472
473 342 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 43 void Channel::dequeueTransaction(Transaction* txn) {
503 43 FW_ASSERT(txn);
504 43 CfdpCListRemove(&m_qs[txn->m_flags.com.q_index], &txn->m_cl_node);
505
506 // Update queue depth telemetry
507 43 Cfdp::ChannelTelemetry& tlm = m_engine->getChannelTelemetryRef(m_channelId);
508
3/7
✗ Branch 2 not taken.
✓ Branch 3 taken 17 times.
✓ Branch 4 taken 14 times.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
43 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 17 case Cfdp::QueueId::TXA:
514
515 17 tlm.set_queueTxActive(static_cast<U16>(tlm.get_queueTxActive() - 1));
516 17 break;
517 14 case Cfdp::QueueId::TXW:
518
519 14 tlm.set_queueTxWaiting(static_cast<U16>(tlm.get_queueTxWaiting() - 1));
520 14 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 43 }
537
538 17 void Channel::moveTransaction(Transaction* txn, QueueId::T queue) {
539 17 FW_ASSERT(txn);
540 17 Cfdp::ChannelTelemetry& tlm = m_engine->getChannelTelemetryRef(m_channelId);
541
542 // Decrement old queue
543 17 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 17 times.
✗ Branch 8 not taken.
17 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 17 case Cfdp::QueueId::PEND:
566 case Cfdp::QueueId::HIST_FREE:
567 // PEND and HIST_FREE queues are not tracked in telemetry
568 17 break;
569 ✗ default:
570 ✗ FW_ASSERT(0, txn->m_flags.com.q_index);
571 }
572
573 // Increment new queue
574 17 CfdpCListInsertBack(&m_qs[queue], &txn->m_cl_node);
575 17 txn->m_flags.com.q_index = queue;
576
1/7
✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
17 switch (queue) {
577 ✗ case Cfdp::QueueId::FREE:
578 ✗ tlm.set_queueFree(static_cast<U16>(tlm.get_queueFree() + 1));
579 ✗ break;
580 17 case Cfdp::QueueId::TXA:
581 17 tlm.set_queueTxActive(static_cast<U16>(tlm.get_queueTxActive() + 1));
582 17 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 17 }
600
601 15727 void Channel::freeTransaction(Transaction* txn) {
602 // Reset transaction to default state (preserves channel context)
603 15727 txn->reset();
604
605 // Initialize the linked list node for the FREE queue
606 15727 CfdpCListInitNode(&txn->m_cl_node);
607 15727 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 15727 txn->m_flags.com.q_index = QueueId::FREE;
617 15727 }
618
619 26 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 25 times.
26 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 26 this->dequeueTransaction(txn); // this makes it "float" (not in any queue)
632
633 // this should always be
634
1/2
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
26 if (txn->m_history != nullptr) {
635
2/2
✓ Branch 2 taken 19 times.
✓ Branch 3 taken 7 times.
26 if (txn->m_chunks != nullptr) {
636 19 chunklist_head = this->getChunkListHead(static_cast<U8>(txn->m_history->dir));
637
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 if (chunklist_head != nullptr) {
638 // Reset chunk list to clear stale data from previous transaction
639 19 txn->m_chunks->chunks.reset();
640 19 CfdpCListInsertBack(chunklist_head, &txn->m_chunks->cl_node);
641 19 txn->m_chunks = nullptr;
642 }
643 }
644
645
3/4
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
✓ Branch 3 taken 24 times.
✓ Branch 4 taken 2 times.
26 if (txn->m_flags.com.keep_history) {
646 // move transaction history to history queue
647 24 hist_destq = QueueId::HIST;
648 } else {
649 2 hist_destq = QueueId::HIST_FREE;
650 }
651 26 this->insertBackInQueue(hist_destq, &txn->m_history->cl_node);
652 26 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 26 this->freeTransaction(txn);
659 26 }
660
661 37 void Channel::insertSortPrio(Transaction* txn, QueueId::T queue) {
662 37 bool insert_back = false;
663
664 37 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 37 times.
✗ Branch 4 not taken.
37 if (!m_qs[queue]) {
670 // list is empty, so just insert
671 37 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 37 times.
✗ Branch 1 not taken.
37 if (insert_back) {
683 37 this->insertBackInQueue(queue, &txn->m_cl_node);
684 }
685 37 txn->m_flags.com.q_index = queue;
686 37 }
687
688 // ----------------------------------------------------------------------
689 // Channel State Management
690 // ----------------------------------------------------------------------
691
692 14 void Channel::decrementCmdTxCounter() {
693 14 FW_ASSERT(m_numCmdTx); // sanity check
694 14 --m_numCmdTx;
695 14 }
696
697 47 void Channel::clearCurrentIfMatch(Transaction* txn) {
698 // Done with this TX transaction
699
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 47 times.
47 if (this->m_currentTxn == txn) {
700 ✗ this->m_currentTxn = nullptr;
701 }
702 47 }
703
704 ✗ void Channel::setCurrentTxn(const Transaction* txn) {
705 ✗ this->m_currentTxn = txn;
706 ✗ }
707
708 // ----------------------------------------------------------------------
709 // Resource Management
710 // ----------------------------------------------------------------------
711
712 31573 CListNode** Channel::getChunkListHead(U8 direction) {
713 CListNode** result;
714
715
1/2
✓ Branch 0 taken 31573 times.
✗ Branch 1 not taken.
31573 if (direction < static_cast<U32>(Direction::DIRECTION_NUM)) {
716 31573 result = &m_cs[direction];
717 } else {
718 ✗ result = nullptr;
719 }
720
721 31573 return result;
722 }
723
724 154 CfdpChunkWrapper* Channel::findUnusedChunks(Direction dir) {
725 154 CfdpChunkWrapper* ret = nullptr;
726 CListNode* node;
727 CListNode** chunklist_head;
728
729 154 chunklist_head = this->getChunkListHead(static_cast<U8>(dir));
730
731 // this should never be null
732 154 FW_ASSERT(chunklist_head);
733
734
2/2
✓ Branch 1 taken 152 times.
✓ Branch 2 taken 2 times.
154 if (*chunklist_head != nullptr) {
735 152 node = CfdpCListPop(chunklist_head);
736
1/2
✓ Branch 0 taken 152 times.
✗ Branch 1 not taken.
152 if (node != nullptr) {
737 152 ret = container_of_cpp(node, &CfdpChunkWrapper::cl_node);
738 }
739 }
740
741 154 return ret;
742 }
743
744 // ----------------------------------------------------------------------
745 // Private helper methods
746 // ----------------------------------------------------------------------
747
748 9288 void Channel::processPlaybackDirectory(Playback* pb) {
749 Transaction* txn;
750
1/1
✓ Branch 2 taken 9288 times.
9288 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 9298 times.
✓ Branch 3 taken 14 times.
✓ Branch 4 taken 9284 times.
✓ Branch 7 taken 14 times.
✗ Branch 8 not taken.
9298 while (pb->diropen && (pb->num_ts < NumTransactionsPerPlayback)) {
756
3/3
✓ Branch 6 taken 14 times.
✓ Branch 8 taken 9 times.
✓ Branch 9 taken 5 times.
14 if (pb->pending_file.length() == 0) {
757
1/1
✓ Branch 5 taken 9 times.
9 status = pb->dir.read(path);
758
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 6 times.
9 if (status == Os::Directory::NO_MORE_FILES) {
759 // Directory playback complete - success reported via TxFileTransferCompleted EVR
760
1/1
✓ Branch 5 taken 3 times.
3 pb->dir.close();
761 3 pb->diropen = false;
762 3 break;
763 }
764
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
6 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
1/1
✓ Branch 6 taken 5 times.
5 pb->pending_file = path;
774 } else {
775 // Skip entries whose combined "<dir>/<entry>" source or destination path
776 // would not fit the MaxFilePathSize limit enforced by the metadata PDU.
777 // Checked before a transaction is acquired so no slot is consumed for it.
778 5 const FwSizeType maxPathLen = static_cast<FwSizeType>(MaxFilePathSize);
779
2/2
✓ Branch 6 taken 5 times.
✓ Branch 14 taken 5 times.
5 const FwSizeType srcPathLen = pb->fnames.src_filename.length() + 1 + pb->pending_file.length();
780
2/2
✓ Branch 6 taken 5 times.
✓ Branch 14 taken 5 times.
5 const FwSizeType dstPathLen = pb->fnames.dst_filename.length() + 1 + pb->pending_file.length();
781 5 const bool srcTooLong = (srcPathLen > maxPathLen);
782 5 const bool dstTooLong = (dstPathLen > maxPathLen);
783
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
5 if (srcTooLong) {
784
1/1
✓ Branch 13 taken 1 times.
1 this->m_cfdpManager->log_WARNING_LO_PlaybackSourcePathTooLong(
785 srcPathLen, maxPathLen, pb->fnames.src_filename, pb->pending_file);
786 }
787
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
5 if (dstTooLong) {
788
1/1
✓ Branch 13 taken 1 times.
1 this->m_cfdpManager->log_WARNING_LO_PlaybackDestinationPathTooLong(
789 dstPathLen, maxPathLen, pb->fnames.dst_filename, pb->pending_file);
790 }
791
4/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 3 times.
5 if (srcTooLong || dstTooLong) {
792
1/1
✓ Branch 6 taken 2 times.
2 pb->pending_file = ""; // continue reading dir
793 2 continue;
794 }
795
796
1/1
✓ Branch 2 taken 3 times.
3 txn = this->findUnusedTransaction(Direction::DIRECTION_TX);
797
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (txn == nullptr) {
798 // while not expected this can certainly happen, because
799 // rx transactions consume in these as well.
800 // should not need to do anything special, will come back next tick
801 ✗ break;
802 }
803
804 // Append file name to source/destination folders
805
1/1
✓ Branch 10 taken 3 times.
3 txn->m_history->fnames.src_filename = pb->fnames.src_filename;
806
1/1
✓ Branch 7 taken 3 times.
3 txn->m_history->fnames.src_filename += "/";
807
1/1
✓ Branch 10 taken 3 times.
3 txn->m_history->fnames.src_filename += pb->pending_file;
808
809
1/1
✓ Branch 11 taken 3 times.
3 txn->m_history->fnames.dst_filename = pb->fnames.dst_filename;
810
1/1
✓ Branch 8 taken 3 times.
3 txn->m_history->fnames.dst_filename += "/";
811
1/1
✓ Branch 11 taken 3 times.
3 txn->m_history->fnames.dst_filename += pb->pending_file;
812
813
3/5
✗ Branch 11 not taken.
✓ Branch 12 taken 3 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 3 times.
✓ Branch 17 taken 3 times.
3 m_engine->txFileInitiate(txn, pb->cfdp_class, pb->keep, m_channelId, pb->priority, pb->dest_id);
814
815 3 txn->m_pb = pb;
816 3 ++pb->num_ts;
817
818
1/1
✓ Branch 6 taken 3 times.
3 pb->pending_file = ""; // continue reading dir
819 }
820 }
821
822
4/6
✗ Branch 1 not taken.
✓ Branch 2 taken 9288 times.
✓ Branch 3 taken 9288 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 9285 times.
✓ Branch 8 taken 3 times.
9288 if (!pb->diropen && !pb->num_ts) {
823 // the directory has been exhausted, and there are no more active transactions
824 // for this playback -- so mark it as not busy
825 9285 pb->busy = false;
826 }
827 18576 }
828
829 ✗ void Channel::updatePollPbCounted(Playback* pb, I32 up, U8* counter) {
830 ✗ if (pb->counted != up) {
831 // only handle on state change
832 ✗ pb->counted = !!up; // !! ensure 0 or 1, should be optimized out
833
834 ✗ if (up) {
835 ✗ ++*counter;
836 } else {
837 ✗ FW_ASSERT(*counter); // sanity check it isn't zero
838 ✗ --*counter;
839 }
840 }
841 ✗ }
842
843 17 CListTraverseStatus Channel::cycleTxFirstActive(CListNode* node, void* context) {
844 17 CycleTxArgs* args = static_cast<CycleTxArgs*>(context);
845 17 Transaction* txn = container_of_cpp(node, &Transaction::m_cl_node);
846 17 CListTraverseStatus ret = CLIST_TRAVERSE_EXIT; // default option is exit traversal
847
848
2/4
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 17 times.
17 if (txn->m_flags.com.suspended) {
849 ✗ ret = CLIST_TRAVERSE_CONTINUE; // suspended, so move on to next
850 } else {
851 17 FW_ASSERT(txn->m_flags.com.q_index == QueueId::TXA); // huh?
852
853 // if no more messages, then chan->m_currentTxn will be set.
854 // If the transaction sent the last filedata PDU and EOF, it will move itself
855 // off the active queue. Run until either of these occur.
856
3/4
✓ Branch 2 taken 84 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 67 times.
✓ Branch 7 taken 17 times.
84 while (!this->m_currentTxn && txn->m_flags.com.q_index == QueueId::TXA) {
857 67 m_engine->dispatchTx(txn);
858 }
859
860 17 args->ran_one = 1;
861 }
862
863 17 return ret;
864 }
865
866 233 CListTraverseStatus Channel::doTick(CListNode* node, void* context) {
867 233 CListTraverseStatus ret =
868 CLIST_TRAVERSE_CONTINUE; // CLIST_TRAVERSE_CONTINUE means don't tick one, keep looking for currentTxn
869 233 TickArgs* args = static_cast<TickArgs*>(context);
870 233 Transaction* txn = container_of_cpp(node, &Transaction::m_cl_node);
871
1/4
✗ Branch 2 not taken.
✓ Branch 3 taken 233 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
233 if (!this->m_currentTxn || (this->m_currentTxn == txn)) {
872 // found where we left off, so clear that and move on
873 233 this->m_currentTxn = nullptr;
874
2/4
✗ Branch 1 not taken.
✓ Branch 2 taken 233 times.
✓ Branch 3 taken 233 times.
✗ Branch 4 not taken.
233 if (!txn->m_flags.com.suspended) {
875
1/2
✗ Branch 6 not taken.
✓ Branch 7 taken 233 times.
233 (txn->*args->fn)(&args->cont);
876 }
877
878 // if this->m_currentTxn was set to not-nullptr above, then exit early
879 // NOTE: if channel is frozen, then tick processing won't have been entered.
880 // so there is no need to check it here
881
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 233 times.
233 if (this->m_currentTxn) {
882 ✗ ret = CLIST_TRAVERSE_EXIT;
883 ✗ args->early_exit = true;
884 }
885 }
886
887 233 return ret; // don't tick one, keep looking for currentTxn
888 }
889
890 33 Transaction* Channel::getTransaction(U32 index) {
891 33 FW_ASSERT(index < CFDP_NUM_TRANSACTIONS_PER_CHANNEL);
892 33 return &m_transactions[index];
893 }
894
895 33 History* Channel::getHistory(U32 index) {
896 33 FW_ASSERT(index < NumHistoriesPerChannel);
897 33 return &m_histories[index];
898 }
899
900 // ----------------------------------------------------------------------
901 // Static callback wrapper implementations
902 // ----------------------------------------------------------------------
903
904 17 CListTraverseStatus Channel::cycleTxFirstActiveWrapper(CListNode* node, void* context) {
905 struct CycleTxContext {
906 Channel* channel;
907 CycleTxArgs* args;
908 };
909 17 CycleTxContext* ctx = static_cast<CycleTxContext*>(context);
910 17 return ctx->channel->cycleTxFirstActive(node, ctx->args);
911 }
912
913 233 CListTraverseStatus Channel::doTickWrapper(CListNode* node, void* context) {
914 struct TickContext {
915 Channel* channel;
916 TickArgs* args;
917 };
918 233 TickContext* ctx = static_cast<TickContext*>(context);
919 233 return ctx->channel->doTick(node, ctx->args);
920 }
921
922 ✗ CListTraverseStatus Channel::traverseAllTransactionsWrapper(CListNode* node, void* context) {
923 struct TraverseAllContext {
924 CfdpTraverseAllTransactionsFunc fn;
925 void* userContext;
926 I32* counter;
927 };
928 ✗ TraverseAllContext* ctx = static_cast<TraverseAllContext*>(context);
929 ✗ Transaction* txn = container_of_cpp(node, &Transaction::m_cl_node);
930 ✗ ctx->fn(txn, ctx->userContext);
931 ✗ ++(*ctx->counter);
932 ✗ return CLIST_TRAVERSE_CONTINUE;
933 }
934
935 } // namespace Cfdp
936 } // namespace Ccsds
937 } // namespace Svc
938