GCC Code Coverage Report


Directory: Svc/Ccsds/CfdpManager/
File: TransactionRx.cpp
Date: 2026-09-03 21:16:27
Exec Total Coverage
Lines: 482 543 88.8%
Functions: 28 29 96.6%
Branches: 387 514 75.3%

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 13100 Transaction::Transaction(Channel* channel, U8 channelId, Engine* engine, CfdpManager* manager)
61 13100 : m_state(TxnState::TXN_STATE_UNDEF),
62 13100 m_txn_class(Cfdp::Class::CLASS_1),
63 13100 m_history(nullptr),
64 13100 m_chunks(nullptr),
65 13100 m_inactivity_timer(),
66
1/1
✓ Branch 3 taken 13100 times.
13100 m_ack_timer(),
67 13100 m_fsize(0),
68 13100 m_foffs(0),
69
1/1
✓ Branch 3 taken 13100 times.
13100 m_fd(),
70
1/1
✓ Branch 3 taken 13100 times.
13100 m_crc(),
71 13100 m_keep(Cfdp::Keep::KEEP),
72 13100 m_chan_num(channelId), // Initialize from parameter
73 13100 m_priority(0),
74 13100 m_initType(TransactionInitType::INIT_BY_COMMAND),
75 13100 m_cl_node{},
76 13100 m_pb(nullptr),
77 13100 m_state_data{},
78 13100 m_flags{},
79 13100 m_cfdpManager(manager), // Initialize from parameter
80 13100 m_chan(channel), // Initialize from parameter
81 13100 m_engine(engine) // Initialize from parameter
82 {
83 // Fully zero the union storage
84
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 13100 times.
13100 memset(&this->m_state_data, 0, sizeof(this->m_state_data));
85
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 13100 times.
13100 memset(&this->m_flags, 0, sizeof(this->m_flags));
86 13100 }
87
88 13100 Transaction::~Transaction() {}
89
90 13124 void Transaction::reset() {
91 // Reset transaction state to default values
92 13124 this->m_state = TxnState::TXN_STATE_UNDEF;
93 13124 this->m_txn_class = Cfdp::Class::CLASS_1;
94 13124 this->m_fsize = 0;
95 13124 this->m_foffs = 0;
96 13124 this->m_keep = Cfdp::Keep::KEEP;
97 13124 this->m_priority = 0;
98 13124 this->m_initType = TransactionInitType::INIT_BY_COMMAND;
99
2/2
✓ Branch 2 taken 13124 times.
✓ Branch 9 taken 13124 times.
13124 this->m_crc = CFDP::Checksum(0);
100 13124 this->m_pb = nullptr;
101
102 // Fully zero the union storage
103
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 13124 times.
13124 memset(&this->m_state_data, 0, sizeof(this->m_state_data));
104
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 13124 times.
13124 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 13124 times.
13124 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 13124 this->m_inactivity_timer.disableTimer();
114 13124 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 13124 }
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 35 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 35 this->rDispatchRecv(buffer, &substate_fns, &Transaction::r2SubstateRecvFileData);
195 35 }
196
197 70 void Transaction::rAckTimerTick() {
198 70 U8 ack_limit = 0;
199
200 /* note: the ack timer is only ever armed on class 2 */
201
4/6
✓ Branch 1 taken 33 times.
✓ Branch 2 taken 37 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 33 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 33 times.
70 if (this->m_state != TxnState::TXN_STATE_R2 || !this->m_flags.com.ack_timer_armed) {
202 /* nothing to do */
203 37 return;
204 }
205
206
2/2
✓ Branch 4 taken 26 times.
✓ Branch 5 taken 7 times.
33 if (this->m_ack_timer.getStatus() == Timer::Status::RUNNING) {
207 26 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 80 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 80 times.
✓ Branch 3 taken 80 times.
✗ Branch 4 not taken.
80 if (!this->m_flags.com.inactivity_fired) {
249
1/2
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
80 if (this->m_inactivity_timer.getStatus() == Timer::Status::RUNNING) {
250 80 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 69 times.
80 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 80 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 80 times.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 74 times.
80 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 74 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 73 times.
74 } 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 73 times.
✓ Branch 3 taken 12 times.
✓ Branch 4 taken 61 times.
73 } else if (this->m_flags.rx.send_fin) {
294
1/2
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
12 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 61 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 80 bool retries_exhausted = false;
309
4/6
✗ Branch 1 not taken.
✓ Branch 2 taken 80 times.
✓ Branch 3 taken 11 times.
✓ Branch 4 taken 69 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 11 times.
80 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 80 times.
✓ Branch 3 taken 11 times.
✓ Branch 4 taken 69 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 11 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
80 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 69 this->rAckTimerTick();
329 }
330 80 }
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 78 void Transaction::rInit() {
343 Os::File::Status status;
344
1/1
✓ Branch 2 taken 78 times.
78 Fw::String tmpDir;
345
1/1
✓ Branch 2 taken 78 times.
78 Fw::String dst;
346
347
2/2
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 66 times.
78 if (this->m_state == TxnState::TXN_STATE_R2) {
348
3/4
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 9 times.
12 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 12 times.
12 this->m_engine->armAckTimer(this);
366 }
367
368
1/1
✓ Branch 14 taken 78 times.
78 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 77 times.
78 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 77 this->m_state_data.receive.sub_state = RxSubState::RX_SUB_STATE_FILEDATA;
381 }
382 156 }
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 17 void Transaction::r1Reset() {
390 17 this->m_engine->finishTransaction(this, true);
391 17 }
392
393 5 void Transaction::r2Reset() {
394 5 if ((this->m_state_data.receive.sub_state == RxSubState::RX_SUB_STATE_CLOSEOUT_SYNC) ||
395
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 (static_cast<U8>(this->m_state_data.receive.r2.eof_cc) !=
396 1 static_cast<U8>(ConditionCode::CONDITION_CODE_NO_ERROR)) ||
397
4/10
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✓ Branch 14 taken 5 times.
✗ Branch 15 not taken.
6 TxnStatusIsError(this->m_history->txn_stat) || this->m_flags.com.canceled) {
398 5 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 5 }
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 32 Status::T Transaction::rProcessFd(const Fw::Buffer& buffer) {
486 32 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 32 times.
32 FileDataPdu fd;
491 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
492
3/3
✓ Branch 5 taken 32 times.
✓ Branch 11 taken 32 times.
✓ Branch 14 taken 32 times.
32 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
493
2/2
✓ Branch 5 taken 32 times.
✓ Branch 8 taken 32 times.
32 sb.setBuffLen(buffer.getSize());
494
495
1/1
✓ Branch 2 taken 32 times.
32 Fw::SerializeStatus deserStatus = fd.deserializeFrom(sb);
496
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
32 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 32 FileSize offset = fd.getOffset();
509 32 U16 dataSize = fd.getDataSize();
510 32 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 32 times.
✗ Branch 1 not taken.
32 if (ret == Cfdp::Status::SUCCESS) {
515
3/4
✗ Branch 1 not taken.
✓ Branch 2 taken 32 times.
✓ Branch 3 taken 29 times.
✓ Branch 4 taken 3 times.
32 const FileSize bound = this->m_flags.rx.md_recv ? this->m_fsize : std::numeric_limits<FileSize>::max();
516
3/4
✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 30 times.
32 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 30 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 29 times.
32 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 29 times.
✓ Branch 1 taken 2 times.
31 if (ret == Cfdp::Status::SUCCESS) {
535
2/2
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 25 times.
29 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 28 times.
✓ Branch 1 taken 3 times.
31 if (ret == Cfdp::Status::SUCCESS) {
549 28 FwSizeType write_size = dataSize;
550
1/1
✓ Branch 6 taken 28 times.
28 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 27 times.
28 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 27 this->m_state_data.receive.cached_pos = static_cast<FileSize>(dataSize) + offset;
560
1/1
✓ Branch 8 taken 27 times.
27 this->m_cfdpManager->addRecvFileDataBytes(this->m_chan_num, dataSize);
561 }
562 }
563
564 31 return ret;
565 32 }
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 25 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 25 times.
25 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 25 times.
25 FileDataPdu fd;
767 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
768
3/3
✓ Branch 5 taken 25 times.
✓ Branch 11 taken 25 times.
✓ Branch 14 taken 25 times.
25 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
769
2/2
✓ Branch 5 taken 25 times.
✓ Branch 8 taken 25 times.
25 sb.setBuffLen(buffer.getSize());
770
771
1/1
✓ Branch 2 taken 25 times.
25 Fw::SerializeStatus deserStatus = fd.deserializeFrom(sb);
772
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 25 times.
25 if (deserStatus != Fw::FW_SERIALIZE_OK) {
773 // Bad file data PDU, reset transaction
774 this->m_cfdpManager->log_WARNING_LO_FailFileDataPduDeserialization(this->getChannelId(),
775 static_cast<I32>(deserStatus));
776 this->r2Reset();
777 return;
778 }
779
780 /* got file data PDU? */
781
1/1
✓ Branch 6 taken 25 times.
25 ret = this->m_engine->recvFd(this, fd);
782
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 if (ret == Cfdp::Status::SUCCESS) {
783
1/1
✓ Branch 2 taken 25 times.
25 ret = this->rProcessFd(buffer);
784 }
785
786
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 1 times.
25 if (ret == Cfdp::Status::SUCCESS) {
787 /* class 2 does CRC at FIN, but track gaps */
788
1/1
✓ Branch 9 taken 24 times.
24 this->m_chunks->chunks.add(fd.getOffset(), static_cast<FileSize>(fd.getDataSize()));
789
790
3/4
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 21 times.
24 if (this->m_flags.rx.fd_nak_sent) {
791
1/1
✓ Branch 2 taken 3 times.
3 this->r2Complete(false); /* once nak-retransmit received, start checking for completion at each fd */
792 }
793
794
3/4
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✓ Branch 3 taken 23 times.
✓ Branch 4 taken 1 times.
24 if (!this->m_flags.rx.complete) {
795
1/1
✓ Branch 6 taken 23 times.
23 this->m_engine->armAckTimer(this); /* re-arm ACK timer, since we got data */
796 }
797
798 24 this->m_state_data.receive.r2.acknak_count = 0;
799 } else {
800 /* Reset transaction on failure */
801
1/1
✓ Branch 2 taken 1 times.
1 this->r2Reset();
802 }
803 25 }
804
805 2 void Transaction::r2GapCompute(const Chunk* chunk, NakPdu& nak) {
806 2 FW_ASSERT(chunk->size > 0, static_cast<FwAssertArgType>(chunk->size));
807
808 // Calculate segment offsets relative to scope start
809 2 FileSize offsetStart = chunk->offset - nak.getScopeStart();
810 2 FileSize offsetEnd = offsetStart + chunk->size;
811
812 // Add segment to NAK PDU (returns false if array is full)
813 2 nak.addSegment(offsetStart, offsetEnd);
814 2 }
815
816 2 void Transaction::r2GapComputeWrapper(const Chunk* chunk, void* opaque) {
817 struct GapComputeContext {
818 Transaction* txn;
819 NakPdu* nak;
820 };
821 2 GapComputeContext* ctx = static_cast<GapComputeContext*>(opaque);
822 2 ctx->txn->r2GapCompute(chunk, *ctx->nak);
823 2 }
824
825 1 Status::T Transaction::rSubstateSendNak() {
826 1 Status::T status = Cfdp::Status::SUCCESS;
827
828 // Create and initialize NAK PDU
829
1/1
✓ Branch 2 taken 1 times.
1 NakPdu nakPdu;
830 1 Cfdp::PduDirection direction = PduDirection::DIRECTION_TOWARD_SENDER;
831
832
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) {
833 // We have metadata, so send NAK with file data gaps
834
2/2
✓ Branch 2 taken 1 times.
✓ Branch 8 taken 1 times.
5 nakPdu.initialize(direction,
835 1 this->getClass(), // transmission mode
836 1 this->m_history->peer_eid, // source EID (receiver)
837 1 this->m_history->seq_num, // transaction sequence number
838 1 this->m_cfdpManager->getLocalEidParam(), // destination EID (sender)
839 0, // scope start
840 0 // scope end
841 );
842
843 // Compute gaps and add segments to NAK PDU
844 1 U32 chunkCount = this->m_chunks->chunks.getCount();
845 1 U32 maxChunks = this->m_chunks->chunks.getMaxChunks();
846
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 U32 gapLimit = (chunkCount < maxChunks) ? maxChunks : (maxChunks - 1);
847
848 // For each gap found, add it as a segment to the NAK PDU via callback
849 struct GapComputeContext {
850 Transaction* txn;
851 NakPdu* nak;
852 1 } gapCtx = {this, &nakPdu};
853
854
1/1
✓ Branch 7 taken 1 times.
1 U32 gapCount = this->m_chunks->chunks.computeGaps(static_cast<ChunkIdx>(gapLimit), this->m_fsize, 0,
855 &Transaction::r2GapComputeWrapper, &gapCtx);
856
857
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!gapCount) {
858 // No gaps left, file reception is complete
859 this->m_flags.rx.complete = true;
860 status = Cfdp::Status::SUCCESS;
861 } else {
862 // Gaps are present, send the NAK PDU
863
1/1
✓ Branch 6 taken 1 times.
1 status = this->m_engine->sendNak(this, nakPdu);
864
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (status == Cfdp::Status::SUCCESS) {
865 1 this->m_flags.rx.fd_nak_sent = true;
866
1/1
✓ Branch 8 taken 1 times.
1 this->m_cfdpManager->addSentNakSegmentRequests(this->m_chan_num, gapCount);
867 }
868 }
869 } else {
870 // Need to send NAK to request metadata PDU again
871 // Special case: scope start/end and segment[0] all zeros requests metadata
872 nakPdu.initialize(direction,
873 this->getClass(), // transmission mode
874 this->m_history->peer_eid, // source EID (receiver)
875 this->m_history->seq_num, // transaction sequence number
876 this->m_cfdpManager->getLocalEidParam(), // destination EID (sender)
877 0, // scope start (special value)
878 0 // scope end (special value)
879 );
880
881 // Add special segment [0,0] to request metadata
882 nakPdu.addSegment(0, 0);
883
884 status = this->m_engine->sendNak(this, nakPdu);
885 }
886
887 1 return status;
888 1 }
889
890 8 Status::T Transaction::r2CalcCrcChunk() {
891 8 U8 buf[R2CrcChunkSize];
892 FileSize count_bytes;
893 FileSize want_offs_size;
894 8 FwSizeType read_size;
895 Os::File::Status fileStatus;
896 8 Status::T ret = Cfdp::Status::SUCCESS;
897 8 FileSize rx_crc_calc_bytes_per_cycle = 0;
898
899
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 memset(buf, 0, sizeof(buf));
900
901 8 count_bytes = 0;
902
903 // Open file for CRC calculation if needed
904
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 if (ret == Cfdp::Status::SUCCESS) {
905
2/2
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 2 times.
8 if (this->m_state_data.receive.r2.rx_crc_calc_bytes == 0) {
906
2/2
✓ Branch 2 taken 6 times.
✓ Branch 9 taken 6 times.
6 this->m_crc = CFDP::Checksum(0);
907
908 // For Class 2 RX, the file was opened in WRITE mode for receiving FileData PDUs.
909 // Now we need to READ it for CRC calculation. Close and reopen in READ mode.
910
2/3
✓ Branch 6 taken 6 times.
✓ Branch 8 taken 6 times.
✗ Branch 9 not taken.
6 if (this->m_fd.isOpen()) {
911
1/1
✓ Branch 6 taken 6 times.
6 this->m_fd.close();
912 }
913
914
1/1
✓ Branch 14 taken 6 times.
6 fileStatus = this->m_fd.open(this->m_history->fnames.dst_filename.toChar(), Os::File::OPEN_READ);
915
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (fileStatus != Os::File::OP_OK) {
916 this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_FILE_SIZE_ERROR);
917 ret = Cfdp::Status::ERROR;
918 } else {
919 // Reset cached position since we just reopened the file
920 6 this->m_state_data.receive.cached_pos = 0;
921 }
922 }
923 }
924
925 // Process file in chunks
926
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 if (ret == Cfdp::Status::SUCCESS) {
927
1/1
✓ Branch 6 taken 8 times.
8 rx_crc_calc_bytes_per_cycle = this->m_cfdpManager->getRxCrcCalcBytesPerCycleParam();
928
929
3/4
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
31 while ((ret == Cfdp::Status::SUCCESS) && (count_bytes < rx_crc_calc_bytes_per_cycle) &&
930
2/2
✓ Branch 4 taken 23 times.
✓ Branch 5 taken 6 times.
29 (this->m_state_data.receive.r2.rx_crc_calc_bytes < this->m_fsize)) {
931 23 want_offs_size = this->m_state_data.receive.r2.rx_crc_calc_bytes + static_cast<FileSize>(sizeof(buf));
932
933
2/2
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 18 times.
23 if (want_offs_size > this->m_fsize) {
934 5 read_size = this->m_fsize - this->m_state_data.receive.r2.rx_crc_calc_bytes;
935 } else {
936 18 read_size = sizeof(buf);
937 }
938
939
2/2
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 22 times.
23 if (this->m_state_data.receive.cached_pos != this->m_state_data.receive.r2.rx_crc_calc_bytes) {
940 fileStatus =
941
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);
942
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (fileStatus != Os::File::OP_OK) {
943
2/2
✓ Branch 13 taken 1 times.
✓ Branch 17 taken 1 times.
4 this->m_cfdpManager->log_WARNING_LO_RxSeekCrcFailed(
944 3 this->getClass(), this->m_history->src_eid, this->m_history->seq_num,
945 this->m_state_data.receive.r2.rx_crc_calc_bytes, fileStatus);
946 // this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_FILE_SIZE_ERROR);
947
1/1
✓ Branch 8 taken 1 times.
1 this->m_cfdpManager->incrementFaultFileSeek(this->m_chan_num);
948 1 ret = Cfdp::Status::ERROR;
949 }
950 }
951
952
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 1 times.
23 if (ret == Cfdp::Status::SUCCESS) {
953 22 FwSizeType expected_read_size = read_size;
954
1/1
✓ Branch 6 taken 22 times.
22 fileStatus = this->m_fd.read(buf, read_size, Os::File::WaitType::WAIT);
955
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 21 times.
22 if (fileStatus != Os::File::OP_OK) {
956
2/2
✓ Branch 11 taken 1 times.
✓ Branch 15 taken 1 times.
4 this->m_cfdpManager->log_WARNING_LO_RxReadCrcFailed(
957 3 this->getClass(), this->m_history->src_eid, this->m_history->seq_num,
958 static_cast<U32>(expected_read_size), static_cast<I32>(read_size));
959
1/1
✓ Branch 6 taken 1 times.
1 this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_FILE_SIZE_ERROR);
960
1/1
✓ Branch 8 taken 1 times.
1 this->m_cfdpManager->incrementFaultFileRead(this->m_chan_num);
961 1 ret = Cfdp::Status::ERROR;
962 } else {
963
1/1
✓ Branch 6 taken 21 times.
21 this->m_crc.update(buf, this->m_state_data.receive.r2.rx_crc_calc_bytes,
964 static_cast<U32>(read_size));
965 21 this->m_state_data.receive.r2.rx_crc_calc_bytes += static_cast<FileSize>(read_size);
966 21 this->m_state_data.receive.cached_pos = this->m_state_data.receive.r2.rx_crc_calc_bytes;
967 21 count_bytes += static_cast<FileSize>(read_size);
968
969 // Reset inactivity timer to indicate transaction is actively processing
970
1/1
✓ Branch 6 taken 21 times.
21 this->m_engine->armInactTimer(this);
971 }
972 }
973 }
974 }
975
976 // Check final CRC if all bytes processed
977
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
8 if (ret == Cfdp::Status::SUCCESS) {
978
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) {
979 /* all bytes calculated, so now check */
980
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) {
981 /* CRC matched! We are happy */
982 5 this->m_keep = Cfdp::Keep::KEEP; /* save the file */
983
984 /* set FIN PDU status */
985 5 this->m_state_data.receive.r2.dc = FinDeliveryCode::FIN_DELIVERY_CODE_COMPLETE;
986 5 this->m_state_data.receive.r2.fs = FinFileStatus::FIN_FILE_STATUS_RETAINED;
987 } else {
988
1/1
✓ Branch 2 taken 1 times.
1 this->r2SetFinTxnStatus(TxnStatus::TXN_STATUS_FILE_CHECKSUM_FAILURE);
989 }
990
991 6 this->m_flags.com.crc_calc = true;
992 } else {
993 // Not all bytes processed yet, return ERROR to signal need to continue
994 ret = Cfdp::Status::ERROR;
995 }
996 }
997
998 16 return ret;
999 }
1000
1001 12 Status::T Transaction::r2SubstateSendFin() {
1002 Status::T sret;
1003 12 Status::T ret = Cfdp::Status::SUCCESS;
1004
1005
6/8
✓ Branch 5 taken 6 times.
✓ Branch 6 taken 6 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 6 times.
✓ Branch 10 taken 6 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 6 times.
✓ Branch 13 taken 6 times.
12 if (!TxnStatusIsError(this->m_history->txn_stat) && !this->m_flags.com.crc_calc) {
1006 /* no error, and haven't checked CRC -- so start checking it */
1007
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
6 if (this->r2CalcCrcChunk()) {
1008 ret = Cfdp::Status::ERROR; /* signal to caller to re-enter next tick */
1009 }
1010 }
1011
1012
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 if (ret != Cfdp::Status::ERROR) {
1013 24 sret = this->m_engine->sendFin(this, this->m_state_data.receive.r2.dc, this->m_state_data.receive.r2.fs,
1014 12 static_cast<ConditionCode>(TxnStatusToConditionCode(this->m_history->txn_stat)));
1015
1016 /* Serialization error already logged in serializeAndSendPdu if ERROR returned */
1017 12 this->m_state_data.receive.sub_state =
1018 RxSubState::RX_SUB_STATE_CLOSEOUT_SYNC; /* whether or not FIN send successful, ok to transition state */
1019
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (sret != Cfdp::Status::SUCCESS) {
1020 ret = Cfdp::Status::ERROR;
1021 }
1022 }
1023
1024 /* if no message, then try again next time */
1025 12 return ret;
1026 }
1027
1028 4 void Transaction::r2RecvFinAck(const Fw::Buffer& buffer) {
1029 // Deserialize ACK PDU from buffer
1030
1/1
✓ Branch 2 taken 4 times.
4 AckPdu ack;
1031 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
1032
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());
1033
2/2
✓ Branch 5 taken 4 times.
✓ Branch 8 taken 4 times.
4 sb.setBuffLen(buffer.getSize());
1034
1035
1/1
✓ Branch 2 taken 4 times.
4 Fw::SerializeStatus deserStatus = ack.deserializeFrom(sb);
1036
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (deserStatus != Fw::FW_SERIALIZE_OK) {
1037 // Bad ACK PDU
1038 this->m_cfdpManager->log_WARNING_LO_FailAckPduDeserialization(this->getChannelId(),
1039 static_cast<I32>(deserStatus));
1040 this->m_cfdpManager->incrementRecvErrors(this->m_chan_num);
1041 return;
1042 }
1043
1044 // ACK PDU has been validated during deserialization
1045 // Got fin-ack, so time to close the state
1046
1/1
✓ Branch 2 taken 4 times.
4 this->r2Reset();
1047 4 }
1048
1049 3 void Transaction::r2RecvMd(const Fw::Buffer& buffer) {
1050
1/1
✓ Branch 2 taken 3 times.
3 Fw::String fname;
1051 Os::File::Status fileStatus;
1052 Os::FileSystem::Status fileSysStatus;
1053 3 bool success = true;
1054
1055 /* it isn't an error to get another MD PDU, right? */
1056
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) {
1057 /* NOTE: this->m_flags.rx.md_recv always 1 in R1, so this is R2 only */
1058 /* parse the md PDU. this will overwrite the transaction's history, which contains our filename. so let's
1059 * save the filename in a local buffer so it can be used with moveFile upon successful parsing of
1060 * the md PDU */
1061
1/1
✓ Branch 7 taken 3 times.
3 fname = this->m_history->fnames.dst_filename;
1062
1063 // Deserialize Metadata PDU from buffer
1064
1/1
✓ Branch 2 taken 3 times.
3 MetadataPdu md;
1065 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
1066
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());
1067
2/2
✓ Branch 5 taken 3 times.
✓ Branch 8 taken 3 times.
3 sb.setBuffLen(buffer.getSize());
1068
1069
1/1
✓ Branch 2 taken 3 times.
3 Fw::SerializeStatus deserStatus = md.deserializeFrom(sb);
1070
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (deserStatus != Fw::FW_SERIALIZE_OK) {
1071 // Bad metadata PDU
1072 this->m_cfdpManager->log_WARNING_LO_FailMetadataPduDeserialization(this->getChannelId(),
1073 static_cast<I32>(deserStatus));
1074 return;
1075 }
1076
1077 // PDU validation already done during deserialization
1078
1/1
✓ Branch 6 taken 3 times.
3 this->m_engine->recvMd(this, md);
1079
1080 /* successfully obtained md PDU */
1081
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) {
1082 /* EOF was received, so check that md and EOF sizes match */
1083
1/2
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 if (this->m_state_data.receive.r2.eof_size != this->m_fsize) {
1084
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,
1085 1 this->m_history->seq_num, this->m_fsize,
1086 this->m_state_data.receive.r2.eof_size);
1087
1/1
✓ Branch 8 taken 1 times.
1 this->m_cfdpManager->incrementFaultFileSizeMismatch(this->m_chan_num);
1088
1/1
✓ Branch 2 taken 1 times.
1 this->r2SetFinTxnStatus(TxnStatus::TXN_STATUS_FILE_SIZE_ERROR);
1089 1 success = false;
1090 }
1091 }
1092
1093
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 if (success) {
1094 /* close and rename file */
1095
1/1
✓ Branch 6 taken 2 times.
2 this->m_fd.close();
1096
1097
1/1
✓ Branch 11 taken 2 times.
2 fileSysStatus = Os::FileSystem::moveFile(fname.toChar(), this->m_history->fnames.dst_filename.toChar());
1098
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (fileSysStatus != Os::FileSystem::OP_OK) {
1099
2/2
✓ Branch 13 taken 1 times.
✓ Branch 17 taken 1 times.
5 this->m_cfdpManager->log_WARNING_LO_RxFileRenameFailed(
1100 3 this->getClass(), this->m_history->src_eid, this->m_history->seq_num, fname,
1101 1 this->m_history->fnames.dst_filename, fileSysStatus);
1102
1/1
✓ Branch 2 taken 1 times.
1 this->r2SetFinTxnStatus(TxnStatus::TXN_STATUS_FILESTORE_REJECTION);
1103
1/1
✓ Branch 8 taken 1 times.
1 this->m_cfdpManager->incrementFaultFileRename(this->m_chan_num);
1104 1 success = false;
1105 } else {
1106 // File was successfully renamed, open for writing
1107
1/1
✓ Branch 14 taken 1 times.
1 fileStatus = this->m_fd.open(this->m_history->fnames.dst_filename.toChar(), Os::File::OPEN_WRITE);
1108
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (fileStatus != Os::File::OP_OK) {
1109
2/2
✓ Branch 13 taken 1 times.
✓ Branch 17 taken 1 times.
5 this->m_cfdpManager->log_WARNING_LO_RxFileReopenFailed(
1110 3 this->getClass(), this->m_history->src_eid, this->m_history->seq_num,
1111 1 this->m_history->fnames.dst_filename, fileStatus);
1112
1/1
✓ Branch 2 taken 1 times.
1 this->r2SetFinTxnStatus(TxnStatus::TXN_STATUS_FILESTORE_REJECTION);
1113
1/1
✓ Branch 8 taken 1 times.
1 this->m_cfdpManager->incrementFaultFileOpen(this->m_chan_num);
1114 1 success = false;
1115 }
1116 }
1117
1118
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (success) {
1119 this->m_state_data.receive.cached_pos = 0; /* reset psn due to open */
1120 this->m_flags.rx.md_recv = true;
1121 this->m_state_data.receive.r2.acknak_count = 0; /* in case part of NAK */
1122 this->r2Complete(true); /* check for completion now that md is received */
1123 }
1124 }
1125 3 }
1126 3 }
1127
1128 1 void Transaction::rSendInactivityEvent() {
1129
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,
1130 1 this->m_history->seq_num);
1131 1 this->m_cfdpManager->incrementFaultInactivityTimer(this->m_chan_num);
1132 1 }
1133
1134 // ======================================================================
1135 // Dispatch Methods
1136 // ======================================================================
1137
1138 51 void Transaction::rDispatchRecv(const Fw::Buffer& buffer, const RSubstateDispatchTable* dispatch, StateRecvFunc fd_fn) {
1139 StateRecvFunc selected_handler;
1140
1141 51 FW_ASSERT(this->m_state_data.receive.sub_state < RxSubState::RX_SUB_STATE_NUM_STATES,
1142 static_cast<U8>(this->m_state_data.receive.sub_state),
1143 static_cast<U8>(RxSubState::RX_SUB_STATE_NUM_STATES));
1144
1145 51 selected_handler = nullptr;
1146
1147 // Peek at PDU type from buffer
1148 51 Cfdp::PduTypeEnum::T pduType = Cfdp::peekPduType(buffer);
1149
1150 // Special handling for file data PDU
1151
2/2
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 18 times.
51 if (pduType == Cfdp::PduTypeEnum::FILE_DATA) {
1152 /* For file data PDU, use the provided fd_fn */
1153
1/2
✓ Branch 5 taken 33 times.
✗ Branch 6 not taken.
33 if (!TxnStatusIsError(this->m_history->txn_stat)) {
1154 33 selected_handler = fd_fn;
1155 }
1156 } else {
1157 // Not a file-data PDU - parse as a directive PDU to get the directive code.
1158 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
1159
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());
1160
2/2
✓ Branch 5 taken 18 times.
✓ Branch 8 taken 18 times.
18 sb.setBuffLen(buffer.getSize());
1161
1162 18 Cfdp::PduHeader header;
1163
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) {
1164 // Read directive code (first byte after header for directive PDUs)
1165 18 U8 directiveCodeByte;
1166
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) {
1167 18 FileDirective directiveCode = static_cast<FileDirective>(directiveCodeByte);
1168
1169
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 1 times.
18 if (directiveCode < FileDirective::FILE_DIRECTIVE_INVALID_MAX) {
1170 /* The CFDP_R_SubstateDispatchTable_t is only used with file directive PDU */
1171
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) {
1172 34 selected_handler = dispatch->state[static_cast<U32>(this->m_state_data.receive.sub_state)]
1173 17 ->fdirective[static_cast<U32>(directiveCode)];
1174 }
1175 } else {
1176
1/1
✓ Branch 8 taken 1 times.
1 this->m_cfdpManager->incrementRecvSpurious(this->m_chan_num);
1177
2/2
✓ Branch 11 taken 1 times.
✓ Branch 15 taken 1 times.
4 this->m_cfdpManager->log_WARNING_LO_RxInvalidDirectiveCode(
1178 3 this->getClass(), this->m_history->src_eid, this->m_history->seq_num, directiveCodeByte,
1179 1 static_cast<U8>(this->m_state_data.receive.sub_state));
1180 }
1181 }
1182 }
1183 18 }
1184
1185 /*
1186 * NOTE: if no handler is selected, this will drop packets on the floor here.
1187 */
1188
2/2
✓ Branch 0 taken 50 times.
✓ Branch 1 taken 1 times.
51 if (selected_handler != nullptr) {
1189
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 50 times.
50 (this->*selected_handler)(buffer);
1190 } else {
1191 1 this->m_cfdpManager->incrementRecvDropped(this->m_chan_num);
1192 }
1193 51 }
1194
1195 } // namespace Cfdp
1196 } // namespace Ccsds
1197 } // namespace Svc
1198