GCC Code Coverage Report


Directory: ./
File: Svc/Ccsds/CfdpManager/TransactionRx.cpp
Date: 2026-09-03 22:12:29
Exec Total Coverage
Lines: 0 536 0.0%
Functions: 0 29 0.0%
Branches: 0 462 0.0%

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 Transaction::Transaction(Channel* channel, U8 channelId, Engine* engine, CfdpManager* manager)
61 : m_state(TxnState::TXN_STATE_UNDEF),
62 m_txn_class(Cfdp::Class::CLASS_1),
63 m_history(nullptr),
64 m_chunks(nullptr),
65 m_inactivity_timer(),
66 m_ack_timer(),
67 m_fsize(0),
68 m_foffs(0),
69 m_fd(),
70 m_crc(),
71 m_keep(Cfdp::Keep::KEEP),
72 m_chan_num(channelId), // Initialize from parameter
73 m_priority(0),
74 m_initType(TransactionInitType::INIT_BY_COMMAND),
75 m_cl_node{},
76 m_pb(nullptr),
77 m_state_data{},
78 m_flags{},
79 m_cfdpManager(manager), // Initialize from parameter
80 m_chan(channel), // Initialize from parameter
81 m_engine(engine) // Initialize from parameter
82 {
83 // Fully zero the union storage
84 memset(&this->m_state_data, 0, sizeof(this->m_state_data));
85 memset(&this->m_flags, 0, sizeof(this->m_flags));
86 }
87
88 Transaction::~Transaction() {}
89
90 void Transaction::reset() {
91 // Reset transaction state to default values
92 this->m_state = TxnState::TXN_STATE_UNDEF;
93 this->m_txn_class = Cfdp::Class::CLASS_1;
94 this->m_fsize = 0;
95 this->m_foffs = 0;
96 this->m_keep = Cfdp::Keep::KEEP;
97 this->m_priority = 0;
98 this->m_initType = TransactionInitType::INIT_BY_COMMAND;
99 this->m_crc = CFDP::Checksum(0);
100 this->m_pb = nullptr;
101
102 // Fully zero the union storage
103 memset(&this->m_state_data, 0, sizeof(this->m_state_data));
104 memset(&this->m_flags, 0, sizeof(this->m_flags));
105
106 // Close the file if it is open
107 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 this->m_inactivity_timer.disableTimer();
114 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 }
125
126 // ======================================================================
127 // RX State Machine - Public Methods
128 // ======================================================================
129
130 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 this->rDispatchRecv(buffer, &substate_fns, &Transaction::r1SubstateRecvFileData);
154 }
155
156 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 this->rDispatchRecv(buffer, &substate_fns, &Transaction::r2SubstateRecvFileData);
195 }
196
197 void Transaction::rAckTimerTick() {
198 U8 ack_limit = 0;
199
200 /* note: the ack timer is only ever armed on class 2 */
201 if (this->m_state != TxnState::TXN_STATE_R2 || !this->m_flags.com.ack_timer_armed) {
202 /* nothing to do */
203 return;
204 }
205
206 if (this->m_ack_timer.getStatus() == Timer::Status::RUNNING) {
207 this->m_ack_timer.run();
208 } else {
209 /* ACK timer expired, so check for completion */
210 if (!this->m_flags.rx.complete) {
211 this->r2Complete(true);
212 } else if (this->m_state_data.receive.sub_state == RxSubState::RX_SUB_STATE_CLOSEOUT_SYNC) {
213 /* Increment acknak counter */
214 ++this->m_state_data.receive.r2.acknak_count;
215
216 /* Check limit and handle if needed */
217 ack_limit = this->m_cfdpManager->getAckLimitParam(this->m_chan_num);
218 if (this->m_state_data.receive.r2.acknak_count >= ack_limit) {
219 this->m_cfdpManager->log_WARNING_LO_RxAckLimitReached(this->getClass(), this->m_history->src_eid,
220 this->m_history->seq_num);
221 this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_ACK_LIMIT_NO_FIN);
222 this->m_cfdpManager->incrementFaultAckLimit(this->m_chan_num);
223
224 /* give up on this */
225 this->m_engine->finishTransaction(this, true);
226 this->m_flags.com.ack_timer_armed = false;
227 } else {
228 this->m_flags.rx.send_fin = true;
229 }
230 }
231
232 /* re-arm the timer if it is still pending */
233 if (this->m_flags.com.ack_timer_armed) {
234 /* whether sending FIN or waiting for more filedata, need ACK timer armed */
235 this->m_engine->armAckTimer(this);
236 }
237 }
238 }
239
240 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 if (!this->m_flags.com.inactivity_fired) {
249 if (this->m_inactivity_timer.getStatus() == Timer::Status::RUNNING) {
250 this->m_inactivity_timer.run();
251 // Check if timer just expired naturally (after run())
252 if (this->m_inactivity_timer.getStatus() == Timer::Status::EXPIRED) {
253 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 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 pending_send = true; /* maybe; tbd */
271
272 /* rx maintenance: possibly process send_eof_ack, send_nak or send_fin */
273 if (this->m_flags.rx.send_eof_ack) {
274 sret = this->m_engine->sendAck(this, AckTxnStatus::ACK_TXN_STATUS_ACTIVE,
275 FileDirective::FILE_DIRECTIVE_END_OF_FILE,
276 static_cast<ConditionCode>(this->m_state_data.receive.r2.eof_cc),
277 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 if (sret == Cfdp::Status::SUCCESS) {
281 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 } else if (this->m_flags.rx.send_nak) {
290 if (!this->rSubstateSendNak()) {
291 this->m_flags.rx.send_nak = false; /* will re-enter on error */
292 }
293 } else if (this->m_flags.rx.send_fin) {
294 if (!this->r2SubstateSendFin()) {
295 this->m_flags.rx.send_fin = false; /* will re-enter on error */
296 }
297 } else {
298 /* no pending responses to the sender */
299 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 bool retries_exhausted = false;
309 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 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 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 this->rAckTimerTick();
329 }
330 }
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 void Transaction::rInit() {
343 Os::File::Status status;
344 Fw::String tmpDir;
345 Fw::String dst;
346
347 if (this->m_state == TxnState::TXN_STATE_R2) {
348 if (!this->m_flags.rx.md_recv) {
349 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 dst.format("%s/%" CFDP_PRI_ENTITY_ID ":%" CFDP_PRI_TRANSACTION_SEQ ".tmp", tmpDir.toChar(),
356 this->m_history->src_eid, this->m_history->seq_num);
357
358 this->m_history->fnames.dst_filename = dst;
359
360 this->m_cfdpManager->log_ACTIVITY_LO_RxTempFileCreated(this->getClass(), this->m_history->src_eid,
361 this->m_history->seq_num,
362 this->m_history->fnames.dst_filename);
363 }
364
365 this->m_engine->armAckTimer(this);
366 }
367
368 status = this->m_fd.open(this->m_history->fnames.dst_filename.toChar(), Os::File::OPEN_CREATE, Os::File::OVERWRITE);
369 if (status != Os::File::OP_OK) {
370 this->m_cfdpManager->log_WARNING_LO_RxFileCreateFailed(this->getClass(), this->m_history->src_eid,
371 this->m_history->seq_num,
372 this->m_history->fnames.dst_filename, status);
373 this->m_cfdpManager->incrementFaultFileOpen(this->m_chan_num);
374 if (this->m_state == TxnState::TXN_STATE_R2) {
375 this->r2SetFinTxnStatus(TxnStatus::TXN_STATUS_FILESTORE_REJECTION);
376 } else {
377 this->r1Reset();
378 }
379 } else {
380 this->m_state_data.receive.sub_state = RxSubState::RX_SUB_STATE_FILEDATA;
381 }
382 }
383
384 void Transaction::r2SetFinTxnStatus(TxnStatus txn_stat) {
385 this->m_engine->setTxnStatus(this, txn_stat);
386 this->m_flags.rx.send_fin = true;
387 }
388
389 void Transaction::r1Reset() {
390 this->m_engine->finishTransaction(this, true);
391 }
392
393 void Transaction::r2Reset() {
394 if ((this->m_state_data.receive.sub_state == RxSubState::RX_SUB_STATE_CLOSEOUT_SYNC) ||
395 (static_cast<U8>(this->m_state_data.receive.r2.eof_cc) !=
396 static_cast<U8>(ConditionCode::CONDITION_CODE_NO_ERROR)) ||
397 TxnStatusIsError(this->m_history->txn_stat) || this->m_flags.com.canceled) {
398 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 }
404
405 Status::T Transaction::rCheckCrc(U32 expected_crc) {
406 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 crc_result = this->m_crc.getValue();
414 if (crc_result != expected_crc) {
415 this->m_cfdpManager->log_WARNING_LO_RxCrcMismatch(this->getClass(), this->m_history->src_eid,
416 this->m_history->seq_num, expected_crc, crc_result);
417 this->m_cfdpManager->incrementFaultCrcMismatch(this->m_chan_num);
418 ret = Cfdp::Status::ERROR;
419 }
420
421 return ret;
422 }
423
424 void Transaction::r2Complete(I32 ok_to_send_nak) {
425 U32 ret;
426 bool send_nak = false;
427 bool send_fin = false;
428 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 if (!TxnStatusIsError(this->m_history->txn_stat)) {
433 /* first, check if md is received. If not, send specialized NAK */
434 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 ret = this->m_chunks->chunks.computeGaps(1, this->m_fsize, 0, nullptr, nullptr);
439
440 if (ret) {
441 /* there is at least 1 gap, so send a NAK */
442 send_nak = true;
443 } 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 send_fin = true;
446 }
447 }
448
449 if (send_nak && ok_to_send_nak) {
450 /* Increment the acknak counter */
451 ++this->m_state_data.receive.r2.acknak_count;
452
453 /* Check limit and handle if needed */
454 nack_limit = this->m_cfdpManager->getNackLimitParam(this->m_chan_num);
455 if (this->m_state_data.receive.r2.acknak_count >= nack_limit) {
456 this->m_cfdpManager->log_WARNING_LO_RxNakLimitReached(this->getClass(), this->m_history->src_eid,
457 this->m_history->seq_num);
458 send_fin = true;
459 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 this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_NAK_LIMIT_REACHED);
462 this->m_state_data.receive.r2.acknak_count = 0; /* reset for fin/ack */
463 } else {
464 this->m_flags.rx.send_nak = true;
465 }
466 }
467
468 if (send_fin) {
469 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 this->r2SetFinTxnStatus(TxnStatus::TXN_STATUS_NO_ERROR);
474 }
475
476 /* always go to RxSubState::RX_SUB_STATE_FILEDATA, and let tick change state */
477 this->m_state_data.receive.sub_state = RxSubState::RX_SUB_STATE_FILEDATA;
478 }
479 }
480
481 // ======================================================================
482 // RX State Machine - Private Helper Methods
483 // ======================================================================
484
485 Status::T Transaction::rProcessFd(const Fw::Buffer& buffer) {
486 Status::T ret = Cfdp::Status::SUCCESS;
487
488 /* this function is only entered for data PDUs */
489 // Deserialize FileData PDU from buffer
490 FileDataPdu fd;
491 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
492 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
493 sb.setBuffLen(buffer.getSize());
494
495 Fw::SerializeStatus deserStatus = fd.deserializeFrom(sb);
496 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 FileSize offset = fd.getOffset();
509 U16 dataSize = fd.getDataSize();
510 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 if (ret == Cfdp::Status::SUCCESS) {
515 const FileSize bound = this->m_flags.rx.md_recv ? this->m_fsize : std::numeric_limits<FileSize>::max();
516 if ((offset > bound) || ((bound - offset) < dataSize)) {
517 this->m_cfdpManager->log_WARNING_LO_RxFileDataOutOfBounds(
518 this->getClass(), this->m_history->src_eid, this->m_history->seq_num, offset, dataSize, bound);
519 this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_FILE_SIZE_ERROR);
520 this->m_cfdpManager->incrementFaultFileSizeMismatch(this->m_chan_num);
521 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 if ((ret == Cfdp::Status::SUCCESS) && (dataSize == 0)) {
530 return ret;
531 }
532
533 // Seek to file offset if needed
534 if (ret == Cfdp::Status::SUCCESS) {
535 if (this->m_state_data.receive.cached_pos != offset) {
536 Os::File::Status status = this->m_fd.seek(offset, Os::File::SeekType::ABSOLUTE);
537 if (status != Os::File::OP_OK) {
538 this->m_cfdpManager->log_WARNING_LO_RxSeekFailed(this->getClass(), this->m_history->src_eid,
539 this->m_history->seq_num, offset, status);
540 this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_FILE_SIZE_ERROR);
541 this->m_cfdpManager->incrementFaultFileSeek(this->m_chan_num);
542 ret = Cfdp::Status::ERROR;
543 }
544 }
545 }
546
547 // Write file data
548 if (ret == Cfdp::Status::SUCCESS) {
549 FwSizeType write_size = dataSize;
550 Os::File::Status status = this->m_fd.write(dataPtr, write_size, Os::File::WaitType::WAIT);
551 if (status != Os::File::OP_OK) {
552 this->m_cfdpManager->log_WARNING_LO_RxWriteFailed(this->getClass(), this->m_history->src_eid,
553 this->m_history->seq_num, dataSize,
554 static_cast<I32>(write_size));
555 this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_FILESTORE_REJECTION);
556 this->m_cfdpManager->incrementFaultFileWrite(this->m_chan_num);
557 ret = Cfdp::Status::ERROR;
558 } else {
559 this->m_state_data.receive.cached_pos = static_cast<FileSize>(dataSize) + offset;
560 this->m_cfdpManager->addRecvFileDataBytes(this->m_chan_num, dataSize);
561 }
562 }
563
564 return ret;
565 }
566
567 Status::T Transaction::rSubstateRecvEof(const Fw::Buffer& buffer) {
568 Status::T ret = Cfdp::Status::SUCCESS;
569
570 // Deserialize EOF PDU from buffer
571 EofPdu eof;
572 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
573 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
574 sb.setBuffLen(buffer.getSize());
575
576 Fw::SerializeStatus deserStatus = eof.deserializeFrom(sb);
577 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 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 if (!this->m_engine->recvEof(this, eof)) {
590 /* this function is only entered for PDUs identified as EOF type */
591 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 if (this->m_flags.rx.md_recv && (cc == ConditionCode::CONDITION_CODE_NO_ERROR) &&
596 (eof.getFileSize() != this->m_fsize)) {
597 this->m_cfdpManager->log_WARNING_LO_RxFileSizeMismatch(this->getClass(), this->m_history->src_eid,
598 this->m_history->seq_num, this->m_fsize,
599 eof.getFileSize());
600 this->m_cfdpManager->incrementFaultFileSizeMismatch(this->m_chan_num);
601 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 if (cc != ConditionCode::CONDITION_CODE_NO_ERROR) {
606 /* Set transaction status from condition code to prevent completion event */
607 this->m_engine->setTxnStatus(this, static_cast<TxnStatus>(static_cast<I32>(cc)));
608
609 if (cc == ConditionCode::CONDITION_CODE_CANCEL_REQUEST_RECEIVED) {
610 /* Increment receive EOF cancellation counter (normal operation) */
611 this->m_cfdpManager->incrementRecvEofCanceled(this->m_chan_num);
612
613 this->m_cfdpManager->log_ACTIVITY_HI_RxEofCancelReceived(this->getClass(), this->m_history->src_eid,
614 this->m_history->seq_num);
615 } else {
616 /* Increment RX EOF error counter (protocol error) */
617 this->m_cfdpManager->incrementFaultRxEofError(this->m_chan_num);
618
619 this->m_cfdpManager->log_WARNING_LO_RxEofWithError(this->getClass(), this->m_history->src_eid,
620 this->m_history->seq_num, static_cast<U8>(cc));
621 }
622 }
623 }
624 }
625
626 return ret;
627 }
628
629 void Transaction::r1SubstateRecvEof(const Fw::Buffer& buffer) {
630 // Deserialize EOF PDU from buffer
631 EofPdu eof;
632 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
633 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
634 sb.setBuffLen(buffer.getSize());
635
636 Fw::SerializeStatus deserStatus = eof.deserializeFrom(sb);
637 if (deserStatus != Fw::FW_SERIALIZE_OK) {
638 // Bad EOF, reset transaction
639 this->m_cfdpManager->log_WARNING_LO_FailEofPduDeserialization(this->getChannelId(),
640 static_cast<I32>(deserStatus));
641 this->r1Reset();
642 return;
643 }
644
645 Status::T ret = this->rSubstateRecvEof(buffer);
646 U32 crc = eof.getChecksum();
647 ConditionCode cc = eof.getConditionCode();
648
649 if (ret == Cfdp::Status::SUCCESS) {
650 /* Only check CRC if no error condition code */
651 if (cc == ConditionCode::CONDITION_CODE_NO_ERROR) {
652 /* Verify CRC */
653 if (this->rCheckCrc(crc) == Cfdp::Status::SUCCESS) {
654 /* successfully processed the file */
655 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 this->r1Reset();
664 }
665
666 void Transaction::r2SubstateRecvEof(const Fw::Buffer& buffer) {
667 Status::T ret;
668
669 if (!this->m_flags.rx.eof_recv) {
670 // Deserialize EOF PDU from buffer
671 EofPdu eof;
672 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
673 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
674 sb.setBuffLen(buffer.getSize());
675
676 Fw::SerializeStatus deserStatus = eof.deserializeFrom(sb);
677 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 ret = this->rSubstateRecvEof(buffer);
686
687 /* did receiving EOF succeed? */
688 if (ret == Cfdp::Status::SUCCESS) {
689 this->m_flags.rx.eof_recv = true;
690
691 /* need to remember the EOF CRC for later */
692 this->m_state_data.receive.r2.eof_crc = eof.getChecksum();
693 this->m_state_data.receive.r2.eof_size = eof.getFileSize();
694
695 /* always ACK the EOF, even if we're not done */
696 this->m_state_data.receive.r2.eof_cc = static_cast<U8>(eof.getConditionCode());
697 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 if (static_cast<U8>(this->m_state_data.receive.r2.eof_cc) ==
701 static_cast<U8>(ConditionCode::CONDITION_CODE_NO_ERROR)) {
702 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 }
719 }
720
721 void Transaction::r1SubstateRecvFileData(const Fw::Buffer& buffer) {
722 Status::T ret;
723
724 // Deserialize FileData PDU from buffer
725 FileDataPdu fd;
726 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
727 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
728 sb.setBuffLen(buffer.getSize());
729
730 Fw::SerializeStatus deserStatus = fd.deserializeFrom(sb);
731 if (deserStatus != Fw::FW_SERIALIZE_OK) {
732 // Bad file data PDU, reset transaction
733 this->m_cfdpManager->log_WARNING_LO_FailFileDataPduDeserialization(this->getChannelId(),
734 static_cast<I32>(deserStatus));
735 this->r1Reset();
736 return;
737 }
738
739 /* got file data PDU? */
740 ret = this->m_engine->recvFd(this, fd);
741 if (ret == Cfdp::Status::SUCCESS) {
742 ret = this->rProcessFd(buffer);
743 }
744
745 if (ret == Cfdp::Status::SUCCESS) {
746 /* class 1 digests CRC */
747 this->m_crc.update(fd.getData(), fd.getOffset(), static_cast<U32>(fd.getDataSize()));
748 } else {
749 /* Reset transaction on failure */
750 this->r1Reset();
751 }
752 }
753
754 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 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 FileDataPdu fd;
767 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
768 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
769 sb.setBuffLen(buffer.getSize());
770
771 Fw::SerializeStatus deserStatus = fd.deserializeFrom(sb);
772 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 ret = this->m_engine->recvFd(this, fd);
782 if (ret == Cfdp::Status::SUCCESS) {
783 ret = this->rProcessFd(buffer);
784 }
785
786 if (ret == Cfdp::Status::SUCCESS) {
787 /* class 2 does CRC at FIN, but track gaps */
788 this->m_chunks->chunks.add(fd.getOffset(), static_cast<FileSize>(fd.getDataSize()));
789
790 if (this->m_flags.rx.fd_nak_sent) {
791 this->r2Complete(false); /* once nak-retransmit received, start checking for completion at each fd */
792 }
793
794 if (!this->m_flags.rx.complete) {
795 this->m_engine->armAckTimer(this); /* re-arm ACK timer, since we got data */
796 }
797
798 this->m_state_data.receive.r2.acknak_count = 0;
799 } else {
800 /* Reset transaction on failure */
801 this->r2Reset();
802 }
803 }
804
805 void Transaction::r2GapCompute(const Chunk* chunk, NakPdu& nak) {
806 FW_ASSERT(chunk->size > 0, static_cast<FwAssertArgType>(chunk->size));
807
808 // Calculate segment offsets relative to scope start
809 FileSize offsetStart = chunk->offset - nak.getScopeStart();
810 FileSize offsetEnd = offsetStart + chunk->size;
811
812 // Add segment to NAK PDU (returns false if array is full)
813 nak.addSegment(offsetStart, offsetEnd);
814 }
815
816 void Transaction::r2GapComputeWrapper(const Chunk* chunk, void* opaque) {
817 struct GapComputeContext {
818 Transaction* txn;
819 NakPdu* nak;
820 };
821 GapComputeContext* ctx = static_cast<GapComputeContext*>(opaque);
822 ctx->txn->r2GapCompute(chunk, *ctx->nak);
823 }
824
825 Status::T Transaction::rSubstateSendNak() {
826 Status::T status = Cfdp::Status::SUCCESS;
827
828 // Create and initialize NAK PDU
829 NakPdu nakPdu;
830 Cfdp::PduDirection direction = PduDirection::DIRECTION_TOWARD_SENDER;
831
832 if (this->m_flags.rx.md_recv) {
833 // We have metadata, so send NAK with file data gaps
834 nakPdu.initialize(direction,
835 this->getClass(), // transmission mode
836 this->m_history->peer_eid, // source EID (receiver)
837 this->m_history->seq_num, // transaction sequence number
838 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 U32 chunkCount = this->m_chunks->chunks.getCount();
845 U32 maxChunks = this->m_chunks->chunks.getMaxChunks();
846 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 } gapCtx = {this, &nakPdu};
853
854 U32 gapCount = this->m_chunks->chunks.computeGaps(static_cast<ChunkIdx>(gapLimit), this->m_fsize, 0,
855 &Transaction::r2GapComputeWrapper, &gapCtx);
856
857 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 status = this->m_engine->sendNak(this, nakPdu);
864 if (status == Cfdp::Status::SUCCESS) {
865 this->m_flags.rx.fd_nak_sent = true;
866 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 return status;
888 }
889
890 Status::T Transaction::r2CalcCrcChunk() {
891 U8 buf[R2CrcChunkSize];
892 FileSize count_bytes;
893 FileSize want_offs_size;
894 FwSizeType read_size;
895 Os::File::Status fileStatus;
896 Status::T ret = Cfdp::Status::SUCCESS;
897 FileSize rx_crc_calc_bytes_per_cycle = 0;
898
899 memset(buf, 0, sizeof(buf));
900
901 count_bytes = 0;
902
903 // Open file for CRC calculation if needed
904 if (ret == Cfdp::Status::SUCCESS) {
905 if (this->m_state_data.receive.r2.rx_crc_calc_bytes == 0) {
906 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 if (this->m_fd.isOpen()) {
911 this->m_fd.close();
912 }
913
914 fileStatus = this->m_fd.open(this->m_history->fnames.dst_filename.toChar(), Os::File::OPEN_READ);
915 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 this->m_state_data.receive.cached_pos = 0;
921 }
922 }
923 }
924
925 // Process file in chunks
926 if (ret == Cfdp::Status::SUCCESS) {
927 rx_crc_calc_bytes_per_cycle = this->m_cfdpManager->getRxCrcCalcBytesPerCycleParam();
928
929 while ((ret == Cfdp::Status::SUCCESS) && (count_bytes < rx_crc_calc_bytes_per_cycle) &&
930 (this->m_state_data.receive.r2.rx_crc_calc_bytes < this->m_fsize)) {
931 want_offs_size = this->m_state_data.receive.r2.rx_crc_calc_bytes + static_cast<FileSize>(sizeof(buf));
932
933 if (want_offs_size > this->m_fsize) {
934 read_size = this->m_fsize - this->m_state_data.receive.r2.rx_crc_calc_bytes;
935 } else {
936 read_size = sizeof(buf);
937 }
938
939 if (this->m_state_data.receive.cached_pos != this->m_state_data.receive.r2.rx_crc_calc_bytes) {
940 fileStatus =
941 this->m_fd.seek(this->m_state_data.receive.r2.rx_crc_calc_bytes, Os::File::SeekType::ABSOLUTE);
942 if (fileStatus != Os::File::OP_OK) {
943 this->m_cfdpManager->log_WARNING_LO_RxSeekCrcFailed(
944 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 this->m_cfdpManager->incrementFaultFileSeek(this->m_chan_num);
948 ret = Cfdp::Status::ERROR;
949 }
950 }
951
952 if (ret == Cfdp::Status::SUCCESS) {
953 FwSizeType expected_read_size = read_size;
954 fileStatus = this->m_fd.read(buf, read_size, Os::File::WaitType::WAIT);
955 if (fileStatus != Os::File::OP_OK) {
956 this->m_cfdpManager->log_WARNING_LO_RxReadCrcFailed(
957 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 this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_FILE_SIZE_ERROR);
960 this->m_cfdpManager->incrementFaultFileRead(this->m_chan_num);
961 ret = Cfdp::Status::ERROR;
962 } else {
963 this->m_crc.update(buf, this->m_state_data.receive.r2.rx_crc_calc_bytes,
964 static_cast<U32>(read_size));
965 this->m_state_data.receive.r2.rx_crc_calc_bytes += static_cast<FileSize>(read_size);
966 this->m_state_data.receive.cached_pos = this->m_state_data.receive.r2.rx_crc_calc_bytes;
967 count_bytes += static_cast<FileSize>(read_size);
968
969 // Reset inactivity timer to indicate transaction is actively processing
970 this->m_engine->armInactTimer(this);
971 }
972 }
973 }
974 }
975
976 // Check final CRC if all bytes processed
977 if (ret == Cfdp::Status::SUCCESS) {
978 if (this->m_state_data.receive.r2.rx_crc_calc_bytes == this->m_fsize) {
979 /* all bytes calculated, so now check */
980 if (this->rCheckCrc(this->m_state_data.receive.r2.eof_crc) == Cfdp::Status::SUCCESS) {
981 /* CRC matched! We are happy */
982 this->m_keep = Cfdp::Keep::KEEP; /* save the file */
983
984 /* set FIN PDU status */
985 this->m_state_data.receive.r2.dc = FinDeliveryCode::FIN_DELIVERY_CODE_COMPLETE;
986 this->m_state_data.receive.r2.fs = FinFileStatus::FIN_FILE_STATUS_RETAINED;
987 } else {
988 this->r2SetFinTxnStatus(TxnStatus::TXN_STATUS_FILE_CHECKSUM_FAILURE);
989 }
990
991 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 return ret;
999 }
1000
1001 Status::T Transaction::r2SubstateSendFin() {
1002 Status::T sret;
1003 Status::T ret = Cfdp::Status::SUCCESS;
1004
1005 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 if (this->r2CalcCrcChunk()) {
1008 ret = Cfdp::Status::ERROR; /* signal to caller to re-enter next tick */
1009 }
1010 }
1011
1012 if (ret != Cfdp::Status::ERROR) {
1013 sret = this->m_engine->sendFin(this, this->m_state_data.receive.r2.dc, this->m_state_data.receive.r2.fs,
1014 static_cast<ConditionCode>(TxnStatusToConditionCode(this->m_history->txn_stat)));
1015
1016 /* Serialization error already logged in serializeAndSendPdu if ERROR returned */
1017 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 if (sret != Cfdp::Status::SUCCESS) {
1020 ret = Cfdp::Status::ERROR;
1021 }
1022 }
1023
1024 /* if no message, then try again next time */
1025 return ret;
1026 }
1027
1028 void Transaction::r2RecvFinAck(const Fw::Buffer& buffer) {
1029 // Deserialize ACK PDU from buffer
1030 AckPdu ack;
1031 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
1032 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
1033 sb.setBuffLen(buffer.getSize());
1034
1035 Fw::SerializeStatus deserStatus = ack.deserializeFrom(sb);
1036 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 this->r2Reset();
1047 }
1048
1049 void Transaction::r2RecvMd(const Fw::Buffer& buffer) {
1050 Fw::String fname;
1051 Os::File::Status fileStatus;
1052 Os::FileSystem::Status fileSysStatus;
1053 bool success = true;
1054
1055 /* it isn't an error to get another MD PDU, right? */
1056 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 fname = this->m_history->fnames.dst_filename;
1062
1063 // Deserialize Metadata PDU from buffer
1064 MetadataPdu md;
1065 // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only)
1066 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
1067 sb.setBuffLen(buffer.getSize());
1068
1069 Fw::SerializeStatus deserStatus = md.deserializeFrom(sb);
1070 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 this->m_engine->recvMd(this, md);
1079
1080 /* successfully obtained md PDU */
1081 if (this->m_flags.rx.eof_recv) {
1082 /* EOF was received, so check that md and EOF sizes match */
1083 if (this->m_state_data.receive.r2.eof_size != this->m_fsize) {
1084 this->m_cfdpManager->log_WARNING_LO_RxEofMdSizeMismatch(this->getClass(), this->m_history->src_eid,
1085 this->m_history->seq_num, this->m_fsize,
1086 this->m_state_data.receive.r2.eof_size);
1087 this->m_cfdpManager->incrementFaultFileSizeMismatch(this->m_chan_num);
1088 this->r2SetFinTxnStatus(TxnStatus::TXN_STATUS_FILE_SIZE_ERROR);
1089 success = false;
1090 }
1091 }
1092
1093 if (success) {
1094 /* close and rename file */
1095 this->m_fd.close();
1096
1097 fileSysStatus = Os::FileSystem::moveFile(fname.toChar(), this->m_history->fnames.dst_filename.toChar());
1098 if (fileSysStatus != Os::FileSystem::OP_OK) {
1099 this->m_cfdpManager->log_WARNING_LO_RxFileRenameFailed(
1100 this->getClass(), this->m_history->src_eid, this->m_history->seq_num, fname,
1101 this->m_history->fnames.dst_filename, fileSysStatus);
1102 this->r2SetFinTxnStatus(TxnStatus::TXN_STATUS_FILESTORE_REJECTION);
1103 this->m_cfdpManager->incrementFaultFileRename(this->m_chan_num);
1104 success = false;
1105 } else {
1106 // File was successfully renamed, open for writing
1107 fileStatus = this->m_fd.open(this->m_history->fnames.dst_filename.toChar(), Os::File::OPEN_WRITE);
1108 if (fileStatus != Os::File::OP_OK) {
1109 this->m_cfdpManager->log_WARNING_LO_RxFileReopenFailed(
1110 this->getClass(), this->m_history->src_eid, this->m_history->seq_num,
1111 this->m_history->fnames.dst_filename, fileStatus);
1112 this->r2SetFinTxnStatus(TxnStatus::TXN_STATUS_FILESTORE_REJECTION);
1113 this->m_cfdpManager->incrementFaultFileOpen(this->m_chan_num);
1114 success = false;
1115 }
1116 }
1117
1118 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 }
1126 }
1127
1128 void Transaction::rSendInactivityEvent() {
1129 this->m_cfdpManager->log_WARNING_LO_RxInactivityTimeout(this->getClass(), this->m_history->src_eid,
1130 this->m_history->seq_num);
1131 this->m_cfdpManager->incrementFaultInactivityTimer(this->m_chan_num);
1132 }
1133
1134 // ======================================================================
1135 // Dispatch Methods
1136 // ======================================================================
1137
1138 void Transaction::rDispatchRecv(const Fw::Buffer& buffer, const RSubstateDispatchTable* dispatch, StateRecvFunc fd_fn) {
1139 StateRecvFunc selected_handler;
1140
1141 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 selected_handler = nullptr;
1146
1147 // Peek at PDU type from buffer
1148 Cfdp::PduTypeEnum::T pduType = Cfdp::peekPduType(buffer);
1149
1150 // Special handling for file data PDU
1151 if (pduType == Cfdp::PduTypeEnum::FILE_DATA) {
1152 /* For file data PDU, use the provided fd_fn */
1153 if (!TxnStatusIsError(this->m_history->txn_stat)) {
1154 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 Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize());
1160 sb.setBuffLen(buffer.getSize());
1161
1162 Cfdp::PduHeader header;
1163 if (header.fromSerialBuffer(sb) == Fw::FW_SERIALIZE_OK) {
1164 // Read directive code (first byte after header for directive PDUs)
1165 U8 directiveCodeByte;
1166 if (sb.deserializeTo(directiveCodeByte) == Fw::FW_SERIALIZE_OK) {
1167 FileDirective directiveCode = static_cast<FileDirective>(directiveCodeByte);
1168
1169 if (directiveCode < FileDirective::FILE_DIRECTIVE_INVALID_MAX) {
1170 /* The CFDP_R_SubstateDispatchTable_t is only used with file directive PDU */
1171 if (dispatch->state[static_cast<U32>(this->m_state_data.receive.sub_state)] != nullptr) {
1172 selected_handler = dispatch->state[static_cast<U32>(this->m_state_data.receive.sub_state)]
1173 ->fdirective[static_cast<U32>(directiveCode)];
1174 }
1175 } else {
1176 this->m_cfdpManager->incrementRecvSpurious(this->m_chan_num);
1177 this->m_cfdpManager->log_WARNING_LO_RxInvalidDirectiveCode(
1178 this->getClass(), this->m_history->src_eid, this->m_history->seq_num, directiveCodeByte,
1179 static_cast<U8>(this->m_state_data.receive.sub_state));
1180 }
1181 }
1182 }
1183 }
1184
1185 /*
1186 * NOTE: if no handler is selected, this will drop packets on the floor here.
1187 */
1188 if (selected_handler != nullptr) {
1189 (this->*selected_handler)(buffer);
1190 } else {
1191 this->m_cfdpManager->incrementRecvDropped(this->m_chan_num);
1192 }
1193 }
1194
1195 } // namespace Cfdp
1196 } // namespace Ccsds
1197 } // namespace Svc
1198