GCC Code Coverage Report


Directory: ./
File: Svc/Ccsds/CfdpManager/TransactionRx.cpp
Date: 2026-10-14 21:15:02
Exec Total Coverage
Lines: 487 544 89.5%
Functions: 28 29 96.6%
Branches: 396 517 76.6%

Line Branch Exec Source
1 // ======================================================================
2 // \title TransactionRx.cpp
3 // \brief cpp file for CFDP RX Transaction state machine
4 //
5 // This file is a port of RX transaction state machine operations from the following files
6 // from the NASA Core Flight System (cFS) CFDP (CF) Application, version 3.0.0,
7 // adapted for use within the F-Prime (F') framework:
8 // - cf_cfdp_r.c (receive-file transaction state handling routines)
9 // - cf_cfdp_dispatch.c (RX state machine dispatch functions)
10 //
11 // This file contains various state handling routines for
12 // transactions which are receiving a file, as well as dispatch
13 // functions for RX state machines and top-level transaction dispatch.
14 //
15 // ======================================================================
16 //
17 // NASA Docket No. GSC-18,447-1
18 //
19 // Copyright (c) 2019 United States Government as represented by the
20 // Administrator of the National Aeronautics and Space Administration.
21 // All Rights Reserved.
22 //
23 // Licensed under the Apache License, Version 2.0 (the "License"); you may
24 // not use this file except in compliance with the License. You may obtain
25 // a copy of the License at
26 //
27 // http://www.apache.org/licenses/LICENSE-2.0
28 //
29 // Unless required by applicable law or agreed to in writing, software
30 // distributed under the License is distributed on an "AS IS" BASIS,
31 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
32 // See the License for the specific language governing permissions and
33 // limitations under the License.
34 //
35 // ======================================================================
36
37 #include <stdio.h>
38 #include <string.h>
39
40 #include <limits>
41
42 #include <Fw/Types/SuccessEnumAc.hpp>
43 #include <Os/FileSystem.hpp>
44
45 #include <Svc/Ccsds/CfdpManager/CfdpManager.hpp>
46 #include <Svc/Ccsds/CfdpManager/Channel.hpp>
47 #include <Svc/Ccsds/CfdpManager/Chunk.hpp>
48 #include <Svc/Ccsds/CfdpManager/Engine.hpp>
49 #include <Svc/Ccsds/CfdpManager/Transaction.hpp>
50 #include <Svc/Ccsds/CfdpManager/Utils.hpp>
51
52 namespace Svc {
53 namespace Ccsds {
54 namespace Cfdp {
55
56 // ======================================================================
57 // Construction and Destruction
58 // ======================================================================
59
60 14400 Transaction::Transaction(Channel* channel, U8 channelId, Engine* engine, CfdpManager* manager)
61 14400 : m_state(TxnState::TXN_STATE_UNDEF),
62 14400 m_txn_class(Cfdp::Class::CLASS_1),
63 14400 m_history(nullptr),
64 14400 m_chunks(nullptr),
65 14400 m_inactivity_timer(),
66
1/1
✓ Branch 3 taken 14400 times.
14400 m_ack_timer(),
67 14400 m_fsize(0),
68 14400 m_foffs(0),
69
1/1
✓ Branch 3 taken 14400 times.
14400 m_fd(),
70
1/1
✓ Branch 3 taken 14400 times.
14400 m_crc(),
71 14400 m_keep(Cfdp::Keep::KEEP),
72 14400 m_chan_num(channelId), // Initialize from parameter
73 14400 m_priority(0),
74 14400 m_initType(TransactionInitType::INIT_BY_COMMAND),
75 14400 m_cl_node{},
76 14400 m_pb(nullptr),
77 14400 m_state_data{},
78 14400 m_flags{},
79 14400 m_cfdpManager(manager), // Initialize from parameter
80 14400 m_chan(channel), // Initialize from parameter
81 14400 m_engine(engine) // Initialize from parameter
82 {
83 // Fully zero the union storage
84
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 14400 times.
14400 memset(&this->m_state_data, 0, sizeof(this->m_state_data));
85
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 14400 times.
14400 memset(&this->m_flags, 0, sizeof(this->m_flags));
86 14400 }
87
88 14400 Transaction::~Transaction() {}
89
90 14424 void Transaction::reset() {
91 // Reset transaction state to default values
92 14424 this->m_state = TxnState::TXN_STATE_UNDEF;
93 14424 this->m_txn_class = Cfdp::Class::CLASS_1;
94 14424 this->m_fsize = 0;
95 14424 this->m_foffs = 0;
96 14424 this->m_keep = Cfdp::Keep::KEEP;
97 14424 this->m_priority = 0;
98 14424 this->m_initType = TransactionInitType::INIT_BY_COMMAND;
99
2/2
✓ Branch 2 taken 14424 times.
✓ Branch 9 taken 14424 times.
14424 this->m_crc = CFDP::Checksum(0);
100 14424 this->m_pb = nullptr;
101
102 // Fully zero the union storage
103
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 14424 times.
14424 memset(&this->m_state_data, 0, sizeof(this->m_state_data));
104
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 14424 times.
14424 memset(&this->m_flags, 0, sizeof(this->m_flags));
105
106 // Close the file if it is open
107
1/2
✗ Branch 6 not taken.
✓ Branch 7 taken 14424 times.
14424 if (this->m_fd.isOpen()) {
108 ✗ this->m_fd.close();
109 }
110
111 // Disable timers to ensure clean state for next transaction
112 // This prevents stale timers from a previous transaction firing in a new context
113 14424 this->m_inactivity_timer.disableTimer();
114 14424 this->m_ack_timer.disableTimer();
115
116 // The following state information is PRESERVED across reset (NOT modified):
117 // - this->m_cfdpManager // Channel binding
118 // - this->m_chan // Channel binding
119 // - this->m_engine // Channel binding
120 // - this->m_chan_num // Channel binding
121 // - this->m_history // Assigned when transaction is activated
122 // - this->m_chunks // Assigned when transaction is activated
123 // - this->m_cl_node // Managed by queue operations in freeTransaction()
124 14424 }
125
126 // ======================================================================
127 // RX State Machine - Public Methods
128 // ======================================================================
129
130 15 void Transaction::r1Recv(const Fw::Buffer& buffer) {
131 static const FileDirectiveDispatchTable r1_fdir_handlers = {{
132 nullptr, /* CFDP_FileDirective_INVALID_MIN */
133 nullptr, /* 1 is unused in the CFDP_FileDirective_t enum */
134 nullptr, /* 2 is unused in the CFDP_FileDirective_t enum */
135 nullptr, /* 3 is unused in the CFDP_FileDirective_t enum */
136 &Transaction::r1SubstateRecvEof, /* CFDP_FileDirective_EOF */
137 nullptr, /* CFDP_FileDirective_FIN */
138 nullptr, /* CFDP_FileDirective_ACK */
139 nullptr, /* CFDP_FileDirective_METADATA */
140 nullptr, /* CFDP_FileDirective_NAK */
141 nullptr, /* CFDP_FileDirective_PROMPT */
142 nullptr, /* 10 is unused in the CFDP_FileDirective_t enum */
143 nullptr, /* 11 is unused in the CFDP_FileDirective_t enum */
144 nullptr, /* CFDP_FileDirective_KEEP_ALIVE */
145 }};
146
147 static const RSubstateDispatchTable substate_fns = {{
148 &r1_fdir_handlers, /* RxSubState::RX_SUB_STATE_FILEDATA */
149 &r1_fdir_handlers, /* RxSubState::RX_SUB_STATE_EOF */
150 &r1_fdir_handlers, /* RxSubState::RX_SUB_STATE_CLOSEOUT_SYNC */
151 }};
152
153 15 this->rDispatchRecv(buffer, &substate_fns, &Transaction::r1SubstateRecvFileData);
154 15 }
155
156 37 void Transaction::r2Recv(const Fw::Buffer& buffer) {
157 static const FileDirectiveDispatchTable r2_fdir_handlers_normal = {{
158 nullptr, /* CFDP_FileDirective_INVALID_MIN */
159 nullptr, /* 1 is unused in the CFDP_FileDirective_t enum */
160 nullptr, /* 2 is unused in the CFDP_FileDirective_t enum */
161 nullptr, /* 3 is unused in the CFDP_FileDirective_t enum */
162 &Transaction::r2SubstateRecvEof, /* CFDP_FileDirective_EOF */
163 nullptr, /* CFDP_FileDirective_FIN */
164 nullptr, /* CFDP_FileDirective_ACK */
165 &Transaction::r2RecvMd, /* CFDP_FileDirective_METADATA */
166 nullptr, /* CFDP_FileDirective_NAK */
167 nullptr, /* CFDP_FileDirective_PROMPT */
168 nullptr, /* 10 is unused in the CFDP_FileDirective_t enum */
169 nullptr, /* 11 is unused in the CFDP_FileDirective_t enum */
170 nullptr, /* CFDP_FileDirective_KEEP_ALIVE */
171 }};
172 static const FileDirectiveDispatchTable r2_fdir_handlers_finack = {{
173 nullptr, /* CFDP_FileDirective_INVALID_MIN */
174 nullptr, /* 1 is unused in the CFDP_FileDirective_t enum */
175 nullptr, /* 2 is unused in the CFDP_FileDirective_t enum */
176 nullptr, /* 3 is unused in the CFDP_FileDirective_t enum */
177 &Transaction::r2SubstateRecvEof, /* CFDP_FileDirective_EOF */
178 nullptr, /* CFDP_FileDirective_FIN */
179 &Transaction::r2RecvFinAck, /* CFDP_FileDirective_ACK */
180 nullptr, /* CFDP_FileDirective_METADATA */
181 nullptr, /* CFDP_FileDirective_NAK */
182 nullptr, /* CFDP_FileDirective_PROMPT */
183 nullptr, /* 10 is unused in the CFDP_FileDirective_t enum */
184 nullptr, /* 11 is unused in the CFDP_FileDirective_t enum */
185 nullptr, /* CFDP_FileDirective_KEEP_ALIVE */
186 }};
187
188 static const RSubstateDispatchTable substate_fns = {{
189 &r2_fdir_handlers_normal, /* RxSubState::RX_SUB_STATE_FILEDATA */
190 &r2_fdir_handlers_normal, /* RxSubState::RX_SUB_STATE_EOF */
191 &r2_fdir_handlers_finack, /* RxSubState::RX_SUB_STATE_CLOSEOUT_SYNC */
192 }};
193
194 37 this->rDispatchRecv(buffer, &substate_fns, &Transaction::r2SubstateRecvFileData);
195 37 }
196
197 72 void Transaction::rAckTimerTick() {
198 72 U8 ack_limit = 0;
199
200 /* note: the ack timer is only ever armed on class 2 */
201
4/6
✓ Branch 1 taken 34 times.
✓ Branch 2 taken 38 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 34 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 34 times.
72 if (this->m_state != TxnState::TXN_STATE_R2 || !this->m_flags.com.ack_timer_armed) {
202 /* nothing to do */
203 38 return;
204 }
205
206
2/2
✓ Branch 4 taken 27 times.
✓ Branch 5 taken 7 times.
34 if (this->m_ack_timer.getStatus() == Timer::Status::RUNNING) {
207 27 this->m_ack_timer.run();
208 } else {
209 /* ACK timer expired, so check for completion */
210
2/4
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7 times.
7 if (!this->m_flags.rx.complete) {
211 ✗ this->r2Complete(true);
212
1/2
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
7 } else if (this->m_state_data.receive.sub_state == RxSubState::RX_SUB_STATE_CLOSEOUT_SYNC) {
213 /* Increment acknak counter */
214 7 ++this->m_state_data.receive.r2.acknak_count;
215
216 /* Check limit and handle if needed */
217 7 ack_limit = this->m_cfdpManager->getAckLimitParam(this->m_chan_num);
218
2/2
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 6 times.
7 if (this->m_state_data.receive.r2.acknak_count >= ack_limit) {
219
2/2
✓ Branch 15 taken 1 times.
✓ Branch 19 taken 1 times.
2 this->m_cfdpManager->log_WARNING_LO_RxAckLimitReached(this->getClass(), this->m_history->src_eid,
220 1 this->m_history->seq_num);
221 1 this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_ACK_LIMIT_NO_FIN);
222 1 this->m_cfdpManager->incrementFaultAckLimit(this->m_chan_num);
223
224 /* give up on this */
225 1 this->m_engine->finishTransaction(this, true);
226 1 this->m_flags.com.ack_timer_armed = false;
227 } else {
228 6 this->m_flags.rx.send_fin = true;
229 }
230 }
231
232 /* re-arm the timer if it is still pending */
233
3/4
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 1 times.
7 if (this->m_flags.com.ack_timer_armed) {
234 /* whether sending FIN or waiting for more filedata, need ACK timer armed */
235 6 this->m_engine->armAckTimer(this);
236 }
237 }
238 }
239
240 82 void Transaction::rTick(I32* cont /* unused */) {
241 /* Steven is not real happy with this function. There should be a better way to separate out
242 * the logic by state so that it isn't a bunch of if statements for different flags
243 */
244
245 Status::T sret;
246 bool pending_send;
247
248
2/4
✗ Branch 1 not taken.
✓ Branch 2 taken 82 times.
✓ Branch 3 taken 82 times.
✗ Branch 4 not taken.
82 if (!this->m_flags.com.inactivity_fired) {
249
1/2
✓ Branch 4 taken 82 times.
✗ Branch 5 not taken.
82 if (this->m_inactivity_timer.getStatus() == Timer::Status::RUNNING) {
250 82 this->m_inactivity_timer.run();
251 // Check if timer just expired naturally (after run())
252
2/2
✓ Branch 4 taken 11 times.
✓ Branch 5 taken 71 times.
82 if (this->m_inactivity_timer.getStatus() == Timer::Status::EXPIRED) {
253 11 this->m_flags.com.inactivity_fired = true;
254
255 /* HOLD state is the normal path to recycle transaction objects, not an error */
256 /* Canceled transactions timing out is also normal */
257 /* inactivity is abnormal in any other state */
258
1/6
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
11 if (this->m_state != TxnState::TXN_STATE_HOLD && !this->m_flags.com.canceled) {
259 ✗ this->rSendInactivityEvent();
260
261 /* in class 2 this also triggers sending an early FIN response */
262 ✗ if (this->m_state == TxnState::TXN_STATE_R2) {
263 ✗ this->r2SetFinTxnStatus(TxnStatus::TXN_STATUS_INACTIVITY_DETECTED);
264 }
265 }
266 }
267 }
268 }
269
270 82 pending_send = true; /* maybe; tbd */
271
272 /* rx maintenance: possibly process send_eof_ack, send_nak or send_fin */
273
3/4
✗ Branch 1 not taken.
✓ Branch 2 taken 82 times.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 76 times.
82 if (this->m_flags.rx.send_eof_ack) {
274 18 sret = this->m_engine->sendAck(this, AckTxnStatus::ACK_TXN_STATUS_ACTIVE,
275 FileDirective::FILE_DIRECTIVE_END_OF_FILE,
276 6 static_cast<ConditionCode>(this->m_state_data.receive.r2.eof_cc),
277 12 this->m_history->peer_eid, this->m_history->seq_num);
278
279 /* if SUCCESS, move on. If NO_BUF_AVAIL, retry later. If ERROR, stop retrying. */
280
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (sret == Cfdp::Status::SUCCESS) {
281 6 this->m_flags.rx.send_eof_ack = false;
282 ✗ } else if (sret == Cfdp::Status::ERROR) {
283 /* Serialization failed - error already logged in serializeAndSendPdu */
284 /* Clear flag to avoid infinite retry loop */
285 ✗ this->m_flags.rx.send_eof_ack = false;
286 ✗ pending_send = false;
287 }
288 /* else NO_BUF_AVAIL: leave flag set to retry next tick */
289
3/4
✗ Branch 1 not taken.
✓ Branch 2 taken 76 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 75 times.
76 } else if (this->m_flags.rx.send_nak) {
290
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 if (!this->rSubstateSendNak()) {
291 1 this->m_flags.rx.send_nak = false; /* will re-enter on error */
292 }
293
3/4
✗ Branch 1 not taken.
✓ Branch 2 taken 75 times.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 62 times.
75 } else if (this->m_flags.rx.send_fin) {
294
2/2
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 1 times.
13 if (!this->r2SubstateSendFin()) {
295 12 this->m_flags.rx.send_fin = false; /* will re-enter on error */
296 }
297 } else {
298 /* no pending responses to the sender */
299 62 pending_send = false;
300 }
301
302 /* if the inactivity timer ran out, then there is no sense
303 * pending for responses for anything. Send out anything
304 * that we need to send (i.e. the FIN) just in case the sender
305 * is still listening to us but do not expect any future ACKs.
306 *
307 * Bound the deferral like the TX path: after a small retry budget, recycle regardless. */
308 82 bool retries_exhausted = false;
309
4/6
✗ Branch 1 not taken.
✓ Branch 2 taken 82 times.
✓ Branch 3 taken 11 times.
✓ Branch 4 taken 71 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 11 times.
82 if (this->m_flags.com.inactivity_fired && pending_send) {
310 ✗ if (this->m_flags.com.post_inactivity_send_retries >=
311 ✗ this->m_cfdpManager->getPostInactivitySendRetriesParam()) {
312 ✗ retries_exhausted = true;
313 } else {
314 ✗ this->m_flags.com.post_inactivity_send_retries++;
315 }
316 }
317
4/8
✗ Branch 1 not taken.
✓ Branch 2 taken 82 times.
✓ Branch 3 taken 11 times.
✓ Branch 4 taken 71 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 11 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
82 if (this->m_flags.com.inactivity_fired && (!pending_send || retries_exhausted)) {
318 /* the transaction is now recyclable - this means we will
319 * no longer have a record of this transaction seq. If the sender
320 * wakes up or if the network delivers severely delayed PDUs at
321 * some future point, then they will be seen as spurious. They
322 * will no longer be associable with this transaction at all */
323 11 this->m_chan->recycleTransaction(this);
324
325 /* NOTE: this must be the last thing in here. Do not use txn after this */
326 } else {
327 /* transaction still valid so process the ACK timer, if relevant */
328 71 this->rAckTimerTick();
329 }
330 82 }
331
332 ✗ void Transaction::rCancel() {
333 /* for cancel, only need to send FIN if R2 */
334 ✗ if ((this->m_state == TxnState::TXN_STATE_R2) &&
335 ✗ (this->m_state_data.receive.sub_state < RxSubState::RX_SUB_STATE_CLOSEOUT_SYNC)) {
336 ✗ this->m_flags.rx.send_fin = true;
337 } else {
338 ✗ this->r1Reset(); /* if R1, just call it quits */
339 }
340 ✗ }
341
342 80 void Transaction::rInit() {
343 Os::File::Status status;
344
1/1
✓ Branch 2 taken 80 times.
80 Fw::String tmpDir;
345
1/1
✓ Branch 2 taken 80 times.
80 Fw::String dst;
346
347
2/2
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 66 times.
80 if (this->m_state == TxnState::TXN_STATE_R2) {
348
3/4
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 11 times.
14 if (!this->m_flags.rx.md_recv) {
349
2/2
✓ Branch 6 taken 3 times.
✓ Branch 11 taken 3 times.
3 tmpDir = this->m_cfdpManager->getTmpDirParam(this->m_chan_num);
350 /* we need to make a temp file and then do a NAK for md PDU */
351 /* the transaction already has a history, and that has a buffer that we can use to
352 * hold the temp filename which is defined by the sequence number and the source entity ID */
353
354 // Create destination filepath with format: <tmpDir>/<src_eid>:<seq_num>.tmp
355
1/1
✓ Branch 6 taken 3 times.
9 dst.format("%s/%" CFDP_PRI_ENTITY_ID ":%" CFDP_PRI_TRANSACTION_SEQ ".tmp", tmpDir.toChar(),
356 6 this->m_history->src_eid, this->m_history->seq_num);
357
358
1/1
✓ Branch 8 taken 3 times.
3 this->m_history->fnames.dst_filename = dst;
359
360
2/2
✓ Branch 17 taken 3 times.
✓ Branch 21 taken 3 times.
9 this->m_cfdpManager->log_ACTIVITY_LO_RxTempFileCreated(this->getClass(), this->m_history->src_eid,
361 3 this->m_history->seq_num,
362 3 this->m_history->fnames.dst_filename);
363 }
364
365
1/1
✓ Branch 6 taken 14 times.
14 this->m_engine->armAckTimer(this);
366 }
367
368
1/1
✓ Branch 14 taken 80 times.
80 status = this->m_fd.open(this->m_history->fnames.dst_filename.toChar(), Os::File::OPEN_CREATE, Os::File::OVERWRITE);
369
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 79 times.
80 if (status != Os::File::OP_OK) {
370
2/2
✓ Branch 17 taken 1 times.
✓ Branch 21 taken 1 times.
3 this->m_cfdpManager->log_WARNING_LO_RxFileCreateFailed(this->getClass(), this->m_history->src_eid,
371 1 this->m_history->seq_num,
372 1 this->m_history->fnames.dst_filename, status);
373
1/1
✓ Branch 8 taken 1 times.
1 this->m_cfdpManager->incrementFaultFileOpen(this->m_chan_num);
374
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if (this->m_state == TxnState::TXN_STATE_R2) {
375 ✗ this->r2SetFinTxnStatus(TxnStatus::TXN_STATUS_FILESTORE_REJECTION);
376 } else {
377
1/1
✓ Branch 2 taken 1 times.
1 this->r1Reset();
378 }
379 } else {
380 79 this->m_state_data.receive.sub_state = RxSubState::RX_SUB_STATE_FILEDATA;
381 }
382 160 }
383
384 11 void Transaction::r2SetFinTxnStatus(TxnStatus txn_stat) {
385 11 this->m_engine->setTxnStatus(this, txn_stat);
386 11 this->m_flags.rx.send_fin = true;
387 11 }
388
389 18 void Transaction::r1Reset() {
390 18 this->m_engine->finishTransaction(this, true);
391 18 }
392
393 6 void Transaction::r2Reset() {
394 6 if ((this->m_state_data.receive.sub_state == RxSubState::RX_SUB_STATE_CLOSEOUT_SYNC) ||
395
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 (static_cast<U8>(this->m_state_data.receive.r2.eof_cc) !=
396 2 static_cast<U8>(ConditionCode::CONDITION_CODE_NO_ERROR)) ||
397
4/10
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✓ Branch 14 taken 6 times.
✗ Branch 15 not taken.
8 TxnStatusIsError(this->m_history->txn_stat) || this->m_flags.com.canceled) {
398 6 this->r1Reset(); /* it's done */
399 } else {
400 /* not waiting for FIN ACK, so trigger send FIN */
401 ✗ this->m_flags.rx.send_fin = true;
402 }
403 6 }
404
405 9 Status::T Transaction::rCheckCrc(U32 expected_crc) {
406 9 Status::T ret = Cfdp::Status::SUCCESS;
407 U32 crc_result;
408
409 // The F' version does not have an equivalent finalize call as it
410 // - Never stores a partial word internally
411 // - Never needs to "flush" anything
412 // - Always accounts for padding at update time
413 9 crc_result = this->m_crc.getValue();
414
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 8 times.
9 if (crc_result != expected_crc) {
415
2/2
✓ Branch 15 taken 1 times.
✓ Branch 19 taken 1 times.
2 this->m_cfdpManager->log_WARNING_LO_RxCrcMismatch(this->getClass(), this->m_history->src_eid,
416 1 this->m_history->seq_num, expected_crc, crc_result);
417 1 this->m_cfdpManager->incrementFaultCrcMismatch(this->m_chan_num);
418 1 ret = Cfdp::Status::ERROR;
419 }
420
421 9 return ret;
422 }
423
424 10 void Transaction::r2Complete(I32 ok_to_send_nak) {
425 U32 ret;
426 10 bool send_nak = false;
427 10 bool send_fin = false;
428 10 U8 nack_limit = 0;
429 /* checking if r2 is complete. Check NAK list, and send NAK if appropriate */
430 /* if all data is present, then there will be no gaps in the chunk */
431
432
1/2
✓ Branch 5 taken 10 times.
✗ Branch 6 not taken.
10 if (!TxnStatusIsError(this->m_history->txn_stat)) {
433 /* first, check if md is received. If not, send specialized NAK */
434
2/4
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
10 if (!this->m_flags.rx.md_recv) {
435 ✗ send_nak = true;
436 } else {
437 /* only look for 1 gap, since the goal here is just to know that there are gaps */
438 10 ret = this->m_chunks->chunks.computeGaps(1, this->m_fsize, 0, nullptr, nullptr);
439
440
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 6 times.
10 if (ret) {
441 /* there is at least 1 gap, so send a NAK */
442 4 send_nak = true;
443
2/4
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
6 } else if (this->m_flags.rx.eof_recv) {
444 /* the EOF was received, and there are no NAKs -- process completion in send FIN state */
445 6 send_fin = true;
446 }
447 }
448
449
4/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
10 if (send_nak && ok_to_send_nak) {
450 /* Increment the acknak counter */
451 2 ++this->m_state_data.receive.r2.acknak_count;
452
453 /* Check limit and handle if needed */
454 2 nack_limit = this->m_cfdpManager->getNackLimitParam(this->m_chan_num);
455
2/2
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
2 if (this->m_state_data.receive.r2.acknak_count >= nack_limit) {
456
2/2
✓ Branch 15 taken 1 times.
✓ Branch 19 taken 1 times.
2 this->m_cfdpManager->log_WARNING_LO_RxNakLimitReached(this->getClass(), this->m_history->src_eid,
457 1 this->m_history->seq_num);
458 1 send_fin = true;
459 1 this->m_cfdpManager->incrementFaultNakLimit(this->m_chan_num);
460 /* don't use CFDP_R2_SetFinTxnStatus because many places in this function set send_fin */
461 1 this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_NAK_LIMIT_REACHED);
462 1 this->m_state_data.receive.r2.acknak_count = 0; /* reset for fin/ack */
463 } else {
464 1 this->m_flags.rx.send_nak = true;
465 }
466 }
467
468
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 3 times.
10 if (send_fin) {
469 7 this->m_flags.rx.complete = true; /* latch completeness, since send_fin is cleared later */
470
471 /* the transaction is now considered complete, but this will not overwrite an
472 * error status code if there was one set */
473 7 this->r2SetFinTxnStatus(TxnStatus::TXN_STATUS_NO_ERROR);
474 }
475
476 /* always go to RxSubState::RX_SUB_STATE_FILEDATA, and let tick change state */
477 10 this->m_state_data.receive.sub_state = RxSubState::RX_SUB_STATE_FILEDATA;
478 }
479 10 }
480
481 // ======================================================================
482 // RX State Machine - Private Helper Methods
483 // ======================================================================
484
485 33 Status::T Transaction::rProcessFd(const Fw::Buffer& buffer) {
486 33 Status::T ret = Cfdp::Status::SUCCESS;
487
488 /* this function is only entered for data PDUs */
489 // Deserialize FileData PDU from buffer
490
1/1
✓ Branch 2 taken 33 times.
33 FileDataPdu fd;
491 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
492
3/3
✓ Branch 5 taken 33 times.
✓ Branch 11 taken 33 times.
✓ Branch 14 taken 33 times.
33 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
493
2/2
✓ Branch 5 taken 33 times.
✓ Branch 8 taken 33 times.
33 sb.setBuffLen(buffer.getSize());
494
495
1/1
✓ Branch 2 taken 33 times.
33 Fw::SerializeStatus deserStatus = fd.deserializeFrom(sb);
496
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 33 times.
33 if (deserStatus != Fw::FW_SERIALIZE_OK) {
497 ✗ this->m_cfdpManager->log_WARNING_LO_FailFileDataPduDeserialization(this->getChannelId(),
498 static_cast<I32>(deserStatus));
499 ✗ ret = Cfdp::Status::ERROR;
500 }
501
502 /*
503 * NOTE: The decode routine should have left a direct pointer to the data and actual data length
504 * within the PDU. The length has already been verified, too. Should not need to make any
505 * adjustments here, just write it.
506 */
507
508 33 FileSize offset = fd.getOffset();
509 33 U16 dataSize = fd.getDataSize();
510 33 const U8* dataPtr = fd.getData();
511
512 // Reject file data past the declared file size, or past the addressable offset space when no
513 // metadata has been received yet (subtraction form avoids offset + dataSize overflow).
514
1/2
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
33 if (ret == Cfdp::Status::SUCCESS) {
515
3/4
✗ Branch 1 not taken.
✓ Branch 2 taken 33 times.
✓ Branch 3 taken 30 times.
✓ Branch 4 taken 3 times.
33 const FileSize bound = this->m_flags.rx.md_recv ? this->m_fsize : std::numeric_limits<FileSize>::max();
516
3/4
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 31 times.
33 if ((offset > bound) || ((bound - offset) < dataSize)) {
517
2/2
✓ Branch 11 taken 2 times.
✓ Branch 15 taken 2 times.
8 this->m_cfdpManager->log_WARNING_LO_RxFileDataOutOfBounds(
518 6 this->getClass(), this->m_history->src_eid, this->m_history->seq_num, offset, dataSize, bound);
519
1/1
✓ Branch 6 taken 2 times.
2 this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_FILE_SIZE_ERROR);
520
1/1
✓ Branch 8 taken 2 times.
2 this->m_cfdpManager->incrementFaultFileSizeMismatch(this->m_chan_num);
521 2 ret = Cfdp::Status::ERROR;
522 }
523 }
524
525 // A zero-length FileData segment is syntactically valid (payload length equals the encoded
526 // offset length) but carries no bytes. There is nothing to seek to, write, or track as a gap,
527 // so treat it as a successful no-op. This avoids a pointless file I/O round trip and prevents
528 // a zero-size interval from ever reaching the chunk tracker.
529
4/4
✓ Branch 0 taken 31 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 30 times.
33 if ((ret == Cfdp::Status::SUCCESS) && (dataSize == 0)) {
530 1 return ret;
531 }
532
533 // Seek to file offset if needed
534
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 2 times.
32 if (ret == Cfdp::Status::SUCCESS) {
535
2/2
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 26 times.
30 if (this->m_state_data.receive.cached_pos != offset) {
536
1/1
✓ Branch 6 taken 4 times.
4 Os::File::Status status = this->m_fd.seek(offset, Os::File::SeekType::ABSOLUTE);
537
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
4 if (status != Os::File::OP_OK) {
538
2/2
✓ Branch 15 taken 1 times.
✓ Branch 19 taken 1 times.
2 this->m_cfdpManager->log_WARNING_LO_RxSeekFailed(this->getClass(), this->m_history->src_eid,
539 1 this->m_history->seq_num, offset, status);
540
1/1
✓ Branch 6 taken 1 times.
1 this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_FILE_SIZE_ERROR);
541
1/1
✓ Branch 8 taken 1 times.
1 this->m_cfdpManager->incrementFaultFileSeek(this->m_chan_num);
542 1 ret = Cfdp::Status::ERROR;
543 }
544 }
545 }
546
547 // Write file data
548
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 3 times.
32 if (ret == Cfdp::Status::SUCCESS) {
549 29 FwSizeType write_size = dataSize;
550
1/1
✓ Branch 6 taken 29 times.
29 Os::File::Status status = this->m_fd.write(dataPtr, write_size, Os::File::WaitType::WAIT);
551
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 28 times.
29 if (status != Os::File::OP_OK) {
552
2/2
✓ Branch 15 taken 1 times.
✓ Branch 19 taken 1 times.
2 this->m_cfdpManager->log_WARNING_LO_RxWriteFailed(this->getClass(), this->m_history->src_eid,
553 1 this->m_history->seq_num, dataSize,
554 static_cast<I32>(write_size));
555
1/1
✓ Branch 6 taken 1 times.
1 this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_FILESTORE_REJECTION);
556
1/1
✓ Branch 8 taken 1 times.
1 this->m_cfdpManager->incrementFaultFileWrite(this->m_chan_num);
557 1 ret = Cfdp::Status::ERROR;
558 } else {
559 28 this->m_state_data.receive.cached_pos = static_cast<FileSize>(dataSize) + offset;
560
1/1
✓ Branch 8 taken 28 times.
28 this->m_cfdpManager->addRecvFileDataBytes(this->m_chan_num, dataSize);
561 }
562 }
563
564 32 return ret;
565 33 }
566
567 12 Status::T Transaction::rSubstateRecvEof(const Fw::Buffer& buffer) {
568 12 Status::T ret = Cfdp::Status::SUCCESS;
569
570 // Deserialize EOF PDU from buffer
571
1/1
✓ Branch 2 taken 12 times.
12 EofPdu eof;
572 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
573
3/3
✓ Branch 5 taken 12 times.
✓ Branch 11 taken 12 times.
✓ Branch 14 taken 12 times.
12 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
574
2/2
✓ Branch 5 taken 12 times.
✓ Branch 8 taken 12 times.
12 sb.setBuffLen(buffer.getSize());
575
576
1/1
✓ Branch 2 taken 12 times.
12 Fw::SerializeStatus deserStatus = eof.deserializeFrom(sb);
577
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (deserStatus != Fw::FW_SERIALIZE_OK) {
578 ✗ this->m_cfdpManager->log_WARNING_LO_FailEofPduDeserialization(this->getChannelId(),
579 static_cast<I32>(deserStatus));
580 ✗ ret = Cfdp::Status::REC_PDU_BAD_EOF_ERROR;
581 }
582
583
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 if (ret == Cfdp::Status::SUCCESS) {
584 // NOTE: Engine::recvEof() currently always returns SUCCESS, so the failure branch is
585 // omitted here. Once recvEof() performs real EOF-level validation (see the TLV
586 // "Future enhancement" TODO in Engine::recvEof) and can return an error status, add an
587 // else branch that emits log_WARNING_LO_RxInvalidEofPdu, increments recvErrors, and sets
588 // Cfdp::Status::REC_PDU_BAD_EOF_ERROR for the failure case.
589
2/3
✓ Branch 6 taken 12 times.
✓ Branch 8 taken 12 times.
✗ Branch 9 not taken.
12 if (!this->m_engine->recvEof(this, eof)) {
590 /* this function is only entered for PDUs identified as EOF type */
591 12 ConditionCode cc = eof.getConditionCode();
592
593 /* Only check size if MD received and EOF doesn't have a non-zero condition code (e.g., don't check size for
594 * canceled transactions) */
595
6/8
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 12 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 10 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 1 times.
✓ Branch 8 taken 11 times.
22 if (this->m_flags.rx.md_recv && (cc == ConditionCode::CONDITION_CODE_NO_ERROR) &&
596
2/2
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 9 times.
10 (eof.getFileSize() != this->m_fsize)) {
597
2/2
✓ Branch 18 taken 1 times.
✓ Branch 22 taken 1 times.
3 this->m_cfdpManager->log_WARNING_LO_RxFileSizeMismatch(this->getClass(), this->m_history->src_eid,
598 1 this->m_history->seq_num, this->m_fsize,
599 1 eof.getFileSize());
600
1/1
✓ Branch 8 taken 1 times.
1 this->m_cfdpManager->incrementFaultFileSizeMismatch(this->m_chan_num);
601 1 ret = Cfdp::Status::REC_PDU_FSIZE_MISMATCH_ERROR;
602 }
603
604 /* Log condition code if non-zero (cancel or error) - applies to both Class 1 and Class 2 */
605
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 10 times.
12 if (cc != ConditionCode::CONDITION_CODE_NO_ERROR) {
606 /* Set transaction status from condition code to prevent completion event */
607
1/1
✓ Branch 6 taken 2 times.
2 this->m_engine->setTxnStatus(this, static_cast<TxnStatus>(static_cast<I32>(cc)));
608
609
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (cc == ConditionCode::CONDITION_CODE_CANCEL_REQUEST_RECEIVED) {
610 /* Increment receive EOF cancellation counter (normal operation) */
611
1/1
✓ Branch 8 taken 1 times.
1 this->m_cfdpManager->incrementRecvEofCanceled(this->m_chan_num);
612
613
2/2
✓ Branch 15 taken 1 times.
✓ Branch 19 taken 1 times.
2 this->m_cfdpManager->log_ACTIVITY_HI_RxEofCancelReceived(this->getClass(), this->m_history->src_eid,
614 1 this->m_history->seq_num);
615 } else {
616 /* Increment RX EOF error counter (protocol error) */
617
1/1
✓ Branch 8 taken 1 times.
1 this->m_cfdpManager->incrementFaultRxEofError(this->m_chan_num);
618
619
2/2
✓ Branch 15 taken 1 times.
✓ Branch 19 taken 1 times.
2 this->m_cfdpManager->log_WARNING_LO_RxEofWithError(this->getClass(), this->m_history->src_eid,
620 1 this->m_history->seq_num, static_cast<U8>(cc));
621 }
622 }
623 }
624 }
625
626 12 return ret;
627 12 }
628
629 7 void Transaction::r1SubstateRecvEof(const Fw::Buffer& buffer) {
630 // Deserialize EOF PDU from buffer
631
1/1
✓ Branch 2 taken 7 times.
7 EofPdu eof;
632 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
633
3/3
✓ Branch 5 taken 7 times.
✓ Branch 11 taken 7 times.
✓ Branch 14 taken 7 times.
7 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
634
2/2
✓ Branch 5 taken 7 times.
✓ Branch 8 taken 7 times.
7 sb.setBuffLen(buffer.getSize());
635
636
1/1
✓ Branch 2 taken 7 times.
7 Fw::SerializeStatus deserStatus = eof.deserializeFrom(sb);
637
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 6 times.
7 if (deserStatus != Fw::FW_SERIALIZE_OK) {
638 // Bad EOF, reset transaction
639
1/1
✓ Branch 9 taken 1 times.
1 this->m_cfdpManager->log_WARNING_LO_FailEofPduDeserialization(this->getChannelId(),
640 static_cast<I32>(deserStatus));
641
1/1
✓ Branch 2 taken 1 times.
1 this->r1Reset();
642 1 return;
643 }
644
645
1/1
✓ Branch 2 taken 6 times.
6 Status::T ret = this->rSubstateRecvEof(buffer);
646 6 U32 crc = eof.getChecksum();
647 6 ConditionCode cc = eof.getConditionCode();
648
649
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
6 if (ret == Cfdp::Status::SUCCESS) {
650 /* Only check CRC if no error condition code */
651
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5 if (cc == ConditionCode::CONDITION_CODE_NO_ERROR) {
652 /* Verify CRC */
653
2/3
✓ Branch 2 taken 3 times.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
3 if (this->rCheckCrc(crc) == Cfdp::Status::SUCCESS) {
654 /* successfully processed the file */
655 3 this->m_keep = Cfdp::Keep::KEEP; /* save the file */
656 }
657 /* if file failed to process, there's nothing to do. CFDP_R_CheckCrc() generates an event on failure */
658 }
659 }
660
661 /* after exit, always reset since we are done */
662 /* reset even if the EOF failed -- class 1, so it won't come again! */
663
1/1
✓ Branch 2 taken 6 times.
6 this->r1Reset();
664 8 }
665
666 6 void Transaction::r2SubstateRecvEof(const Fw::Buffer& buffer) {
667 Status::T ret;
668
669
2/4
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
6 if (!this->m_flags.rx.eof_recv) {
670 // Deserialize EOF PDU from buffer
671
1/1
✓ Branch 2 taken 6 times.
6 EofPdu eof;
672 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
673
3/3
✓ Branch 5 taken 6 times.
✓ Branch 11 taken 6 times.
✓ Branch 14 taken 6 times.
6 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
674
2/2
✓ Branch 5 taken 6 times.
✓ Branch 8 taken 6 times.
6 sb.setBuffLen(buffer.getSize());
675
676
1/1
✓ Branch 2 taken 6 times.
6 Fw::SerializeStatus deserStatus = eof.deserializeFrom(sb);
677
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (deserStatus != Fw::FW_SERIALIZE_OK) {
678 // Bad EOF, return to FILEDATA substate
679 ✗ this->m_cfdpManager->log_WARNING_LO_FailEofPduDeserialization(this->getChannelId(),
680 static_cast<I32>(deserStatus));
681 ✗ this->m_state_data.receive.sub_state = RxSubState::RX_SUB_STATE_FILEDATA;
682 ✗ return;
683 }
684
685
1/1
✓ Branch 2 taken 6 times.
6 ret = this->rSubstateRecvEof(buffer);
686
687 /* did receiving EOF succeed? */
688
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (ret == Cfdp::Status::SUCCESS) {
689 6 this->m_flags.rx.eof_recv = true;
690
691 /* need to remember the EOF CRC for later */
692 6 this->m_state_data.receive.r2.eof_crc = eof.getChecksum();
693 6 this->m_state_data.receive.r2.eof_size = eof.getFileSize();
694
695 /* always ACK the EOF, even if we're not done */
696 6 this->m_state_data.receive.r2.eof_cc = static_cast<U8>(eof.getConditionCode());
697 6 this->m_flags.rx.send_eof_ack = true; /* defer sending ACK to tick handling */
698
699 /* only check for complete if EOF with no errors */
700
1/2
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 if (static_cast<U8>(this->m_state_data.receive.r2.eof_cc) ==
701 static_cast<U8>(ConditionCode::CONDITION_CODE_NO_ERROR)) {
702
1/1
✓ Branch 2 taken 6 times.
6 this->r2Complete(true); /* CFDP_R2_Complete() will change state */
703 } else {
704 /* All CFDP CC values directly correspond to a Transaction Status of the same numeric value */
705 ✗ this->m_engine->setTxnStatus(
706 ✗ this, static_cast<TxnStatus>(static_cast<I32>(this->m_state_data.receive.r2.eof_cc)));
707 ✗ this->r2Reset();
708 }
709 } else {
710 /* bad EOF sent? */
711 ✗ if (ret == Cfdp::Status::REC_PDU_FSIZE_MISMATCH_ERROR) {
712 ✗ this->r2SetFinTxnStatus(TxnStatus::TXN_STATUS_FILE_SIZE_ERROR);
713 } else {
714 /* can't do anything with this bad EOF, so return to FILEDATA */
715 ✗ this->m_state_data.receive.sub_state = RxSubState::RX_SUB_STATE_FILEDATA;
716 }
717 }
718 6 }
719 }
720
721 8 void Transaction::r1SubstateRecvFileData(const Fw::Buffer& buffer) {
722 Status::T ret;
723
724 // Deserialize FileData PDU from buffer
725
1/1
✓ Branch 2 taken 8 times.
8 FileDataPdu fd;
726 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
727
3/3
✓ Branch 5 taken 8 times.
✓ Branch 11 taken 8 times.
✓ Branch 14 taken 8 times.
8 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
728
2/2
✓ Branch 5 taken 8 times.
✓ Branch 8 taken 8 times.
8 sb.setBuffLen(buffer.getSize());
729
730
1/1
✓ Branch 2 taken 8 times.
8 Fw::SerializeStatus deserStatus = fd.deserializeFrom(sb);
731
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
8 if (deserStatus != Fw::FW_SERIALIZE_OK) {
732 // Bad file data PDU, reset transaction
733
1/1
✓ Branch 9 taken 1 times.
1 this->m_cfdpManager->log_WARNING_LO_FailFileDataPduDeserialization(this->getChannelId(),
734 static_cast<I32>(deserStatus));
735
1/1
✓ Branch 2 taken 1 times.
1 this->r1Reset();
736 1 return;
737 }
738
739 /* got file data PDU? */
740
1/1
✓ Branch 6 taken 7 times.
7 ret = this->m_engine->recvFd(this, fd);
741
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 if (ret == Cfdp::Status::SUCCESS) {
742
1/1
✓ Branch 2 taken 7 times.
7 ret = this->rProcessFd(buffer);
743 }
744
745
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3 times.
7 if (ret == Cfdp::Status::SUCCESS) {
746 /* class 1 digests CRC */
747
1/1
✓ Branch 10 taken 4 times.
4 this->m_crc.update(fd.getData(), fd.getOffset(), static_cast<U32>(fd.getDataSize()));
748 } else {
749 /* Reset transaction on failure */
750
1/1
✓ Branch 2 taken 3 times.
3 this->r1Reset();
751 }
752 9 }
753
754 27 void Transaction::r2SubstateRecvFileData(const Fw::Buffer& buffer) {
755 Status::T ret;
756
757 // If CRC calculation has started (file reopened in READ mode), ignore late FileData PDUs.
758 // This can happen if retransmitted FileData arrives after EOF was received and CRC began.
759
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 27 times.
27 if (this->m_state_data.receive.r2.rx_crc_calc_bytes > 0) {
760 // Silently ignore - file is complete and we're calculating CRC
761 // No EVR needed - late retransmissions are expected in CFDP Class 2
762 ✗ return;
763 }
764
765 // Deserialize FileData PDU from buffer
766
1/1
✓ Branch 2 taken 27 times.
27 FileDataPdu fd;
767 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
768
3/3
✓ Branch 5 taken 27 times.
✓ Branch 11 taken 27 times.
✓ Branch 14 taken 27 times.
27 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
769
2/2
✓ Branch 5 taken 27 times.
✓ Branch 8 taken 27 times.
27 sb.setBuffLen(buffer.getSize());
770
771
1/1
✓ Branch 2 taken 27 times.
27 Fw::SerializeStatus deserStatus = fd.deserializeFrom(sb);
772
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 26 times.
27 if (deserStatus != Fw::FW_SERIALIZE_OK) {
773 // Bad file data PDU, reset transaction
774
1/1
✓ Branch 9 taken 1 times.
1 this->m_cfdpManager->log_WARNING_LO_FailFileDataPduDeserialization(this->getChannelId(),
775 static_cast<I32>(deserStatus));
776
1/1
✓ Branch 6 taken 1 times.
1 this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_PROTOCOL_ERROR);
777
1/1
✓ Branch 2 taken 1 times.
1 this->r2Reset();
778 1 return;
779 }
780
781 /* got file data PDU? */
782
1/1
✓ Branch 6 taken 26 times.
26 ret = this->m_engine->recvFd(this, fd);
783
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 if (ret == Cfdp::Status::SUCCESS) {
784
1/1
✓ Branch 2 taken 26 times.
26 ret = this->rProcessFd(buffer);
785 }
786
787
2/2
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 1 times.
26 if (ret == Cfdp::Status::SUCCESS) {
788 /* class 2 does CRC at FIN, but track gaps */
789
1/1
✓ Branch 9 taken 25 times.
25 this->m_chunks->chunks.add(fd.getOffset(), static_cast<FileSize>(fd.getDataSize()));
790
791
3/4
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 22 times.
25 if (this->m_flags.rx.fd_nak_sent) {
792
1/1
✓ Branch 2 taken 3 times.
3 this->r2Complete(false); /* once nak-retransmit received, start checking for completion at each fd */
793 }
794
795
3/4
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✓ Branch 3 taken 24 times.
✓ Branch 4 taken 1 times.
25 if (!this->m_flags.rx.complete) {
796
1/1
✓ Branch 6 taken 24 times.
24 this->m_engine->armAckTimer(this); /* re-arm ACK timer, since we got data */
797 }
798
799 25 this->m_state_data.receive.r2.acknak_count = 0;
800 } else {
801 /* Reset transaction on failure */
802
1/1
✓ Branch 2 taken 1 times.
1 this->r2Reset();
803 }
804 28 }
805
806 2 void Transaction::r2GapCompute(const Chunk* chunk, NakPdu& nak) {
807 2 FW_ASSERT(chunk->size > 0, static_cast<FwAssertArgType>(chunk->size));
808
809 // Calculate segment offsets relative to scope start
810 2 FileSize offsetStart = chunk->offset - nak.getScopeStart();
811 2 FileSize offsetEnd = offsetStart + chunk->size;
812
813 // Add segment to NAK PDU (returns false if array is full)
814 2 nak.addSegment(offsetStart, offsetEnd);
815 2 }
816
817 2 void Transaction::r2GapComputeWrapper(const Chunk* chunk, void* opaque) {
818 struct GapComputeContext {
819 Transaction* txn;
820 NakPdu* nak;
821 };
822 2 GapComputeContext* ctx = static_cast<GapComputeContext*>(opaque);
823 2 ctx->txn->r2GapCompute(chunk, *ctx->nak);
824 2 }
825
826 1 Status::T Transaction::rSubstateSendNak() {
827 1 Status::T status = Cfdp::Status::SUCCESS;
828
829 // Create and initialize NAK PDU
830
1/1
✓ Branch 2 taken 1 times.
1 NakPdu nakPdu;
831 1 Cfdp::PduDirection direction = PduDirection::DIRECTION_TOWARD_SENDER;
832
833
2/4
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
1 if (this->m_flags.rx.md_recv) {
834 // We have metadata, so send NAK with file data gaps
835
2/2
✓ Branch 2 taken 1 times.
✓ Branch 8 taken 1 times.
5 nakPdu.initialize(direction,
836 1 this->getClass(), // transmission mode
837 1 this->m_history->peer_eid, // source EID (receiver)
838 1 this->m_history->seq_num, // transaction sequence number
839 1 this->m_cfdpManager->getLocalEidParam(), // destination EID (sender)
840 0, // scope start
841 0 // scope end
842 );
843
844 // Compute gaps and add segments to NAK PDU
845 1 U32 chunkCount = this->m_chunks->chunks.getCount();
846 1 U32 maxChunks = this->m_chunks->chunks.getMaxChunks();
847
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 U32 gapLimit = (chunkCount < maxChunks) ? maxChunks : (maxChunks - 1);
848
849 // For each gap found, add it as a segment to the NAK PDU via callback
850 struct GapComputeContext {
851 Transaction* txn;
852 NakPdu* nak;
853 1 } gapCtx = {this, &nakPdu};
854
855
1/1
✓ Branch 7 taken 1 times.
1 U32 gapCount = this->m_chunks->chunks.computeGaps(static_cast<ChunkIdx>(gapLimit), this->m_fsize, 0,
856 &Transaction::r2GapComputeWrapper, &gapCtx);
857
858
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!gapCount) {
859 // No gaps left, file reception is complete
860 ✗ this->m_flags.rx.complete = true;
861 ✗ status = Cfdp::Status::SUCCESS;
862 } else {
863 // Gaps are present, send the NAK PDU
864
1/1
✓ Branch 6 taken 1 times.
1 status = this->m_engine->sendNak(this, nakPdu);
865
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (status == Cfdp::Status::SUCCESS) {
866 1 this->m_flags.rx.fd_nak_sent = true;
867
1/1
✓ Branch 8 taken 1 times.
1 this->m_cfdpManager->addSentNakSegmentRequests(this->m_chan_num, gapCount);
868 }
869 }
870 } else {
871 // Need to send NAK to request metadata PDU again
872 // Special case: scope start/end and segment[0] all zeros requests metadata
873 ✗ nakPdu.initialize(direction,
874 ✗ this->getClass(), // transmission mode
875 ✗ this->m_history->peer_eid, // source EID (receiver)
876 ✗ this->m_history->seq_num, // transaction sequence number
877 ✗ this->m_cfdpManager->getLocalEidParam(), // destination EID (sender)
878 0, // scope start (special value)
879 0 // scope end (special value)
880 );
881
882 // Add special segment [0,0] to request metadata
883 ✗ nakPdu.addSegment(0, 0);
884
885 ✗ status = this->m_engine->sendNak(this, nakPdu);
886 }
887
888 1 return status;
889 1 }
890
891 9 Status::T Transaction::r2CalcCrcChunk() {
892 9 U8 buf[R2CrcChunkSize];
893 FileSize count_bytes;
894 FileSize want_offs_size;
895 9 FwSizeType read_size;
896 Os::File::Status fileStatus;
897 9 Status::T ret = Cfdp::Status::SUCCESS;
898 9 FileSize rx_crc_calc_bytes_per_cycle = 0;
899
900
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 memset(buf, 0, sizeof(buf));
901
902 9 count_bytes = 0;
903
904 // Open file for CRC calculation if needed
905
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 if (ret == Cfdp::Status::SUCCESS) {
906
2/2
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 2 times.
9 if (this->m_state_data.receive.r2.rx_crc_calc_bytes == 0) {
907
2/2
✓ Branch 2 taken 7 times.
✓ Branch 9 taken 7 times.
7 this->m_crc = CFDP::Checksum(0);
908
909 // For Class 2 RX, the file was opened in WRITE mode for receiving FileData PDUs.
910 // Now we need to READ it for CRC calculation. Close and reopen in READ mode.
911
2/3
✓ Branch 6 taken 7 times.
✓ Branch 8 taken 7 times.
✗ Branch 9 not taken.
7 if (this->m_fd.isOpen()) {
912
1/1
✓ Branch 6 taken 7 times.
7 this->m_fd.close();
913 }
914
915
1/1
✓ Branch 14 taken 7 times.
7 fileStatus = this->m_fd.open(this->m_history->fnames.dst_filename.toChar(), Os::File::OPEN_READ);
916
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (fileStatus != Os::File::OP_OK) {
917 ✗ this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_FILE_SIZE_ERROR);
918 ✗ ret = Cfdp::Status::ERROR;
919 } else {
920 // Reset cached position since we just reopened the file
921 7 this->m_state_data.receive.cached_pos = 0;
922 }
923 }
924 }
925
926 // Process file in chunks
927
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 if (ret == Cfdp::Status::SUCCESS) {
928
1/1
✓ Branch 6 taken 9 times.
9 rx_crc_calc_bytes_per_cycle = this->m_cfdpManager->getRxCrcCalcBytesPerCycleParam();
929
930
3/4
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
42 while ((ret == Cfdp::Status::SUCCESS) && (count_bytes < rx_crc_calc_bytes_per_cycle) &&
931
2/2
✓ Branch 4 taken 24 times.
✓ Branch 5 taken 6 times.
30 (this->m_state_data.receive.r2.rx_crc_calc_bytes < this->m_fsize)) {
932 24 want_offs_size = this->m_state_data.receive.r2.rx_crc_calc_bytes + static_cast<FileSize>(sizeof(buf));
933
934
2/2
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 18 times.
24 if (want_offs_size > this->m_fsize) {
935 6 read_size = this->m_fsize - this->m_state_data.receive.r2.rx_crc_calc_bytes;
936 } else {
937 18 read_size = sizeof(buf);
938 }
939
940
2/2
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 23 times.
24 if (this->m_state_data.receive.cached_pos != this->m_state_data.receive.r2.rx_crc_calc_bytes) {
941 fileStatus =
942
1/1
✓ Branch 8 taken 1 times.
1 this->m_fd.seek(this->m_state_data.receive.r2.rx_crc_calc_bytes, Os::File::SeekType::ABSOLUTE);
943
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (fileStatus != Os::File::OP_OK) {
944
2/2
✓ Branch 13 taken 1 times.
✓ Branch 17 taken 1 times.
4 this->m_cfdpManager->log_WARNING_LO_RxSeekCrcFailed(
945 3 this->getClass(), this->m_history->src_eid, this->m_history->seq_num,
946 this->m_state_data.receive.r2.rx_crc_calc_bytes, fileStatus);
947 // this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_FILE_SIZE_ERROR);
948
1/1
✓ Branch 8 taken 1 times.
1 this->m_cfdpManager->incrementFaultFileSeek(this->m_chan_num);
949 1 ret = Cfdp::Status::ERROR;
950 }
951 }
952
953
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 23 times.
24 if (ret == Cfdp::Status::SUCCESS) {
954 23 FwSizeType expected_read_size = read_size;
955
1/1
✓ Branch 6 taken 23 times.
23 fileStatus = this->m_fd.read(buf, read_size, Os::File::WaitType::WAIT);
956
4/4
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 21 times.
23 if ((fileStatus != Os::File::OP_OK) || (read_size != expected_read_size)) {
957
2/2
✓ Branch 11 taken 2 times.
✓ Branch 15 taken 2 times.
8 this->m_cfdpManager->log_WARNING_LO_RxReadCrcFailed(
958 6 this->getClass(), this->m_history->src_eid, this->m_history->seq_num,
959 static_cast<U32>(expected_read_size), static_cast<I32>(read_size));
960
1/1
✓ Branch 6 taken 2 times.
2 this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_FILE_SIZE_ERROR);
961
1/1
✓ Branch 8 taken 2 times.
2 this->m_cfdpManager->incrementFaultFileRead(this->m_chan_num);
962 2 ret = Cfdp::Status::ERROR;
963 } else {
964
1/1
✓ Branch 6 taken 21 times.
21 this->m_crc.update(buf, this->m_state_data.receive.r2.rx_crc_calc_bytes,
965 static_cast<U32>(read_size));
966 21 this->m_state_data.receive.r2.rx_crc_calc_bytes += static_cast<FileSize>(read_size);
967 21 this->m_state_data.receive.cached_pos = this->m_state_data.receive.r2.rx_crc_calc_bytes;
968 21 count_bytes += static_cast<FileSize>(read_size);
969
970 // Reset inactivity timer to indicate transaction is actively processing
971
1/1
✓ Branch 6 taken 21 times.
21 this->m_engine->armInactTimer(this);
972 }
973 }
974 }
975 }
976
977 // Check final CRC if all bytes processed
978
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 3 times.
9 if (ret == Cfdp::Status::SUCCESS) {
979
1/2
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 if (this->m_state_data.receive.r2.rx_crc_calc_bytes == this->m_fsize) {
980 /* all bytes calculated, so now check */
981
3/3
✓ Branch 4 taken 6 times.
✓ Branch 6 taken 5 times.
✓ Branch 7 taken 1 times.
6 if (this->rCheckCrc(this->m_state_data.receive.r2.eof_crc) == Cfdp::Status::SUCCESS) {
982 /* CRC matched! We are happy */
983 5 this->m_keep = Cfdp::Keep::KEEP; /* save the file */
984
985 /* set FIN PDU status */
986 5 this->m_state_data.receive.r2.dc = FinDeliveryCode::FIN_DELIVERY_CODE_COMPLETE;
987 5 this->m_state_data.receive.r2.fs = FinFileStatus::FIN_FILE_STATUS_RETAINED;
988 } else {
989
1/1
✓ Branch 2 taken 1 times.
1 this->r2SetFinTxnStatus(TxnStatus::TXN_STATUS_FILE_CHECKSUM_FAILURE);
990 }
991
992 6 this->m_flags.com.crc_calc = true;
993 } else {
994 // Not all bytes processed yet, return ERROR to signal need to continue
995 ✗ ret = Cfdp::Status::ERROR;
996 }
997 }
998
999 18 return ret;
1000 }
1001
1002 13 Status::T Transaction::r2SubstateSendFin() {
1003 Status::T sret;
1004 13 Status::T ret = Cfdp::Status::SUCCESS;
1005
1006
6/8
✓ Branch 5 taken 7 times.
✓ Branch 6 taken 6 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 7 times.
✓ Branch 10 taken 7 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 7 times.
✓ Branch 13 taken 6 times.
13 if (!TxnStatusIsError(this->m_history->txn_stat) && !this->m_flags.com.crc_calc) {
1007 /* no error, and haven't checked CRC -- so start checking it */
1008
2/2
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 6 times.
7 if (this->r2CalcCrcChunk()) {
1009 1 ret = Cfdp::Status::ERROR; /* signal to caller to re-enter next tick */
1010 }
1011 }
1012
1013
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1 times.
13 if (ret != Cfdp::Status::ERROR) {
1014 24 sret = this->m_engine->sendFin(this, this->m_state_data.receive.r2.dc, this->m_state_data.receive.r2.fs,
1015 12 static_cast<ConditionCode>(TxnStatusToConditionCode(this->m_history->txn_stat)));
1016
1017 /* Serialization error already logged in serializeAndSendPdu if ERROR returned */
1018 12 this->m_state_data.receive.sub_state =
1019 RxSubState::RX_SUB_STATE_CLOSEOUT_SYNC; /* whether or not FIN send successful, ok to transition state */
1020
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (sret != Cfdp::Status::SUCCESS) {
1021 ✗ ret = Cfdp::Status::ERROR;
1022 }
1023 }
1024
1025 /* if no message, then try again next time */
1026 13 return ret;
1027 }
1028
1029 4 void Transaction::r2RecvFinAck(const Fw::Buffer& buffer) {
1030 // Deserialize ACK PDU from buffer
1031
1/1
✓ Branch 2 taken 4 times.
4 AckPdu ack;
1032 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
1033
3/3
✓ Branch 5 taken 4 times.
✓ Branch 11 taken 4 times.
✓ Branch 14 taken 4 times.
4 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
1034
2/2
✓ Branch 5 taken 4 times.
✓ Branch 8 taken 4 times.
4 sb.setBuffLen(buffer.getSize());
1035
1036
1/1
✓ Branch 2 taken 4 times.
4 Fw::SerializeStatus deserStatus = ack.deserializeFrom(sb);
1037
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (deserStatus != Fw::FW_SERIALIZE_OK) {
1038 // Bad ACK PDU
1039 ✗ this->m_cfdpManager->log_WARNING_LO_FailAckPduDeserialization(this->getChannelId(),
1040 static_cast<I32>(deserStatus));
1041 ✗ this->m_cfdpManager->incrementRecvErrors(this->m_chan_num);
1042 ✗ return;
1043 }
1044
1045 // ACK PDU has been validated during deserialization
1046 // Got fin-ack, so time to close the state
1047
1/1
✓ Branch 2 taken 4 times.
4 this->r2Reset();
1048 4 }
1049
1050 3 void Transaction::r2RecvMd(const Fw::Buffer& buffer) {
1051
1/1
✓ Branch 2 taken 3 times.
3 Fw::String fname;
1052 Os::File::Status fileStatus;
1053 Os::FileSystem::Status fileSysStatus;
1054 3 bool success = true;
1055
1056 /* it isn't an error to get another MD PDU, right? */
1057
2/4
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
3 if (!this->m_flags.rx.md_recv) {
1058 /* NOTE: this->m_flags.rx.md_recv always 1 in R1, so this is R2 only */
1059 /* parse the md PDU. this will overwrite the transaction's history, which contains our filename. so let's
1060 * save the filename in a local buffer so it can be used with moveFile upon successful parsing of
1061 * the md PDU */
1062
1/1
✓ Branch 7 taken 3 times.
3 fname = this->m_history->fnames.dst_filename;
1063
1064 // Deserialize Metadata PDU from buffer
1065
1/1
✓ Branch 2 taken 3 times.
3 MetadataPdu md;
1066 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
1067
3/3
✓ Branch 5 taken 3 times.
✓ Branch 11 taken 3 times.
✓ Branch 14 taken 3 times.
3 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
1068
2/2
✓ Branch 5 taken 3 times.
✓ Branch 8 taken 3 times.
3 sb.setBuffLen(buffer.getSize());
1069
1070
1/1
✓ Branch 2 taken 3 times.
3 Fw::SerializeStatus deserStatus = md.deserializeFrom(sb);
1071
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (deserStatus != Fw::FW_SERIALIZE_OK) {
1072 // Bad metadata PDU
1073 ✗ this->m_cfdpManager->log_WARNING_LO_FailMetadataPduDeserialization(this->getChannelId(),
1074 static_cast<I32>(deserStatus));
1075 ✗ return;
1076 }
1077
1078 // PDU validation already done during deserialization
1079
1/1
✓ Branch 6 taken 3 times.
3 this->m_engine->recvMd(this, md);
1080
1081 /* successfully obtained md PDU */
1082
3/4
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 2 times.
3 if (this->m_flags.rx.eof_recv) {
1083 /* EOF was received, so check that md and EOF sizes match */
1084
1/2
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 if (this->m_state_data.receive.r2.eof_size != this->m_fsize) {
1085
2/2
✓ Branch 19 taken 1 times.
✓ Branch 23 taken 1 times.
2 this->m_cfdpManager->log_WARNING_LO_RxEofMdSizeMismatch(this->getClass(), this->m_history->src_eid,
1086 1 this->m_history->seq_num, this->m_fsize,
1087 this->m_state_data.receive.r2.eof_size);
1088
1/1
✓ Branch 8 taken 1 times.
1 this->m_cfdpManager->incrementFaultFileSizeMismatch(this->m_chan_num);
1089
1/1
✓ Branch 2 taken 1 times.
1 this->r2SetFinTxnStatus(TxnStatus::TXN_STATUS_FILE_SIZE_ERROR);
1090 1 success = false;
1091 }
1092 }
1093
1094
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 if (success) {
1095 /* close and rename file */
1096
1/1
✓ Branch 6 taken 2 times.
2 this->m_fd.close();
1097
1098
1/1
✓ Branch 11 taken 2 times.
2 fileSysStatus = Os::FileSystem::moveFile(fname.toChar(), this->m_history->fnames.dst_filename.toChar());
1099
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (fileSysStatus != Os::FileSystem::OP_OK) {
1100
2/2
✓ Branch 13 taken 1 times.
✓ Branch 17 taken 1 times.
5 this->m_cfdpManager->log_WARNING_LO_RxFileRenameFailed(
1101 3 this->getClass(), this->m_history->src_eid, this->m_history->seq_num, fname,
1102 1 this->m_history->fnames.dst_filename, fileSysStatus);
1103
1/1
✓ Branch 2 taken 1 times.
1 this->r2SetFinTxnStatus(TxnStatus::TXN_STATUS_FILESTORE_REJECTION);
1104
1/1
✓ Branch 8 taken 1 times.
1 this->m_cfdpManager->incrementFaultFileRename(this->m_chan_num);
1105 1 success = false;
1106 } else {
1107 // File was successfully renamed, open for writing
1108
1/1
✓ Branch 14 taken 1 times.
1 fileStatus = this->m_fd.open(this->m_history->fnames.dst_filename.toChar(), Os::File::OPEN_WRITE);
1109
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (fileStatus != Os::File::OP_OK) {
1110
2/2
✓ Branch 13 taken 1 times.
✓ Branch 17 taken 1 times.
5 this->m_cfdpManager->log_WARNING_LO_RxFileReopenFailed(
1111 3 this->getClass(), this->m_history->src_eid, this->m_history->seq_num,
1112 1 this->m_history->fnames.dst_filename, fileStatus);
1113
1/1
✓ Branch 2 taken 1 times.
1 this->r2SetFinTxnStatus(TxnStatus::TXN_STATUS_FILESTORE_REJECTION);
1114
1/1
✓ Branch 8 taken 1 times.
1 this->m_cfdpManager->incrementFaultFileOpen(this->m_chan_num);
1115 1 success = false;
1116 }
1117 }
1118
1119
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (success) {
1120 ✗ this->m_state_data.receive.cached_pos = 0; /* reset psn due to open */
1121 ✗ this->m_flags.rx.md_recv = true;
1122 ✗ this->m_state_data.receive.r2.acknak_count = 0; /* in case part of NAK */
1123 ✗ this->r2Complete(true); /* check for completion now that md is received */
1124 }
1125 }
1126 3 }
1127 3 }
1128
1129 1 void Transaction::rSendInactivityEvent() {
1130
2/2
✓ Branch 15 taken 1 times.
✓ Branch 19 taken 1 times.
2 this->m_cfdpManager->log_WARNING_LO_RxInactivityTimeout(this->getClass(), this->m_history->src_eid,
1131 1 this->m_history->seq_num);
1132 1 this->m_cfdpManager->incrementFaultInactivityTimer(this->m_chan_num);
1133 1 }
1134
1135 // ======================================================================
1136 // Dispatch Methods
1137 // ======================================================================
1138
1139 53 void Transaction::rDispatchRecv(const Fw::Buffer& buffer, const RSubstateDispatchTable* dispatch, StateRecvFunc fd_fn) {
1140 StateRecvFunc selected_handler;
1141
1142 53 FW_ASSERT(this->m_state_data.receive.sub_state < RxSubState::RX_SUB_STATE_NUM_STATES,
1143 static_cast<U8>(this->m_state_data.receive.sub_state),
1144 static_cast<U8>(RxSubState::RX_SUB_STATE_NUM_STATES));
1145
1146 53 selected_handler = nullptr;
1147
1148 // Peek at PDU type from buffer
1149 53 Cfdp::PduTypeEnum::T pduType = Cfdp::peekPduType(buffer);
1150
1151 // Special handling for file data PDU
1152
2/2
✓ Branch 0 taken 35 times.
✓ Branch 1 taken 18 times.
53 if (pduType == Cfdp::PduTypeEnum::FILE_DATA) {
1153 /* For file data PDU, use the provided fd_fn */
1154
1/2
✓ Branch 5 taken 35 times.
✗ Branch 6 not taken.
35 if (!TxnStatusIsError(this->m_history->txn_stat)) {
1155 35 selected_handler = fd_fn;
1156 }
1157 } else {
1158 // Not a file-data PDU - parse as a directive PDU to get the directive code.
1159 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
1160
3/3
✓ Branch 5 taken 18 times.
✓ Branch 11 taken 18 times.
✓ Branch 14 taken 18 times.
18 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
1161
2/2
✓ Branch 5 taken 18 times.
✓ Branch 8 taken 18 times.
18 sb.setBuffLen(buffer.getSize());
1162
1163 18 Cfdp::PduHeader header;
1164
2/3
✓ Branch 1 taken 18 times.
✓ Branch 3 taken 18 times.
✗ Branch 4 not taken.
18 if (header.fromSerialBuffer(sb) == Fw::FW_SERIALIZE_OK) {
1165 // Read directive code (first byte after header for directive PDUs)
1166 18 U8 directiveCodeByte;
1167
2/3
✓ Branch 2 taken 18 times.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
18 if (sb.deserializeTo(directiveCodeByte) == Fw::FW_SERIALIZE_OK) {
1168 18 FileDirective directiveCode = static_cast<FileDirective>(directiveCodeByte);
1169
1170
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 1 times.
18 if (directiveCode < FileDirective::FILE_DIRECTIVE_INVALID_MAX) {
1171 /* The CFDP_R_SubstateDispatchTable_t is only used with file directive PDU */
1172
1/2
✓ Branch 4 taken 17 times.
✗ Branch 5 not taken.
17 if (dispatch->state[static_cast<U32>(this->m_state_data.receive.sub_state)] != nullptr) {
1173 34 selected_handler = dispatch->state[static_cast<U32>(this->m_state_data.receive.sub_state)]
1174 17 ->fdirective[static_cast<U32>(directiveCode)];
1175 }
1176 } else {
1177
1/1
✓ Branch 8 taken 1 times.
1 this->m_cfdpManager->incrementRecvSpurious(this->m_chan_num);
1178
2/2
✓ Branch 11 taken 1 times.
✓ Branch 15 taken 1 times.
4 this->m_cfdpManager->log_WARNING_LO_RxInvalidDirectiveCode(
1179 3 this->getClass(), this->m_history->src_eid, this->m_history->seq_num, directiveCodeByte,
1180 1 static_cast<U8>(this->m_state_data.receive.sub_state));
1181 }
1182 }
1183 }
1184 18 }
1185
1186 /*
1187 * NOTE: if no handler is selected, this will drop packets on the floor here.
1188 */
1189
2/2
✓ Branch 0 taken 52 times.
✓ Branch 1 taken 1 times.
53 if (selected_handler != nullptr) {
1190
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 52 times.
52 (this->*selected_handler)(buffer);
1191 } else {
1192 1 this->m_cfdpManager->incrementRecvDropped(this->m_chan_num);
1193 }
1194 53 }
1195
1196 } // namespace Cfdp
1197 } // namespace Ccsds
1198 } // namespace Svc
1199