| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // ====================================================================== | ||
| 2 | // \title TransactionTx.cpp | ||
| 3 | // \brief cpp file for CFDP TX Transaction state machine | ||
| 4 | // | ||
| 5 | // This file is a port of TX 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_s.c (send-file transaction state handling routines) | ||
| 9 | // - cf_cfdp_dispatch.c (TX state machine dispatch functions) | ||
| 10 | // | ||
| 11 | // This file contains various state handling routines for | ||
| 12 | // transactions which are sending a file, as well as dispatch | ||
| 13 | // functions for TX 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 <Svc/Ccsds/CfdpManager/CfdpManager.hpp> | ||
| 41 | #include <Svc/Ccsds/CfdpManager/Channel.hpp> | ||
| 42 | #include <Svc/Ccsds/CfdpManager/Chunk.hpp> | ||
| 43 | #include <Svc/Ccsds/CfdpManager/Engine.hpp> | ||
| 44 | #include <Svc/Ccsds/CfdpManager/Timer.hpp> | ||
| 45 | #include <Svc/Ccsds/CfdpManager/Transaction.hpp> | ||
| 46 | #include <Svc/Ccsds/CfdpManager/Utils.hpp> | ||
| 47 | |||
| 48 | namespace Svc { | ||
| 49 | namespace Ccsds { | ||
| 50 | namespace Cfdp { | ||
| 51 | |||
| 52 | // ====================================================================== | ||
| 53 | // TX State Machine - Private Helper (anonymous namespace) | ||
| 54 | // ====================================================================== | ||
| 55 | |||
| 56 | namespace { | ||
| 57 | |||
| 58 | // Helper to build dispatch tables | ||
| 59 | 3 | FileDirectiveDispatchTable makeFileDirectiveTable(StateRecvFunc fin, StateRecvFunc ack, StateRecvFunc nak) { | |
| 60 | 3 | FileDirectiveDispatchTable table = {}; | |
| 61 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | memset(&table, 0, sizeof(table)); |
| 62 | |||
| 63 | 3 | table.fdirective[static_cast<U32>(FileDirective::FILE_DIRECTIVE_FIN)] = fin; | |
| 64 | 3 | table.fdirective[static_cast<U32>(FileDirective::FILE_DIRECTIVE_ACK)] = ack; | |
| 65 | 3 | table.fdirective[static_cast<U32>(FileDirective::FILE_DIRECTIVE_NAK)] = nak; | |
| 66 | |||
| 67 | 3 | return table; | |
| 68 | } | ||
| 69 | |||
| 70 | } // anonymous namespace | ||
| 71 | |||
| 72 | // ====================================================================== | ||
| 73 | // TX State Machine - Public Methods | ||
| 74 | // ====================================================================== | ||
| 75 | |||
| 76 | ✗ | void Transaction::s1Recv(const Fw::Buffer& buffer) { | |
| 77 | // s1 doesn't need to receive anything | ||
| 78 | static const SSubstateRecvDispatchTable substate_fns = {{nullptr}}; | ||
| 79 | ✗ | this->sDispatchRecv(buffer, &substate_fns); | |
| 80 | ✗ | } | |
| 81 | |||
| 82 | 14 | void Transaction::s2Recv(const Fw::Buffer& buffer) { | |
| 83 | static const FileDirectiveDispatchTable s2_meta = | ||
| 84 |
3/4✓ Branch 0 taken 1 times.
✓ Branch 1 taken 13 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
|
14 | makeFileDirectiveTable(&Transaction::s2EarlyFin, nullptr, nullptr); |
| 85 | |||
| 86 | static const FileDirectiveDispatchTable s2_fd_or_eof = | ||
| 87 |
3/4✓ Branch 0 taken 1 times.
✓ Branch 1 taken 13 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
|
14 | makeFileDirectiveTable(&Transaction::s2EarlyFin, nullptr, &Transaction::s2Nak); |
| 88 | |||
| 89 | static const FileDirectiveDispatchTable s2_wait_ack = | ||
| 90 |
3/4✓ Branch 0 taken 1 times.
✓ Branch 1 taken 13 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
|
14 | makeFileDirectiveTable(&Transaction::s2Fin, &Transaction::s2EofAck, &Transaction::s2NakArm); |
| 91 | |||
| 92 | static const SSubstateRecvDispatchTable substate_fns = {{ | ||
| 93 | &s2_meta, /* TxSubState::TX_SUB_STATE_METADATA */ | ||
| 94 | &s2_fd_or_eof, /* TxSubState::TX_SUB_STATE_FILEDATA */ | ||
| 95 | &s2_fd_or_eof, /* TxSubState::TX_SUB_STATE_EOF */ | ||
| 96 | &s2_wait_ack /* TxSubState::TX_SUB_STATE_CLOSEOUT_SYNC */ | ||
| 97 | }}; | ||
| 98 | |||
| 99 | 14 | this->sDispatchRecv(buffer, &substate_fns); | |
| 100 | 14 | } | |
| 101 | |||
| 102 | 15 | void Transaction::initTxFile(Class::T cfdp_class, Keep::T keep, U8 chan, U8 priority) { | |
| 103 | 15 | m_chan_num = chan; | |
| 104 | 15 | m_priority = priority; | |
| 105 | 15 | m_keep = keep; | |
| 106 | 15 | m_txn_class = cfdp_class; | |
| 107 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 6 times.
|
15 | m_state = (cfdp_class == Cfdp::Class::CLASS_2) ? TxnState::TXN_STATE_S2 : TxnState::TXN_STATE_S1; |
| 108 | 15 | m_state_data.send.sub_state = TxSubState::TX_SUB_STATE_METADATA; | |
| 109 | 15 | } | |
| 110 | |||
| 111 | 11 | void Transaction::s1Tx() { | |
| 112 | static const SSubstateSendDispatchTable substate_fns = {{ | ||
| 113 | &Transaction::sSubstateSendMetadata, // TxSubState::TX_SUB_STATE_METADATA | ||
| 114 | &Transaction::sSubstateSendFileData, // TxSubState::TX_SUB_STATE_FILEDATA | ||
| 115 | &Transaction::s1SubstateSendEof, // TxSubState::TX_SUB_STATE_EOF | ||
| 116 | nullptr // TxSubState::TX_SUB_STATE_CLOSEOUT_SYNC | ||
| 117 | }}; | ||
| 118 | |||
| 119 | 11 | this->sDispatchTransmit(&substate_fns); | |
| 120 | 11 | } | |
| 121 | |||
| 122 | 47 | void Transaction::s2Tx() { | |
| 123 | static const SSubstateSendDispatchTable substate_fns = {{ | ||
| 124 | &Transaction::sSubstateSendMetadata, // TxSubState::TX_SUB_STATE_METADATA | ||
| 125 | &Transaction::s2SubstateSendFileData, // TxSubState::TX_SUB_STATE_FILEDATA | ||
| 126 | &Transaction::s2SubstateSendEof, // TxSubState::TX_SUB_STATE_EOF | ||
| 127 | nullptr // TxSubState::TX_SUB_STATE_CLOSEOUT_SYNC | ||
| 128 | }}; | ||
| 129 | |||
| 130 | 47 | this->sDispatchTransmit(&substate_fns); | |
| 131 | 47 | } | |
| 132 | |||
| 133 | 56 | void Transaction::sAckTimerTick() { | |
| 134 | 56 | U8 ack_limit = 0; | |
| 135 | |||
| 136 | // note: the ack timer is only ever relevant on class 2 | ||
| 137 |
5/6✓ Branch 1 taken 23 times.
✓ Branch 2 taken 33 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 23 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 22 times.
|
56 | if (this->m_state != TxnState::TXN_STATE_S2 || !this->m_flags.com.ack_timer_armed) { |
| 138 | // nothing to do | ||
| 139 | 34 | return; | |
| 140 | } | ||
| 141 | |||
| 142 |
2/2✓ Branch 4 taken 19 times.
✓ Branch 5 taken 3 times.
|
22 | if (this->m_ack_timer.getStatus() == Timer::Status::RUNNING) { |
| 143 | 19 | this->m_ack_timer.run(); | |
| 144 |
1/2✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | } else if (this->m_state_data.send.sub_state == TxSubState::TX_SUB_STATE_CLOSEOUT_SYNC) { |
| 145 | // Check limit and handle if needed | ||
| 146 | 3 | ack_limit = this->m_cfdpManager->getAckLimitParam(this->m_chan_num); | |
| 147 |
2/2✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2 times.
|
3 | if (this->m_state_data.send.s2.acknak_count >= ack_limit) { |
| 148 |
2/2✓ Branch 15 taken 1 times.
✓ Branch 19 taken 1 times.
|
2 | this->m_cfdpManager->log_WARNING_LO_TxAckLimitReached(this->getClass(), this->m_history->src_eid, |
| 149 | 1 | this->m_history->seq_num); | |
| 150 | 1 | this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_ACK_LIMIT_NO_EOF); | |
| 151 | 1 | this->m_cfdpManager->incrementFaultAckLimit(this->m_chan_num); | |
| 152 | |||
| 153 | // give up on this | ||
| 154 | 1 | this->m_engine->finishTransaction(this, true); | |
| 155 | 1 | this->m_flags.com.ack_timer_armed = false; | |
| 156 | } else { | ||
| 157 | // Increment acknak counter | ||
| 158 | 2 | ++this->m_state_data.send.s2.acknak_count; | |
| 159 | |||
| 160 | // If the peer sent FIN that is an implicit EOF ack, it is not supposed | ||
| 161 | // to send it before EOF unless an error occurs, and either way we do not | ||
| 162 | // re-transmit anything after FIN unless we get another FIN | ||
| 163 |
4/8✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 2 times.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
|
2 | if (!this->m_flags.tx.eof_ack_recv && !this->m_flags.tx.fin_recv) { |
| 164 | 2 | this->m_flags.tx.send_eof = true; | |
| 165 | } else { | ||
| 166 | // no response is pending | ||
| 167 | ✗ | this->m_flags.com.ack_timer_armed = false; | |
| 168 | } | ||
| 169 | } | ||
| 170 | |||
| 171 | // reset the ack timer if still waiting on something | ||
| 172 |
3/4✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 1 times.
|
3 | if (this->m_flags.com.ack_timer_armed) { |
| 173 | 2 | this->m_engine->armAckTimer(this); | |
| 174 | } | ||
| 175 | } else { | ||
| 176 | // if we are not waiting for anything, why is the ack timer armed? | ||
| 177 | ✗ | this->m_flags.com.ack_timer_armed = false; | |
| 178 | } | ||
| 179 | } | ||
| 180 | |||
| 181 | 65 | void Transaction::sTick(I32* cont /* unused */) { | |
| 182 | bool pending_send; | ||
| 183 | |||
| 184 | 65 | pending_send = true; // maybe; tbd, will be reset if not | |
| 185 | |||
| 186 | // at each tick, various timers used by S are checked | ||
| 187 | // first, check inactivity timer | ||
| 188 |
2/4✗ Branch 1 not taken.
✓ Branch 2 taken 65 times.
✓ Branch 3 taken 65 times.
✗ Branch 4 not taken.
|
65 | if (!this->m_flags.com.inactivity_fired) { |
| 189 |
1/2✓ Branch 4 taken 65 times.
✗ Branch 5 not taken.
|
65 | if (this->m_inactivity_timer.getStatus() == Timer::Status::RUNNING) { |
| 190 | 65 | this->m_inactivity_timer.run(); | |
| 191 | // Check if timer just expired naturally (after run()) | ||
| 192 |
2/2✓ Branch 4 taken 12 times.
✓ Branch 5 taken 53 times.
|
65 | if (this->m_inactivity_timer.getStatus() == Timer::Status::EXPIRED) { |
| 193 | 12 | this->m_flags.com.inactivity_fired = true; | |
| 194 | |||
| 195 | // HOLD state is the normal path to recycle transaction objects, not an error | ||
| 196 | // Canceled transactions timing out while waiting for EOF-ACK is also normal | ||
| 197 | // inactivity is abnormal in any other state | ||
| 198 |
3/4✓ Branch 1 taken 1 times.
✓ Branch 2 taken 11 times.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
12 | if (this->m_state != TxnState::TXN_STATE_HOLD && this->m_state == TxnState::TXN_STATE_S2 && |
| 199 |
2/4✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
|
1 | !this->m_flags.com.canceled) { |
| 200 |
2/2✓ Branch 15 taken 1 times.
✓ Branch 19 taken 1 times.
|
2 | this->m_cfdpManager->log_WARNING_LO_TxInactivityTimeout(this->getClass(), this->m_history->src_eid, |
| 201 | 1 | this->m_history->seq_num); | |
| 202 | 1 | this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_INACTIVITY_DETECTED); | |
| 203 | |||
| 204 | 1 | this->m_cfdpManager->incrementFaultInactivityTimer(this->m_chan_num); | |
| 205 | } | ||
| 206 | } | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | // tx maintenance: possibly process send_eof, or send_fin_ack | ||
| 211 | // On ERROR, clear the flag so we do not retry forever. On NO_BUF_AVAIL, leave it set to retry. | ||
| 212 |
3/4✗ Branch 1 not taken.
✓ Branch 2 taken 65 times.
✓ Branch 3 taken 15 times.
✓ Branch 4 taken 50 times.
|
65 | if (this->m_flags.tx.send_eof) { |
| 213 | 15 | Status::T sret = this->sSendEof(); | |
| 214 |
1/2✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
|
15 | if (sret == Cfdp::Status::SUCCESS) { |
| 215 | 15 | this->m_flags.tx.send_eof = false; | |
| 216 | ✗ | } else if (sret == Cfdp::Status::ERROR) { | |
| 217 | ✗ | this->m_flags.tx.send_eof = false; | |
| 218 | ✗ | pending_send = false; | |
| 219 | } | ||
| 220 |
3/4✗ Branch 1 not taken.
✓ Branch 2 taken 50 times.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 44 times.
|
50 | } else if (this->m_flags.tx.send_fin_ack) { |
| 221 | 6 | Status::T sret = this->sSendFinAck(); | |
| 222 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if (sret == Cfdp::Status::SUCCESS) { |
| 223 | 6 | this->m_flags.tx.send_fin_ack = false; | |
| 224 | ✗ | } else if (sret == Cfdp::Status::ERROR) { | |
| 225 | ✗ | this->m_flags.tx.send_fin_ack = false; | |
| 226 | ✗ | pending_send = false; | |
| 227 | } | ||
| 228 | } else { | ||
| 229 | 44 | pending_send = false; | |
| 230 | } | ||
| 231 | |||
| 232 | // if the inactivity timer ran out, then there is no sense | ||
| 233 | // pending for responses for anything. Send out anything | ||
| 234 | // that we need to send (i.e. the EOF) just in case the sender | ||
| 235 | // is still listening to us but do not expect any future ACKs | ||
| 236 | // | ||
| 237 | // Recycle the transaction once the inactivity timer has fired, but never while a send | ||
| 238 | // is still pending (e.g. a throttled FIN-ACK). The send is attempted above before this | ||
| 239 | // check, so if one is still queued we defer recycle a cycle to give it a chance to go | ||
| 240 | // out. Once the send succeeds, pending_send clears, so this cannot strand the transaction. | ||
| 241 | // This covers the HOLD (finished) and S2/CLOSEOUT_SYNC (stuck waiting for a FIN) cases, | ||
| 242 | // which by then have no pending send, without dropping a FIN-ACK that has not yet been | ||
| 243 | // transmitted. | ||
| 244 | // | ||
| 245 | // Bound the deferral so a send that keeps failing on a buffer shortage cannot hold the slot | ||
| 246 | // forever: after a small retry budget, recycle regardless. A late FIN is answered statelessly. | ||
| 247 | 65 | bool retries_exhausted = false; | |
| 248 |
5/6✗ Branch 1 not taken.
✓ Branch 2 taken 65 times.
✓ Branch 3 taken 12 times.
✓ Branch 4 taken 53 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 11 times.
|
65 | if (this->m_flags.com.inactivity_fired && pending_send) { |
| 249 | 1 | if (this->m_flags.com.post_inactivity_send_retries >= | |
| 250 |
1/2✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
|
1 | this->m_cfdpManager->getPostInactivitySendRetriesParam()) { |
| 251 | ✗ | retries_exhausted = true; | |
| 252 | } else { | ||
| 253 | 1 | this->m_flags.com.post_inactivity_send_retries++; | |
| 254 | } | ||
| 255 | } | ||
| 256 |
6/8✗ Branch 1 not taken.
✓ Branch 2 taken 65 times.
✓ Branch 3 taken 12 times.
✓ Branch 4 taken 53 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 11 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
|
65 | bool should_recycle = this->m_flags.com.inactivity_fired && (!pending_send || retries_exhausted); |
| 257 | |||
| 258 |
2/2✓ Branch 0 taken 11 times.
✓ Branch 1 taken 54 times.
|
65 | if (should_recycle) { |
| 259 | // the transaction is now recyclable - this means we will | ||
| 260 | // no longer have a record of this transaction seq. If the sender | ||
| 261 | // wakes up or if the network delivers severely delayed PDUs at | ||
| 262 | // some future point, then they will be seen as spurious. They | ||
| 263 | // will no longer be associable with this transaction at all | ||
| 264 | 11 | this->m_chan->recycleTransaction(this); | |
| 265 | |||
| 266 | // NOTE: this must be the last thing in here. Do not use txn after this | ||
| 267 | } else { | ||
| 268 | // transaction still valid so process the ACK timer, if relevant | ||
| 269 | 54 | this->sAckTimerTick(); | |
| 270 | } | ||
| 271 | 65 | } | |
| 272 | |||
| 273 | 57 | void Transaction::sTickNak(I32* cont) { | |
| 274 | 57 | bool nakProcessed = false; | |
| 275 | Status::T status; | ||
| 276 | |||
| 277 | // Only Class 2 transactions should process NAKs | ||
| 278 |
3/4✗ Branch 1 not taken.
✓ Branch 2 taken 57 times.
✓ Branch 3 taken 42 times.
✓ Branch 4 taken 15 times.
|
57 | if (this->m_txn_class == Cfdp::Class::CLASS_2) { |
| 279 |
1/1✓ Branch 2 taken 42 times.
|
42 | status = this->sCheckAndRespondNak(&nakProcessed); |
| 280 |
4/6✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 42 times.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 38 times.
|
42 | if ((status == Cfdp::Status::SUCCESS) && nakProcessed) { |
| 281 | 4 | *cont = 1; // cause dispatcher to re-enter this scheduler cycle | |
| 282 | } | ||
| 283 | } | ||
| 284 | 57 | } | |
| 285 | |||
| 286 | 1 | void Transaction::sCancel() { | |
| 287 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
|
1 | if (this->m_state_data.send.sub_state < TxSubState::TX_SUB_STATE_EOF) { |
| 288 | // if state has not reached TxSubState::TX_SUB_STATE_EOF, then set it to TxSubState::TX_SUB_STATE_EOF now. | ||
| 289 | ✗ | this->m_state_data.send.sub_state = TxSubState::TX_SUB_STATE_EOF; | |
| 290 | } | ||
| 291 | 1 | } | |
| 292 | |||
| 293 | // ====================================================================== | ||
| 294 | // TX State Machine - Private Helper Methods | ||
| 295 | // ====================================================================== | ||
| 296 | |||
| 297 | 15 | Status::T Transaction::sSendEof() { | |
| 298 | // note the crc is "finalized" regardless of success or failure of the txn | ||
| 299 | // this is OK as we still need to put some value into the EOF | ||
| 300 |
3/4✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 2 times.
|
15 | if (!this->m_flags.com.crc_calc) { |
| 301 | // The F' version does not have an equivalent finalize call as it | ||
| 302 | // - Never stores a partial word internally | ||
| 303 | // - Never needs to "flush" anything | ||
| 304 | // - Always accounts for padding at update time | ||
| 305 | 13 | this->m_flags.com.crc_calc = true; | |
| 306 | } | ||
| 307 | 15 | return this->m_engine->sendEof(this); | |
| 308 | } | ||
| 309 | |||
| 310 | 3 | void Transaction::s1SubstateSendEof() { | |
| 311 | // set the flag, the EOF is sent by the tick handler | ||
| 312 | 3 | this->m_flags.tx.send_eof = true; | |
| 313 | |||
| 314 | // In class 1 this is the end of normal operation | ||
| 315 | // NOTE: this is not always true, as class 1 can request an EOF ack. | ||
| 316 | // In this case we could change state to CLOSEOUT_SYNC instead and wait, | ||
| 317 | // but right now we do not request an EOF ack in S1 | ||
| 318 | 3 | this->m_engine->finishTransaction(this, true); | |
| 319 | 3 | } | |
| 320 | |||
| 321 | 9 | void Transaction::s2SubstateSendEof() { | |
| 322 | // set the flag, the EOF is sent by the tick handler | ||
| 323 | 9 | this->m_flags.tx.send_eof = true; | |
| 324 | |||
| 325 | // wait for remaining responses to close out the state machine | ||
| 326 | 9 | this->m_state_data.send.sub_state = TxSubState::TX_SUB_STATE_CLOSEOUT_SYNC; | |
| 327 | |||
| 328 | // always move the transaction onto the wait queue now | ||
| 329 | 9 | this->m_chan->dequeueTransaction(this); | |
| 330 | 9 | this->m_chan->insertSortPrio(this, QueueId::TXW); | |
| 331 | |||
| 332 | // the ack timer is armed in class 2 only | ||
| 333 | 9 | this->m_engine->armAckTimer(this); | |
| 334 | 9 | } | |
| 335 | |||
| 336 | 36 | Status::T Transaction::sSendFileData(FileSize foffs, FileSize bytes_to_read, U8 calc_crc, FileSize* bytes_processed) { | |
| 337 | 36 | FW_ASSERT(bytes_processed != nullptr); | |
| 338 | 36 | *bytes_processed = 0; | |
| 339 | |||
| 340 | 36 | Status::T status = Cfdp::Status::SUCCESS; | |
| 341 | |||
| 342 | // Local buffer for file data | ||
| 343 | 36 | U8 fileDataBuffer[MaxPduSize]; | |
| 344 | |||
| 345 | // Create File Data PDU | ||
| 346 |
1/1✓ Branch 2 taken 36 times.
|
36 | FileDataPdu fdPdu; |
| 347 | 36 | Cfdp::PduDirection direction = PduDirection::DIRECTION_TOWARD_RECEIVER; | |
| 348 | |||
| 349 | // Calculate maximum data size we can send, accounting for PDU overhead | ||
| 350 |
1/1✓ Branch 2 taken 36 times.
|
36 | U32 maxDataCapacity = fdPdu.getMaxFileDataSize(); |
| 351 | |||
| 352 | // Limited by: bytes_to_read, outgoing_file_chunk_size, and maxDataCapacity | ||
| 353 |
1/1✓ Branch 6 taken 36 times.
|
36 | FileSize outgoing_file_chunk_size = this->m_cfdpManager->getOutgoingFileChunkSizeParam(); |
| 354 | 36 | FileSize max_data_bytes = bytes_to_read; | |
| 355 |
2/2✓ Branch 0 taken 20 times.
✓ Branch 1 taken 16 times.
|
36 | if (max_data_bytes > outgoing_file_chunk_size) { |
| 356 | 20 | max_data_bytes = outgoing_file_chunk_size; | |
| 357 | } | ||
| 358 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
|
36 | if (max_data_bytes > maxDataCapacity) { |
| 359 | ✗ | max_data_bytes = maxDataCapacity; | |
| 360 | } | ||
| 361 | |||
| 362 | // Seek to file offset if needed | ||
| 363 | 36 | FwSizeType actual_bytes = max_data_bytes; | |
| 364 |
1/2✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
|
36 | if (status == Cfdp::Status::SUCCESS) { |
| 365 |
2/2✓ Branch 2 taken 4 times.
✓ Branch 3 taken 32 times.
|
36 | if (this->m_state_data.send.cached_pos != foffs) { |
| 366 |
1/1✓ Branch 6 taken 4 times.
|
4 | Os::File::Status fileStatus = this->m_fd.seek(foffs, Os::File::SeekType::ABSOLUTE); |
| 367 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (fileStatus != Os::File::OP_OK) { |
| 368 | ✗ | status = Cfdp::Status::ERROR; | |
| 369 | } | ||
| 370 | } | ||
| 371 | } | ||
| 372 | |||
| 373 | // Read file data | ||
| 374 |
1/2✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
|
36 | if (status == Cfdp::Status::SUCCESS) { |
| 375 |
1/1✓ Branch 6 taken 36 times.
|
36 | Os::File::Status fileStatus = this->m_fd.read(fileDataBuffer, actual_bytes, Os::File::WaitType::WAIT); |
| 376 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
|
36 | if (fileStatus != Os::File::OP_OK) { |
| 377 | ✗ | status = Cfdp::Status::ERROR; | |
| 378 | } | ||
| 379 | } | ||
| 380 | |||
| 381 | // Initialize and send PDU | ||
| 382 |
1/2✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
|
36 | if (status == Cfdp::Status::SUCCESS) { |
| 383 | // File has been read successfully, update cached_pos to reflect new file position | ||
| 384 | // This MUST be done before attempting to send, so if send fails (throttle/error), | ||
| 385 | // we don't try to read the same data again on next cycle | ||
| 386 | 36 | this->m_state_data.send.cached_pos += static_cast<FileSize>(actual_bytes); | |
| 387 | |||
| 388 |
2/2✓ Branch 4 taken 36 times.
✓ Branch 8 taken 36 times.
|
180 | fdPdu.initialize(direction, |
| 389 | 36 | this->getClass(), // transmission mode | |
| 390 | 36 | this->m_cfdpManager->getLocalEidParam(), // source EID | |
| 391 | 36 | this->m_history->seq_num, // transaction sequence number | |
| 392 | 36 | this->m_history->peer_eid, // destination EID | |
| 393 | foffs, // file offset | ||
| 394 | static_cast<U16>(actual_bytes), // data size | ||
| 395 | fileDataBuffer // data pointer | ||
| 396 | ); | ||
| 397 | |||
| 398 |
1/1✓ Branch 6 taken 36 times.
|
36 | status = this->m_engine->sendFd(this, fdPdu); |
| 399 | } | ||
| 400 | |||
| 401 | // Update CRC and bytes_processed | ||
| 402 |
1/2✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
|
36 | if (status == Cfdp::Status::SUCCESS) { |
| 403 | 36 | FW_ASSERT((foffs + actual_bytes) <= this->m_fsize, static_cast<FwAssertArgType>(foffs), | |
| 404 | static_cast<FwAssertArgType>(actual_bytes), static_cast<FwAssertArgType>(this->m_fsize)); | ||
| 405 | |||
| 406 |
2/2✓ Branch 0 taken 32 times.
✓ Branch 1 taken 4 times.
|
36 | if (calc_crc) { |
| 407 |
1/1✓ Branch 4 taken 32 times.
|
32 | this->m_crc.update(fileDataBuffer, foffs, static_cast<U32>(actual_bytes)); |
| 408 | } | ||
| 409 | |||
| 410 | 36 | *bytes_processed = static_cast<U32>(actual_bytes); | |
| 411 | } | ||
| 412 | |||
| 413 | 36 | return status; | |
| 414 | 36 | } | |
| 415 | |||
| 416 | 32 | void Transaction::sSubstateSendFileData() { | |
| 417 | 32 | FileSize bytes_processed = 0; | |
| 418 |
1/1✓ Branch 8 taken 32 times.
|
32 | Status::T status = this->sSendFileData(this->m_foffs, (this->m_fsize - this->m_foffs), 1, &bytes_processed); |
| 419 | |||
| 420 | // When SEND_PDU_NO_BUF_AVAIL_ERROR is returned, it means either: | ||
| 421 | // 1) The throttle limit (max_outgoing_pdus_per_cycle) was reached, OR | ||
| 422 | // 2) Buffer allocation failed | ||
| 423 | // In either case, we should stay in FILEDATA state and retry next cycle. | ||
| 424 | // This is NOT a file I/O error, so we should NOT transition to EOF. | ||
| 425 | // We also need to break the cycleTx loop by setting m_chan->m_currentTxn. | ||
| 426 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
|
32 | if (status == Cfdp::Status::SEND_PDU_NO_BUF_AVAIL_ERROR) { |
| 427 | // Throttle limit or buffer exhaustion - stay in FILEDATA, retry next cycle | ||
| 428 | // Set m_currentTxn to break the cycleTx loop for this cycle | ||
| 429 | ✗ | this->m_chan->setCurrentTxn(this); | |
| 430 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
|
32 | } else if (status != Cfdp::Status::SUCCESS) { |
| 431 | // IO error -- change state and send EOF | ||
| 432 | ✗ | this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_FILESTORE_REJECTION); | |
| 433 | ✗ | this->m_state_data.send.sub_state = TxSubState::TX_SUB_STATE_EOF; | |
| 434 |
1/2✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
|
32 | } else if (bytes_processed > 0) { |
| 435 | 32 | this->m_foffs += bytes_processed; | |
| 436 |
2/2✓ Branch 4 taken 12 times.
✓ Branch 5 taken 20 times.
|
32 | if (this->m_foffs == this->m_fsize) { |
| 437 | // file is done - transition to EOF state, which will be sent in next loop iteration | ||
| 438 | 12 | this->m_state_data.send.sub_state = TxSubState::TX_SUB_STATE_EOF; | |
| 439 | } | ||
| 440 | } else { | ||
| 441 | // don't care about other cases | ||
| 442 | } | ||
| 443 | 32 | } | |
| 444 | |||
| 445 | 71 | Status::T Transaction::sCheckAndRespondNak(bool* nakProcessed) { | |
| 446 | const Chunk* chunk; | ||
| 447 | Status::T sret; | ||
| 448 | 71 | Status::T ret = Cfdp::Status::SUCCESS; | |
| 449 | 71 | FileSize bytes_processed = 0; | |
| 450 | |||
| 451 | 71 | FW_ASSERT(nakProcessed != nullptr); | |
| 452 | 71 | *nakProcessed = false; | |
| 453 | |||
| 454 | // Class 2 transactions must have had chunks allocated | ||
| 455 | 71 | FW_ASSERT(this->m_chunks != nullptr); | |
| 456 | |||
| 457 |
2/4✗ Branch 1 not taken.
✓ Branch 2 taken 71 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 71 times.
|
71 | if (this->m_flags.tx.md_need_send) { |
| 458 | ✗ | sret = this->m_engine->sendMd(this); | |
| 459 | ✗ | if (sret == Cfdp::Status::ERROR) { | |
| 460 | ✗ | ret = Cfdp::Status::ERROR; // serialization failure -- fail the transaction | |
| 461 | } else { | ||
| 462 | ✗ | if (sret == Cfdp::Status::SUCCESS) { | |
| 463 | ✗ | this->m_flags.tx.md_need_send = false; | |
| 464 | } | ||
| 465 | // On SUCCESS or SEND_PDU_NO_BUF_AVAIL_ERROR (throttled, retry next cycle), | ||
| 466 | // mark nak processed to keep caller from sending file data this cycle | ||
| 467 | ✗ | *nakProcessed = true; // nak processed, so don't send filedata | |
| 468 | } | ||
| 469 | } else { | ||
| 470 | // Get first chunk and process if available | ||
| 471 |
1/1✓ Branch 5 taken 71 times.
|
71 | chunk = this->m_chunks->chunks.getFirstChunk(); |
| 472 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 67 times.
|
71 | if (chunk != nullptr) { |
| 473 |
1/1✓ Branch 5 taken 4 times.
|
4 | ret = this->sSendFileData(chunk->offset, chunk->size, 0, &bytes_processed); |
| 474 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (ret != Cfdp::Status::SUCCESS) { |
| 475 | // error occurred | ||
| 476 | ✗ | ret = Cfdp::Status::ERROR; // error occurred | |
| 477 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | } else if (bytes_processed > 0) { |
| 478 |
1/1✓ Branch 5 taken 4 times.
|
4 | this->m_chunks->chunks.removeFromFirst(bytes_processed); |
| 479 | 4 | *nakProcessed = true; // nak processed, so caller doesn't send file data | |
| 480 | } | ||
| 481 | } | ||
| 482 | } | ||
| 483 | |||
| 484 | 71 | return ret; | |
| 485 | } | ||
| 486 | |||
| 487 | 29 | void Transaction::s2SubstateSendFileData() { | |
| 488 | Status::T status; | ||
| 489 | 29 | bool nakProcessed = false; | |
| 490 | |||
| 491 |
1/1✓ Branch 2 taken 29 times.
|
29 | status = this->sCheckAndRespondNak(&nakProcessed); |
| 492 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
|
29 | if (status != Cfdp::Status::SUCCESS) { |
| 493 | ✗ | this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_NAK_RESPONSE_ERROR); | |
| 494 | ✗ | this->m_flags.tx.send_eof = true; /* do not leave the remote hanging */ | |
| 495 | ✗ | this->m_engine->finishTransaction(this, true); | |
| 496 | ✗ | return; | |
| 497 | } | ||
| 498 | |||
| 499 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
|
29 | if (!nakProcessed) { |
| 500 |
1/1✓ Branch 2 taken 29 times.
|
29 | this->sSubstateSendFileData(); |
| 501 | } else { | ||
| 502 | // NAK was processed, so do not send filedata | ||
| 503 | } | ||
| 504 | } | ||
| 505 | |||
| 506 | 16 | void Transaction::sSubstateSendMetadata() { | |
| 507 | Status::T status; | ||
| 508 | Os::File::Status fileStatus; | ||
| 509 | 16 | bool success = true; | |
| 510 | |||
| 511 |
1/2✓ Branch 6 taken 16 times.
✗ Branch 7 not taken.
|
16 | if (false == this->m_fd.isOpen()) { |
| 512 | 16 | fileStatus = this->m_fd.open(this->m_history->fnames.src_filename.toChar(), Os::File::OPEN_READ); | |
| 513 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 15 times.
|
16 | if (fileStatus != Os::File::OP_OK) { |
| 514 |
2/2✓ Branch 16 taken 1 times.
✓ Branch 20 taken 1 times.
|
3 | this->m_cfdpManager->log_WARNING_LO_TxFileOpenFailed(this->getClass(), this->m_history->src_eid, |
| 515 | 1 | this->m_history->seq_num, | |
| 516 | 1 | this->m_history->fnames.src_filename, fileStatus); | |
| 517 | 1 | this->m_cfdpManager->incrementFaultFileOpen(this->m_chan_num); | |
| 518 | 1 | success = false; | |
| 519 | } | ||
| 520 | |||
| 521 |
2/2✓ Branch 0 taken 15 times.
✓ Branch 1 taken 1 times.
|
16 | if (success) { |
| 522 | 15 | FwSizeType file_size; | |
| 523 |
1/1✓ Branch 6 taken 15 times.
|
15 | fileStatus = this->m_fd.size(file_size); |
| 524 | 15 | this->m_fsize = static_cast<FileSize>(file_size); | |
| 525 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 14 times.
|
15 | if (fileStatus != Os::File::Status::OP_OK) { |
| 526 |
2/2✓ Branch 15 taken 1 times.
✓ Branch 19 taken 1 times.
|
2 | this->m_cfdpManager->log_WARNING_LO_TxFileSeekFailed(this->getClass(), this->m_history->src_eid, |
| 527 | 1 | this->m_history->seq_num, fileStatus); | |
| 528 |
1/1✓ Branch 8 taken 1 times.
|
1 | this->m_cfdpManager->incrementFaultFileSeek(this->m_chan_num); |
| 529 | 1 | success = false; | |
| 530 |
2/2✓ Branch 2 taken 1 times.
✓ Branch 3 taken 13 times.
|
14 | } else if (this->m_fsize == 0) { |
| 531 | // Zero-length file - fail transaction gracefully instead of asserting | ||
| 532 |
2/2✓ Branch 16 taken 1 times.
✓ Branch 20 taken 1 times.
|
3 | this->m_cfdpManager->log_WARNING_LO_TxZeroLengthFile(this->getClass(), this->m_history->src_eid, |
| 533 | 1 | this->m_history->seq_num, | |
| 534 | 1 | this->m_history->fnames.src_filename); | |
| 535 |
1/1✓ Branch 8 taken 1 times.
|
1 | this->m_cfdpManager->incrementFaultFileSizeMismatch(this->m_chan_num); |
| 536 | 1 | success = false; | |
| 537 | } | ||
| 538 | } | ||
| 539 | } | ||
| 540 | |||
| 541 |
2/2✓ Branch 0 taken 13 times.
✓ Branch 1 taken 3 times.
|
16 | if (success) { |
| 542 | 13 | status = this->m_engine->sendMd(this); | |
| 543 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 12 times.
|
13 | if (status == Cfdp::Status::ERROR) { |
| 544 | /* failed to send md (generic ERROR from a PDU serialization failure) */ | ||
| 545 |
2/2✓ Branch 15 taken 1 times.
✓ Branch 19 taken 1 times.
|
2 | this->m_cfdpManager->log_WARNING_LO_TxSendMetadataFailed(this->getClass(), this->m_history->src_eid, |
| 546 | 1 | this->m_history->seq_num); | |
| 547 | 1 | success = false; | |
| 548 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | } else if (status == Cfdp::Status::SUCCESS) { |
| 549 | /* once metadata is sent, switch to filedata mode */ | ||
| 550 | 12 | this->m_state_data.send.sub_state = TxSubState::TX_SUB_STATE_FILEDATA; | |
| 551 | |||
| 552 |
2/2✓ Branch 15 taken 12 times.
✓ Branch 19 taken 12 times.
|
84 | this->m_cfdpManager->log_ACTIVITY_HI_TxFileTransferStarted( |
| 553 | 36 | this->getClass(), this->m_history->seq_num, this->m_history->src_eid, | |
| 554 | 36 | this->m_history->fnames.src_filename, this->m_history->peer_eid, this->m_history->fnames.dst_filename, | |
| 555 | 12 | static_cast<U32>(this->m_fsize)); | |
| 556 | } | ||
| 557 | /* if status==Cfdp::Status::SEND_PDU_NO_BUF_AVAIL_ERROR, then the send buffer is throttled; | ||
| 558 | leave success==true and retry the metadata send on the next cycle */ | ||
| 559 | } | ||
| 560 | |||
| 561 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 12 times.
|
16 | if (!success) { |
| 562 | 4 | this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_FILESTORE_REJECTION); | |
| 563 | 4 | this->m_engine->finishTransaction(this, true); | |
| 564 | } | ||
| 565 | |||
| 566 | // don't need to reset the CRC since its taken care of by reset_cfdp() | ||
| 567 | 16 | } | |
| 568 | |||
| 569 | 6 | Status::T Transaction::sSendFinAck() { | |
| 570 | Status::T ret = | ||
| 571 | 24 | this->m_engine->sendAck(this, static_cast<AckTxnStatus>(GetTxnStatus(this)), FileDirective::FILE_DIRECTIVE_FIN, | |
| 572 | 6 | static_cast<ConditionCode>(this->m_state_data.send.s2.fin_cc), | |
| 573 | 12 | this->m_history->peer_eid, this->m_history->seq_num); | |
| 574 | 6 | return ret; | |
| 575 | } | ||
| 576 | |||
| 577 | 1 | void Transaction::s2EarlyFin(const Fw::Buffer& buffer) { | |
| 578 | // received early fin, so just cancel | ||
| 579 |
2/2✓ Branch 15 taken 1 times.
✓ Branch 19 taken 1 times.
|
2 | this->m_cfdpManager->log_WARNING_LO_TxEarlyFinReceived(this->getClass(), this->m_history->src_eid, |
| 580 | 1 | this->m_history->seq_num); | |
| 581 | 1 | this->m_engine->setTxnStatus(this, TxnStatus::TXN_STATUS_EARLY_FIN); | |
| 582 | |||
| 583 | 1 | this->m_state_data.send.sub_state = TxSubState::TX_SUB_STATE_CLOSEOUT_SYNC; | |
| 584 | |||
| 585 | // otherwise do normal fin processing | ||
| 586 | 1 | this->s2Fin(buffer); | |
| 587 | 1 | } | |
| 588 | |||
| 589 | 8 | void Transaction::s2Fin(const Fw::Buffer& buffer) { | |
| 590 | // Deserialize FIN PDU from buffer | ||
| 591 |
1/1✓ Branch 2 taken 8 times.
|
8 | FinPdu fin; |
| 592 | // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only) | ||
| 593 |
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()); |
| 594 |
2/2✓ Branch 5 taken 8 times.
✓ Branch 8 taken 8 times.
|
8 | sb.setBuffLen(buffer.getSize()); |
| 595 | |||
| 596 |
1/1✓ Branch 2 taken 8 times.
|
8 | Fw::SerializeStatus deserStatus = fin.deserializeFrom(sb); |
| 597 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
|
8 | if (deserStatus != Fw::FW_SERIALIZE_OK) { |
| 598 | // Bad FIN PDU | ||
| 599 |
1/1✓ Branch 9 taken 1 times.
|
1 | this->m_cfdpManager->log_WARNING_LO_FailFinPduDeserialization(this->getChannelId(), |
| 600 | static_cast<I32>(deserStatus)); | ||
| 601 | 1 | return; | |
| 602 | } | ||
| 603 | |||
| 604 |
2/3✓ Branch 6 taken 7 times.
✓ Branch 8 taken 7 times.
✗ Branch 9 not taken.
|
7 | if (!this->m_engine->recvFin(this, fin)) { |
| 605 | // Always set flag to send FIN-ACK, even if it is a retransmit | ||
| 606 | 7 | this->m_flags.tx.send_fin_ack = true; | |
| 607 | |||
| 608 | // set the CC only on the first time we get the FIN. If this is a dupe | ||
| 609 | // then re-ack but otherwise ignore it | ||
| 610 |
2/4✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 7 times.
✗ Branch 4 not taken.
|
7 | if (!this->m_flags.tx.fin_recv) { |
| 611 | 7 | this->m_flags.tx.fin_recv = true; | |
| 612 | 7 | this->m_state_data.send.s2.fin_cc = static_cast<U8>(fin.getConditionCode()); | |
| 613 | 7 | this->m_state_data.send.s2.acknak_count = 0; // in case retransmits had occurred | |
| 614 | |||
| 615 | // note this is a no-op unless the status was unset previously | ||
| 616 |
1/1✓ Branch 8 taken 7 times.
|
7 | this->m_engine->setTxnStatus(this, static_cast<TxnStatus>(this->m_state_data.send.s2.fin_cc)); |
| 617 | |||
| 618 | // Generally FIN is the last exchange in an S2 transaction, the remote is not supposed | ||
| 619 | // to send it until after the EOF+ACK. So at this point we stop trying to send anything | ||
| 620 | // to the peer, regardless of whether we got every ACK we expected. | ||
| 621 |
1/1✓ Branch 6 taken 7 times.
|
7 | this->m_engine->finishTransaction(this, true); |
| 622 | } | ||
| 623 | } | ||
| 624 | 9 | } | |
| 625 | |||
| 626 | 5 | void Transaction::s2Nak(const Fw::Buffer& buffer) { | |
| 627 | U8 counter; | ||
| 628 | U8 bad_sr; | ||
| 629 | |||
| 630 | 5 | bad_sr = 0; | |
| 631 | |||
| 632 | // Deserialize NAK PDU from buffer | ||
| 633 |
1/1✓ Branch 2 taken 5 times.
|
5 | NakPdu nak; |
| 634 | // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only) | ||
| 635 |
3/3✓ Branch 5 taken 5 times.
✓ Branch 11 taken 5 times.
✓ Branch 14 taken 5 times.
|
5 | Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize()); |
| 636 |
2/2✓ Branch 5 taken 5 times.
✓ Branch 8 taken 5 times.
|
5 | sb.setBuffLen(buffer.getSize()); |
| 637 | |||
| 638 |
1/1✓ Branch 2 taken 5 times.
|
5 | Fw::SerializeStatus deserStatus = nak.deserializeFrom(sb); |
| 639 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
|
5 | if (deserStatus != Fw::FW_SERIALIZE_OK) { |
| 640 | // Bad NAK PDU | ||
| 641 |
1/1✓ Branch 9 taken 1 times.
|
1 | this->m_cfdpManager->log_WARNING_LO_FailNakPduDeserialization(this->getChannelId(), |
| 642 | static_cast<I32>(deserStatus)); | ||
| 643 |
1/1✓ Branch 8 taken 1 times.
|
1 | this->m_cfdpManager->incrementRecvErrors(this->m_chan_num); |
| 644 | 1 | return; | |
| 645 | } | ||
| 646 | |||
| 647 | // this function is only invoked for NAK PDU types | ||
| 648 |
6/7✓ Branch 6 taken 4 times.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
✓ Branch 12 taken 3 times.
✓ Branch 13 taken 1 times.
✓ Branch 14 taken 3 times.
✓ Branch 15 taken 1 times.
|
4 | if (this->m_engine->recvNak(this, nak) == Cfdp::Status::SUCCESS && nak.getNumSegments() > 0) { |
| 649 |
2/2✓ Branch 2 taken 9 times.
✓ Branch 3 taken 3 times.
|
12 | for (counter = 0; counter < nak.getNumSegments(); ++counter) { |
| 650 | 9 | const Cfdp::SegmentRequest& sr = nak.getSegment(counter); | |
| 651 | |||
| 652 |
3/4✓ Branch 1 taken 1 times.
✓ Branch 2 taken 8 times.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
|
9 | if (sr.offsetStart == 0 && sr.offsetEnd == 0) { |
| 653 | // need to re-send metadata PDU | ||
| 654 | 1 | this->m_flags.tx.md_need_send = true; | |
| 655 | } else { | ||
| 656 |
2/2✓ Branch 3 taken 1 times.
✓ Branch 4 taken 7 times.
|
8 | if (sr.offsetEnd < sr.offsetStart) { |
| 657 | 1 | ++bad_sr; | |
| 658 | 1 | continue; | |
| 659 | } | ||
| 660 | |||
| 661 | // overflow probably won't be an issue | ||
| 662 |
2/2✓ Branch 4 taken 1 times.
✓ Branch 5 taken 6 times.
|
7 | if (sr.offsetEnd > this->m_fsize) { |
| 663 | 1 | ++bad_sr; | |
| 664 | 1 | continue; | |
| 665 | } | ||
| 666 | |||
| 667 | // insert gap data in chunks | ||
| 668 |
1/1✓ Branch 9 taken 6 times.
|
6 | this->m_chunks->chunks.add(sr.offsetStart, sr.offsetEnd - sr.offsetStart); |
| 669 | } | ||
| 670 | } | ||
| 671 | |||
| 672 |
1/1✓ Branch 10 taken 3 times.
|
3 | this->m_cfdpManager->addRecvNakSegmentRequests(this->m_chan_num, nak.getNumSegments()); |
| 673 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
|
3 | if (bad_sr) { |
| 674 |
2/2✓ Branch 15 taken 1 times.
✓ Branch 19 taken 1 times.
|
2 | this->m_cfdpManager->log_WARNING_LO_TxInvalidSegmentRequests(this->getClass(), this->m_history->src_eid, |
| 675 | 1 | this->m_history->seq_num, bad_sr); | |
| 676 | } | ||
| 677 | } else { | ||
| 678 |
2/2✓ Branch 15 taken 1 times.
✓ Branch 19 taken 1 times.
|
2 | this->m_cfdpManager->log_WARNING_LO_TxInvalidNakPdu(this->getClass(), this->m_history->src_eid, |
| 679 | 1 | this->m_history->seq_num); | |
| 680 |
1/1✓ Branch 8 taken 1 times.
|
1 | this->m_cfdpManager->incrementRecvErrors(this->m_chan_num); |
| 681 | } | ||
| 682 | 6 | } | |
| 683 | |||
| 684 | 2 | void Transaction::s2NakArm(const Fw::Buffer& buffer) { | |
| 685 | 2 | this->m_engine->armAckTimer(this); | |
| 686 | 2 | this->s2Nak(buffer); | |
| 687 | 2 | } | |
| 688 | |||
| 689 | 7 | void Transaction::s2EofAck(const Fw::Buffer& buffer) { | |
| 690 | // Deserialize ACK PDU from buffer | ||
| 691 |
1/1✓ Branch 2 taken 7 times.
|
7 | AckPdu ack; |
| 692 | // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only) | ||
| 693 |
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()); |
| 694 |
2/2✓ Branch 5 taken 7 times.
✓ Branch 8 taken 7 times.
|
7 | sb.setBuffLen(buffer.getSize()); |
| 695 | |||
| 696 |
1/1✓ Branch 2 taken 7 times.
|
7 | Fw::SerializeStatus deserStatus = ack.deserializeFrom(sb); |
| 697 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 6 times.
|
7 | if (deserStatus != Fw::FW_SERIALIZE_OK) { |
| 698 | // Bad ACK PDU | ||
| 699 |
1/1✓ Branch 9 taken 1 times.
|
1 | this->m_cfdpManager->log_WARNING_LO_FailAckPduDeserialization(this->getChannelId(), |
| 700 | static_cast<I32>(deserStatus)); | ||
| 701 | 1 | return; | |
| 702 | } | ||
| 703 | |||
| 704 | // ACK PDU has been validated during deserialization | ||
| 705 | // Check if this is an EOF acknowledgment | ||
| 706 |
1/2✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
6 | if (ack.getDirectiveCode() == FileDirective::FILE_DIRECTIVE_END_OF_FILE) { |
| 707 | 6 | this->m_flags.tx.eof_ack_recv = true; | |
| 708 | 6 | this->m_flags.com.ack_timer_armed = false; // just wait for FIN now, nothing to re-send | |
| 709 | 6 | this->m_state_data.send.s2.acknak_count = 0; // in case EOF retransmits had occurred | |
| 710 | |||
| 711 | // For canceled transactions, finish immediately after receiving EOF-ACK | ||
| 712 | // The remote side does not send FIN for canceled transactions per CFDP protocol | ||
| 713 | // if FIN was also received then we are done (these can come out of order) | ||
| 714 |
4/8✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 6 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 6 times.
|
6 | if (this->m_flags.com.canceled || this->m_flags.tx.fin_recv) { |
| 715 | ✗ | this->m_engine->finishTransaction(this, true); | |
| 716 | } | ||
| 717 | } | ||
| 718 | 8 | } | |
| 719 | |||
| 720 | // ====================================================================== | ||
| 721 | // Dispatch Methods (ported from cf_cfdp_dispatch.c) | ||
| 722 | // ====================================================================== | ||
| 723 | |||
| 724 | 16 | void Transaction::sDispatchRecv(const Fw::Buffer& buffer, const SSubstateRecvDispatchTable* dispatch) { | |
| 725 | const FileDirectiveDispatchTable* substate_tbl; | ||
| 726 | StateRecvFunc selected_handler; | ||
| 727 | |||
| 728 | 16 | FW_ASSERT(this->m_state_data.send.sub_state < TxSubState::TX_SUB_STATE_NUM_STATES, | |
| 729 | static_cast<U8>(this->m_state_data.send.sub_state), static_cast<U8>(TxSubState::TX_SUB_STATE_NUM_STATES)); | ||
| 730 | |||
| 731 | // Peek at PDU type from buffer | ||
| 732 | 16 | Cfdp::PduTypeEnum::T pduType = Cfdp::peekPduType(buffer); | |
| 733 | |||
| 734 | // send state, so we only care about file directive PDU | ||
| 735 | 16 | selected_handler = nullptr; | |
| 736 | |||
| 737 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 15 times.
|
16 | if (pduType == Cfdp::PduTypeEnum::FILE_DATA) { |
| 738 |
2/2✓ Branch 15 taken 1 times.
✓ Branch 19 taken 1 times.
|
2 | this->m_cfdpManager->log_WARNING_LO_TxNonFileDirectivePduReceived(this->getClass(), this->m_history->src_eid, |
| 739 | 1 | this->m_history->seq_num); | |
| 740 | } else { | ||
| 741 | // Not a file-data PDU - parse as a directive PDU to get the directive code. | ||
| 742 | // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only) | ||
| 743 |
3/3✓ Branch 5 taken 15 times.
✓ Branch 11 taken 15 times.
✓ Branch 14 taken 15 times.
|
15 | Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize()); |
| 744 |
2/2✓ Branch 5 taken 15 times.
✓ Branch 8 taken 15 times.
|
15 | sb.setBuffLen(buffer.getSize()); |
| 745 | |||
| 746 | 15 | Cfdp::PduHeader header; | |
| 747 |
2/3✓ Branch 1 taken 15 times.
✓ Branch 3 taken 15 times.
✗ Branch 4 not taken.
|
15 | if (header.fromSerialBuffer(sb) == Fw::FW_SERIALIZE_OK) { |
| 748 | // Read directive code (first byte after header for directive PDUs) | ||
| 749 | 15 | U8 directiveCodeByte; | |
| 750 |
2/3✓ Branch 2 taken 15 times.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
|
15 | if (sb.deserializeTo(directiveCodeByte) == Fw::FW_SERIALIZE_OK) { |
| 751 | 15 | FileDirective directiveCode = static_cast<FileDirective>(directiveCodeByte); | |
| 752 | |||
| 753 |
2/2✓ Branch 0 taken 14 times.
✓ Branch 1 taken 1 times.
|
15 | if (directiveCode < FileDirective::FILE_DIRECTIVE_INVALID_MAX) { |
| 754 | // This should be silent (no event) if no handler is defined in the table | ||
| 755 | 14 | substate_tbl = dispatch->substate[static_cast<U32>(this->m_state_data.send.sub_state)]; | |
| 756 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | if (substate_tbl != nullptr) { |
| 757 | 14 | selected_handler = substate_tbl->fdirective[static_cast<U32>(directiveCode)]; | |
| 758 | } | ||
| 759 | } else { | ||
| 760 |
2/2✓ Branch 11 taken 1 times.
✓ Branch 15 taken 1 times.
|
4 | this->m_cfdpManager->log_WARNING_LO_TxInvalidDirectiveCode( |
| 761 | 3 | this->getClass(), this->m_history->src_eid, this->m_history->seq_num, directiveCodeByte, | |
| 762 | 1 | static_cast<U8>(this->m_state_data.send.sub_state)); | |
| 763 | } | ||
| 764 | } | ||
| 765 | } | ||
| 766 | 15 | } | |
| 767 | |||
| 768 | // check that there's a valid function pointer. If there isn't, | ||
| 769 | // then silently ignore. We may want to discuss if it's worth | ||
| 770 | // shutting down the whole transaction if a PDU is received | ||
| 771 | // that doesn't make sense to be received (For example, | ||
| 772 | // class 1 CFDP receiving a NAK PDU) but for now, we silently | ||
| 773 | // ignore the received packet and keep chugging along. | ||
| 774 |
2/2✓ Branch 0 taken 14 times.
✓ Branch 1 taken 2 times.
|
16 | if (selected_handler) { |
| 775 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 14 times.
|
14 | (this->*selected_handler)(buffer); |
| 776 | } | ||
| 777 | 16 | } | |
| 778 | |||
| 779 | 58 | void Transaction::sDispatchTransmit(const SSubstateSendDispatchTable* dispatch) { | |
| 780 | StateSendFunc selected_handler; | ||
| 781 | |||
| 782 | 58 | selected_handler = dispatch->substate[static_cast<U32>(this->m_state_data.send.sub_state)]; | |
| 783 |
1/2✓ Branch 0 taken 58 times.
✗ Branch 1 not taken.
|
58 | if (selected_handler != nullptr) { |
| 784 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 58 times.
|
58 | (this->*selected_handler)(); |
| 785 | } | ||
| 786 | 58 | } | |
| 787 | |||
| 788 | 58 | void Transaction::txStateDispatch(const TxnSendDispatchTable* dispatch) { | |
| 789 | StateSendFunc selected_handler; | ||
| 790 | |||
| 791 | 58 | FW_ASSERT(this->m_state < TxnState::TXN_STATE_INVALID, static_cast<U8>(this->m_state), | |
| 792 | static_cast<U8>(TxnState::TXN_STATE_INVALID)); | ||
| 793 | |||
| 794 | 58 | selected_handler = dispatch->tx[static_cast<U32>(this->m_state)]; | |
| 795 |
1/2✓ Branch 0 taken 58 times.
✗ Branch 1 not taken.
|
58 | if (selected_handler != nullptr) { |
| 796 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 58 times.
|
58 | (this->*selected_handler)(); |
| 797 | } | ||
| 798 | 58 | } | |
| 799 | |||
| 800 | } // namespace Cfdp | ||
| 801 | } // namespace Ccsds | ||
| 802 | } // namespace Svc | ||
| 803 |