| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // ====================================================================== | ||
| 2 | // \title Engine.cpp | ||
| 3 | // \brief CFDP Engine implementation | ||
| 4 | // | ||
| 5 | // This file is a port of CFDP engine 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.c (CFDP PDU validation, processing, and engine operations) | ||
| 9 | // | ||
| 10 | // This file contains two sets of functions. The first is what is needed | ||
| 11 | // to deal with CFDP PDUs. Specifically validating them for correctness | ||
| 12 | // and ensuring the byte-order is correct for the target. The second | ||
| 13 | // is incoming and outgoing CFDP PDUs pass through here. All receive | ||
| 14 | // CFDP PDU logic is performed here and the data is passed to the | ||
| 15 | // R (rx) and S (tx) logic. | ||
| 16 | // | ||
| 17 | // ====================================================================== | ||
| 18 | // | ||
| 19 | // NASA Docket No. GSC-18,447-1 | ||
| 20 | // | ||
| 21 | // Copyright (c) 2019 United States Government as represented by the | ||
| 22 | // Administrator of the National Aeronautics and Space Administration. | ||
| 23 | // All Rights Reserved. | ||
| 24 | // | ||
| 25 | // Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
| 26 | // not use this file except in compliance with the License. You may obtain | ||
| 27 | // a copy of the License at | ||
| 28 | // | ||
| 29 | // http://www.apache.org/licenses/LICENSE-2.0 | ||
| 30 | // | ||
| 31 | // Unless required by applicable law or agreed to in writing, software | ||
| 32 | // distributed under the License is distributed on an "AS IS" BASIS, | ||
| 33 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 34 | // See the License for the specific language governing permissions and | ||
| 35 | // limitations under the License. | ||
| 36 | // | ||
| 37 | // ====================================================================== | ||
| 38 | |||
| 39 | #include <string.h> | ||
| 40 | #include <new> | ||
| 41 | |||
| 42 | #include <Fw/Types/StringUtils.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/Engine.hpp> | ||
| 48 | #include <Svc/Ccsds/CfdpManager/Transaction.hpp> | ||
| 49 | #include <Svc/Ccsds/CfdpManager/Types/PduBase.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 | 132 | Engine::Engine(CfdpManager* manager) : m_manager(manager), m_seqNum(0), m_allocator(nullptr), m_allocatorId(0) { | |
| 61 |
2/2✓ Branch 0 taken 264 times.
✓ Branch 1 taken 132 times.
|
396 | for (U8 i = 0; i < Cfdp::NumChannels; ++i) { |
| 62 | 264 | m_channels[i] = nullptr; | |
| 63 | } | ||
| 64 | 132 | } | |
| 65 | |||
| 66 | 264 | Engine::~Engine() { | |
| 67 | 264 | FW_ASSERT(m_allocator != nullptr, 0); // init() must have been called | |
| 68 | |||
| 69 |
2/2✓ Branch 0 taken 264 times.
✓ Branch 1 taken 132 times.
|
792 | for (U8 i = 0; i < Cfdp::NumChannels; ++i) { |
| 70 |
2/2✓ Branch 5 taken 262 times.
✓ Branch 6 taken 2 times.
|
528 | if (m_channels[i] != nullptr) { |
| 71 | // Clean up Channel's internal arrays first | ||
| 72 | 524 | m_channels[i]->cleanup(*m_allocator, m_allocatorId); | |
| 73 | |||
| 74 | // Call destructor | ||
| 75 | 524 | m_channels[i]->~Channel(); | |
| 76 | |||
| 77 | // Deallocate the Channel object itself | ||
| 78 | 524 | m_allocator->deallocate(m_allocatorId, m_channels[i]); | |
| 79 | 524 | m_channels[i] = nullptr; | |
| 80 | } | ||
| 81 | } | ||
| 82 | 264 | } | |
| 83 | |||
| 84 | // ---------------------------------------------------------------------- | ||
| 85 | // Public interface methods | ||
| 86 | // ---------------------------------------------------------------------- | ||
| 87 | |||
| 88 | 131 | void Engine::init(Fw::MemAllocator& allocator, FwEnumStoreType memId) { | |
| 89 | // Store allocator for cleanup in destructor | ||
| 90 | 131 | m_allocator = &allocator; | |
| 91 | 131 | m_allocatorId = memId; | |
| 92 | |||
| 93 | // Allocate and construct all channels using the allocator | ||
| 94 |
2/2✓ Branch 0 taken 262 times.
✓ Branch 1 taken 131 times.
|
393 | for (U8 i = 0; i < Cfdp::NumChannels; ++i) { |
| 95 | 262 | FwSizeType channelSize = sizeof(Channel); | |
| 96 |
1/1✓ Branch 4 taken 262 times.
|
262 | void* channelMem = allocator.allocate(memId, channelSize); |
| 97 | 262 | FW_ASSERT(channelMem != nullptr); | |
| 98 | |||
| 99 | // Use placement new to construct Channel in allocated memory | ||
| 100 |
1/3✓ Branch 7 taken 262 times.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
|
262 | m_channels[i] = new (channelMem) Channel(this, i, this->m_manager, allocator, memId); |
| 101 | } | ||
| 102 | 131 | } | |
| 103 | |||
| 104 | 54 | void Engine::armAckTimer(Transaction* txn) { | |
| 105 | 54 | txn->m_ack_timer.setTimer(txn->m_cfdpManager->getAckTimerParam(txn->m_chan_num)); | |
| 106 | 54 | txn->m_flags.com.ack_timer_armed = true; | |
| 107 | 54 | } | |
| 108 | |||
| 109 | 215 | void Engine::armInactTimer(Transaction* txn) { | |
| 110 | 215 | U32 timerDuration = 0; | |
| 111 | |||
| 112 | // select timeout based on the state | ||
| 113 |
2/2✓ Branch 1 taken 151 times.
✓ Branch 2 taken 64 times.
|
215 | if (GetTxnStatus(txn) == AckTxnStatus::ACK_TXN_STATUS_ACTIVE) { |
| 114 | // in an active transaction, we expect traffic so use the normal inactivity timer | ||
| 115 | 151 | timerDuration = txn->m_cfdpManager->getInactivityTimerParam(txn->m_chan_num); | |
| 116 | } else { | ||
| 117 | // in an inactive transaction, we do NOT expect traffic, and this timer is now used | ||
| 118 | // just in case any late straggler PDUs dp get delivered. In this case the | ||
| 119 | // time should be longer than the retransmit time (ack timer) but less than the full | ||
| 120 | // inactivity timer (because again, we are not expecting traffic, so waiting the full | ||
| 121 | // timeout would hold resources longer than needed). Using double the ack timer should | ||
| 122 | // ensure that if the remote retransmitted anything, we will see it, and avoids adding | ||
| 123 | // another config option just for this. | ||
| 124 | 64 | timerDuration = txn->m_cfdpManager->getAckTimerParam(txn->m_chan_num) * 2; | |
| 125 | } | ||
| 126 | |||
| 127 | 215 | txn->m_inactivity_timer.setTimer(timerDuration); | |
| 128 | 215 | } | |
| 129 | |||
| 130 | 143 | void Engine::dispatchRecv(Transaction* txn, const Fw::Buffer& buffer) { | |
| 131 | // Loop to handle state transitions without recursion | ||
| 132 | // The loop allows recvInit to transition to R2 state and re-dispatch | ||
| 133 | 143 | bool needsDispatch = true; | |
| 134 |
2/2✓ Branch 0 taken 146 times.
✓ Branch 1 taken 143 times.
|
289 | while (needsDispatch) { |
| 135 | 146 | needsDispatch = false; // Assume single dispatch unless state handler requests re-dispatch | |
| 136 | |||
| 137 | // Dispatch based on transaction state | ||
| 138 |
5/8✓ Branch 1 taken 81 times.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 35 times.
✓ Branch 5 taken 14 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
|
146 | switch (txn->m_state) { |
| 139 | 81 | case TxnState::TXN_STATE_INIT: | |
| 140 | 81 | needsDispatch = this->recvInit(txn, buffer); | |
| 141 | 81 | break; | |
| 142 | 15 | case TxnState::TXN_STATE_R1: | |
| 143 | 15 | txn->r1Recv(buffer); | |
| 144 | 15 | break; | |
| 145 | ✗ | case TxnState::TXN_STATE_S1: | |
| 146 | ✗ | txn->s1Recv(buffer); | |
| 147 | ✗ | break; | |
| 148 | 35 | case TxnState::TXN_STATE_R2: | |
| 149 | 35 | txn->r2Recv(buffer); | |
| 150 | 35 | break; | |
| 151 | 14 | case TxnState::TXN_STATE_S2: | |
| 152 | 14 | txn->s2Recv(buffer); | |
| 153 | 14 | break; | |
| 154 | ✗ | case TxnState::TXN_STATE_DROP: | |
| 155 | ✗ | this->recvDrop(txn, buffer); | |
| 156 | ✗ | break; | |
| 157 | 1 | case TxnState::TXN_STATE_HOLD: | |
| 158 | 1 | this->recvHold(txn, buffer); | |
| 159 | 1 | break; | |
| 160 | ✗ | default: | |
| 161 | // Invalid or undefined state | ||
| 162 | ✗ | break; | |
| 163 | } | ||
| 164 | } | ||
| 165 | |||
| 166 | 143 | this->armInactTimer(txn); // whenever a packet was received by the other size, always arm its inactivity timer | |
| 167 | 143 | } | |
| 168 | |||
| 169 | 58 | void Engine::dispatchTx(Transaction* txn) { | |
| 170 | static const TxnSendDispatchTable state_fns = {{ | ||
| 171 | nullptr, // TxnState::TXN_STATE_UNDEF | ||
| 172 | nullptr, // TxnState::TXN_STATE_INIT | ||
| 173 | nullptr, // TxnState::TXN_STATE_R1 | ||
| 174 | &Transaction::s1Tx, // TxnState::TXN_STATE_S1 | ||
| 175 | nullptr, // TxnState::TXN_STATE_R2 | ||
| 176 | &Transaction::s2Tx, // TxnState::TXN_STATE_S2 | ||
| 177 | nullptr, // TxnState::TXN_STATE_DROP | ||
| 178 | nullptr // TxnState::TXN_STATE_HOLD | ||
| 179 | }}; | ||
| 180 | |||
| 181 | 58 | txn->txStateDispatch(&state_fns); | |
| 182 | 58 | } | |
| 183 | |||
| 184 | 13 | Status::T Engine::sendMd(Transaction* txn) { | |
| 185 | 13 | FW_ASSERT((txn->m_state == TxnState::TXN_STATE_S1) || (txn->m_state == TxnState::TXN_STATE_S2), | |
| 186 | static_cast<U8>(txn->m_state)); | ||
| 187 | 13 | FW_ASSERT(txn->m_chan != nullptr); | |
| 188 | |||
| 189 | // Create and initialize Metadata PDU | ||
| 190 |
1/1✓ Branch 2 taken 13 times.
|
13 | MetadataPdu md; |
| 191 | |||
| 192 | // Set closure requested flag based on transaction class | ||
| 193 | // Class 1: closure not requested (0), Class 2: closure requested (1) | ||
| 194 |
2/2✓ Branch 1 taken 9 times.
✓ Branch 2 taken 4 times.
|
13 | U8 closureRequested = (txn->m_state == TxnState::TXN_STATE_S2) ? 1 : 0; |
| 195 | |||
| 196 | // Direction is toward receiver for metadata PDU sent by sender | ||
| 197 | 13 | Cfdp::PduDirection direction = PduDirection::DIRECTION_TOWARD_RECEIVER; | |
| 198 | |||
| 199 |
2/2✓ Branch 6 taken 13 times.
✓ Branch 10 taken 13 times.
|
78 | md.initialize(direction, |
| 200 | 13 | txn->getClass(), // transmission mode (Class 1 or 2) | |
| 201 | 13 | m_manager->getLocalEidParam(), // source EID | |
| 202 | 13 | txn->m_history->seq_num, // transaction sequence number | |
| 203 | 13 | txn->m_history->peer_eid, // destination EID | |
| 204 | txn->m_fsize, // file size | ||
| 205 | 13 | txn->m_history->fnames.src_filename, // source filename | |
| 206 | 13 | txn->m_history->fnames.dst_filename, // destination filename | |
| 207 | ChecksumType::CHECKSUM_TYPE_MODULAR, // checksum type | ||
| 208 | closureRequested // closure requested flag | ||
| 209 | ); | ||
| 210 | |||
| 211 |
1/1✓ Branch 4 taken 13 times.
|
26 | return serializeAndSendPdu(txn, md); |
| 212 | 13 | } | |
| 213 | |||
| 214 | 37 | Status::T Engine::sendFd(Transaction* txn, FileDataPdu& fdPdu) { | |
| 215 | 37 | Status::T status = serializeAndSendPdu(txn, fdPdu); | |
| 216 |
1/2✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
|
37 | if (status == Cfdp::Status::SUCCESS) { |
| 217 | 37 | m_manager->addSentFileDataBytes(txn->getChannelId(), fdPdu.getDataSize()); | |
| 218 | } | ||
| 219 | 37 | return status; | |
| 220 | } | ||
| 221 | |||
| 222 | 16 | Status::T Engine::sendEof(Transaction* txn) { | |
| 223 | // Create and initialize EOF PDU | ||
| 224 |
1/1✓ Branch 2 taken 16 times.
|
16 | EofPdu eof; |
| 225 | |||
| 226 | // Direction is toward receiver for EOF sent by sender | ||
| 227 | 16 | Cfdp::PduDirection direction = PduDirection::DIRECTION_TOWARD_RECEIVER; | |
| 228 |
1/1✓ Branch 5 taken 16 times.
|
16 | ConditionCode conditionCode = static_cast<ConditionCode>(TxnStatusToConditionCode(txn->m_history->txn_stat)); |
| 229 | |||
| 230 | // Increment sent EOF counters based on condition code | ||
| 231 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
|
16 | if (conditionCode == ConditionCode::CONDITION_CODE_CANCEL_REQUEST_RECEIVED) { |
| 232 | ✗ | this->m_manager->incrementSentEofCanceled(txn->getChannelId()); | |
| 233 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 15 times.
|
16 | } else if (conditionCode != ConditionCode::CONDITION_CODE_NO_ERROR) { |
| 234 |
1/1✓ Branch 10 taken 1 times.
|
1 | this->m_manager->incrementFaultTxEofError(txn->getChannelId()); |
| 235 | } | ||
| 236 | |||
| 237 |
3/3✓ Branch 4 taken 16 times.
✓ Branch 9 taken 16 times.
✓ Branch 13 taken 16 times.
|
96 | eof.initialize(direction, |
| 238 | 16 | txn->getClass(), // transmission mode | |
| 239 | 16 | m_manager->getLocalEidParam(), // source EID | |
| 240 | 16 | txn->m_history->seq_num, // transaction sequence number | |
| 241 | 16 | txn->m_history->peer_eid, // destination EID | |
| 242 | conditionCode, // condition code | ||
| 243 | 16 | txn->m_crc.getValue(), // checksum | |
| 244 | txn->m_fsize // file size | ||
| 245 | ); | ||
| 246 | |||
| 247 | // Add entity ID TLV on error conditions (optional per CCSDS spec) | ||
| 248 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 15 times.
|
16 | if (conditionCode != ConditionCode::CONDITION_CODE_NO_ERROR) { |
| 249 |
1/1✓ Branch 2 taken 1 times.
|
1 | Cfdp::Tlv tlv; |
| 250 |
2/2✓ Branch 8 taken 1 times.
✓ Branch 11 taken 1 times.
|
1 | tlv.initialize(m_manager->getLocalEidParam()); // Local entity ID |
| 251 |
1/1✓ Branch 2 taken 1 times.
|
1 | eof.appendTlv(tlv); |
| 252 | } | ||
| 253 | |||
| 254 |
1/1✓ Branch 4 taken 16 times.
|
32 | return serializeAndSendPdu(txn, eof); |
| 255 | 16 | } | |
| 256 | |||
| 257 | 13 | Status::T Engine::sendAck(Transaction* txn, | |
| 258 | AckTxnStatus ts, | ||
| 259 | FileDirective dir_code, | ||
| 260 | ConditionCode cc, | ||
| 261 | EntityId peer_eid, | ||
| 262 | TransactionSeq tsn) { | ||
| 263 | 13 | FW_ASSERT( | |
| 264 | (dir_code == FileDirective::FILE_DIRECTIVE_END_OF_FILE) || (dir_code == FileDirective::FILE_DIRECTIVE_FIN), | ||
| 265 | static_cast<U8>(dir_code)); | ||
| 266 | |||
| 267 | // Determine source and destination EIDs based on transaction direction | ||
| 268 | EntityId src_eid; | ||
| 269 | EntityId dst_eid; | ||
| 270 |
2/2✓ Branch 4 taken 7 times.
✓ Branch 5 taken 6 times.
|
13 | if (txn->getHistory()->dir == Direction::DIRECTION_TX) { |
| 271 |
1/1✓ Branch 8 taken 7 times.
|
7 | src_eid = m_manager->getLocalEidParam(); |
| 272 | 7 | dst_eid = peer_eid; | |
| 273 | } else { | ||
| 274 | 6 | src_eid = peer_eid; | |
| 275 |
1/1✓ Branch 8 taken 6 times.
|
6 | dst_eid = m_manager->getLocalEidParam(); |
| 276 | } | ||
| 277 | |||
| 278 | // Create and initialize ACK PDU | ||
| 279 |
1/1✓ Branch 2 taken 13 times.
|
13 | AckPdu ack; |
| 280 | |||
| 281 | // Direction: toward sender for EOF ACK, toward receiver for FIN ACK | ||
| 282 | 13 | Cfdp::PduDirection direction = (dir_code == FileDirective::FILE_DIRECTIVE_END_OF_FILE) | |
| 283 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 6 times.
|
13 | ? Cfdp::PduDirection::DIRECTION_TOWARD_SENDER |
| 284 | : Cfdp::PduDirection::DIRECTION_TOWARD_RECEIVER; | ||
| 285 | |||
| 286 |
1/1✓ Branch 3 taken 13 times.
|
26 | ack.initialize(direction, |
| 287 | 13 | txn->getClass(), // transmission mode | |
| 288 | src_eid, // source EID | ||
| 289 | tsn, // transaction sequence number | ||
| 290 | dst_eid, // destination EID | ||
| 291 | dir_code, // directive being acknowledged | ||
| 292 | 1, // directive subtype code (always 1) | ||
| 293 | cc, // condition code | ||
| 294 | ts // transaction status | ||
| 295 | ); | ||
| 296 | |||
| 297 |
1/1✓ Branch 4 taken 13 times.
|
26 | return serializeAndSendPdu(txn, ack); |
| 298 | 13 | } | |
| 299 | |||
| 300 | 13 | Status::T Engine::sendFin(Transaction* txn, FinDeliveryCode dc, FinFileStatus fs, ConditionCode cc) { | |
| 301 | // Create and initialize FIN PDU | ||
| 302 |
1/1✓ Branch 2 taken 13 times.
|
13 | FinPdu fin; |
| 303 | |||
| 304 | // Direction is toward sender for FIN sent by receiver | ||
| 305 | 13 | Cfdp::PduDirection direction = PduDirection::DIRECTION_TOWARD_SENDER; | |
| 306 | |||
| 307 |
2/2✓ Branch 2 taken 13 times.
✓ Branch 8 taken 13 times.
|
65 | fin.initialize(direction, |
| 308 | 13 | txn->getClass(), // transmission mode | |
| 309 | 13 | txn->m_history->peer_eid, // source EID (receiver) | |
| 310 | 13 | txn->m_history->seq_num, // transaction sequence number | |
| 311 | 13 | m_manager->getLocalEidParam(), // destination EID (sender) | |
| 312 | cc, // condition code | ||
| 313 | static_cast<FinDeliveryCode>(dc), // delivery code | ||
| 314 | static_cast<FinFileStatus>(fs) // file status | ||
| 315 | ); | ||
| 316 | |||
| 317 | // Add entity ID TLV on error conditions (optional per CCSDS spec) | ||
| 318 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 6 times.
|
13 | if (cc != ConditionCode::CONDITION_CODE_NO_ERROR) { |
| 319 |
1/1✓ Branch 2 taken 7 times.
|
7 | Cfdp::Tlv tlv; |
| 320 |
2/2✓ Branch 8 taken 7 times.
✓ Branch 11 taken 7 times.
|
7 | tlv.initialize(m_manager->getLocalEidParam()); // Local entity ID |
| 321 |
1/1✓ Branch 2 taken 7 times.
|
7 | fin.appendTlv(tlv); |
| 322 | } | ||
| 323 | |||
| 324 |
1/1✓ Branch 4 taken 13 times.
|
26 | return serializeAndSendPdu(txn, fin); |
| 325 | 13 | } | |
| 326 | |||
| 327 | 2 | Status::T Engine::sendNak(Transaction* txn, NakPdu& nakPdu) { | |
| 328 | // Verify this is a Class 2 transaction (NAK only used in Class 2) | ||
| 329 | 2 | Class::T tx_class = txn->getClass(); | |
| 330 | 2 | FW_ASSERT(tx_class == Cfdp::Class::CLASS_2, tx_class); | |
| 331 | |||
| 332 | 2 | return serializeAndSendPdu(txn, nakPdu); | |
| 333 | } | ||
| 334 | |||
| 335 | 95 | Status::T Engine::serializeAndSendPdu(Transaction* txn, PduBase& pdu) { | |
| 336 | // Delegate to the channel-based helper; a transaction always carries its channel. | ||
| 337 | 95 | return this->serializeAndSendPduOnChannel(*txn->m_chan, pdu); | |
| 338 | } | ||
| 339 | |||
| 340 | 96 | Status::T Engine::serializeAndSendPduOnChannel(Channel& chan, PduBase& pdu) { | |
| 341 |
1/1✓ Branch 2 taken 96 times.
|
96 | Fw::Buffer buffer; |
| 342 | 96 | Status::T status = Cfdp::Status::SUCCESS; | |
| 343 | |||
| 344 | // Allocate buffer with space for packet descriptor | ||
| 345 |
1/1✓ Branch 7 taken 96 times.
|
96 | const FwSizeType bufferSize = pdu.getBufferSize() + CfdpManager::PACKET_DESCRIPTOR_SIZE; |
| 346 |
1/1✓ Branch 8 taken 96 times.
|
96 | status = m_manager->getPduBuffer(buffer, chan, bufferSize); |
| 347 | |||
| 348 |
1/2✓ Branch 0 taken 96 times.
✗ Branch 1 not taken.
|
96 | if (status == Cfdp::Status::SUCCESS) { |
| 349 | // Serialize to buffer at offset to leave room for descriptor | ||
| 350 |
1/1✓ Branch 2 taken 96 times.
|
192 | Fw::SerialBuffer sb(buffer.getData() + CfdpManager::PACKET_DESCRIPTOR_SIZE, |
| 351 |
2/2✓ Branch 2 taken 96 times.
✓ Branch 7 taken 96 times.
|
192 | buffer.getSize() - CfdpManager::PACKET_DESCRIPTOR_SIZE); |
| 352 |
1/1✓ Branch 6 taken 96 times.
|
96 | Fw::SerializeStatus serStatus = pdu.serializeTo(sb); |
| 353 | |||
| 354 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 95 times.
|
96 | if (serStatus != Fw::FW_SERIALIZE_OK) { |
| 355 | // Log generic PDU serialization error with PDU type | ||
| 356 |
2/2✓ Branch 14 taken 1 times.
✓ Branch 20 taken 1 times.
|
1 | m_manager->log_WARNING_LO_FailPduSerialization(chan.getChannelId(), pdu.getType(), |
| 357 | static_cast<I32>(serStatus)); | ||
| 358 |
1/1✓ Branch 8 taken 1 times.
|
1 | m_manager->returnPduBuffer(chan, buffer); |
| 359 | 1 | status = Cfdp::Status::ERROR; | |
| 360 | } else { | ||
| 361 | // Update buffer size to actual serialized size plus descriptor | ||
| 362 |
2/2✓ Branch 3 taken 95 times.
✓ Branch 6 taken 95 times.
|
95 | buffer.setSize(sb.getSize() + CfdpManager::PACKET_DESCRIPTOR_SIZE); |
| 363 |
1/1✓ Branch 8 taken 95 times.
|
95 | m_manager->sendPduBuffer(chan, buffer); |
| 364 |
1/1✓ Branch 10 taken 95 times.
|
95 | m_manager->incrementSentPdu(chan.getChannelId()); |
| 365 | } | ||
| 366 | 96 | } | |
| 367 | |||
| 368 | 96 | return status; | |
| 369 | 96 | } | |
| 370 | |||
| 371 | 1 | Status::T Engine::sendFinAckStateless(Channel& chan, | |
| 372 | TransactionSeq tsn, | ||
| 373 | EntityId finSrcEid, | ||
| 374 | EntityId finDstEid, | ||
| 375 | ConditionCode cc) { | ||
| 376 | // A FIN has arrived for a downlink transaction we sourced, but no live transaction | ||
| 377 | // remains (it already completed and was recycled). Per CFDP the sender must still | ||
| 378 | // acknowledge a retransmitted FIN; we do so statelessly from the FIN header. | ||
| 379 | // | ||
| 380 | // The FIN was sent toward us (the sender), so its header carries | ||
| 381 | // sourceEid = the transaction source = this local entity, | ||
| 382 | // destEid = the peer (receiver). | ||
| 383 | // The ACK(FIN) we emit travels toward the receiver, mirroring Engine::sendAck's | ||
| 384 | // DIRECTION_TX case: src = local entity, dst = peer. | ||
| 385 |
1/1✓ Branch 2 taken 1 times.
|
1 | AckPdu ack; |
| 386 |
1/1✓ Branch 2 taken 1 times.
|
1 | ack.initialize(Cfdp::PduDirection::DIRECTION_TOWARD_RECEIVER, |
| 387 | Cfdp::Class::CLASS_2, // FIN/ACK only exist in class 2 | ||
| 388 | finSrcEid, // source EID (this local entity) | ||
| 389 | tsn, // transaction sequence number | ||
| 390 | finDstEid, // destination EID (the peer/receiver) | ||
| 391 | FileDirective::FILE_DIRECTIVE_FIN, // directive being acknowledged | ||
| 392 | 1, // directive subtype code (always 1) | ||
| 393 | cc, // echo the FIN's condition code | ||
| 394 | AckTxnStatus::ACK_TXN_STATUS_UNRECOGNIZED // we no longer recognize this transaction | ||
| 395 | ); | ||
| 396 | |||
| 397 |
1/1✓ Branch 4 taken 1 times.
|
2 | return this->serializeAndSendPduOnChannel(chan, ack); |
| 398 | 1 | } | |
| 399 | |||
| 400 | 78 | void Engine::recvMd(Transaction* txn, const MetadataPdu& md) { | |
| 401 | /* store the expected file size in transaction */ | ||
| 402 | 78 | txn->m_fsize = md.getFileSize(); | |
| 403 | |||
| 404 | /* store the filenames in transaction - validation already done during deserialization */ | ||
| 405 | 78 | txn->m_history->fnames.src_filename = md.getSourceFilename(); | |
| 406 | 78 | txn->m_history->fnames.dst_filename = md.getDestFilename(); | |
| 407 | |||
| 408 | 234 | this->m_manager->log_ACTIVITY_LO_MetadataReceived(txn->m_history->fnames.src_filename, | |
| 409 | 156 | txn->m_history->fnames.dst_filename, txn->m_history->seq_num); | |
| 410 | 78 | } | |
| 411 | |||
| 412 | 33 | Status::T Engine::recvFd(Transaction* txn, const FileDataPdu& fd) { | |
| 413 | 33 | Status::T ret = Cfdp::Status::SUCCESS; | |
| 414 | |||
| 415 | // Extract header | ||
| 416 | 33 | const Cfdp::PduHeader& header = fd.asHeader(); | |
| 417 | |||
| 418 | // Check for segment metadata flag (not currently supported) | ||
| 419 |
2/2✓ Branch 2 taken 1 times.
✓ Branch 3 taken 32 times.
|
33 | if (header.hasSegmentMetadata()) { |
| 420 | /* If recv PDU has the "segment_meta_flag" set, this is not currently handled in CF. */ | ||
| 421 | 1 | this->m_manager->log_WARNING_LO_FileDataSegmentMetadata(); | |
| 422 | 1 | this->setTxnStatus(txn, TxnStatus::TXN_STATUS_PROTOCOL_ERROR); | |
| 423 | 1 | this->m_manager->incrementRecvErrors(txn->getChannelId()); | |
| 424 | 1 | ret = Cfdp::Status::ERROR; | |
| 425 | } | ||
| 426 | |||
| 427 | 33 | return ret; | |
| 428 | } | ||
| 429 | |||
| 430 | 12 | Status::T Engine::recvEof(Transaction* txn, const EofPdu& eofPdu) { | |
| 431 | // EOF PDU has been validated during fromBuffer() | ||
| 432 | |||
| 433 | // Process TLVs if present | ||
| 434 | 12 | const Cfdp::TlvList& tlvList = eofPdu.getTlvList(); | |
| 435 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
|
12 | for (U8 i = 0; i < tlvList.getNumTlv(); i++) { |
| 436 | ✗ | const Cfdp::Tlv& tlv = tlvList.getTlv(i); | |
| 437 | ✗ | if (tlv.getType() == Cfdp::TlvType::TLV_TYPE_ENTITY_ID) { | |
| 438 | // Entity ID TLV present - validation not currently performed | ||
| 439 | // Future enhancement: Add validation or logging if required | ||
| 440 | } | ||
| 441 | // Other TLV types can be processed here in the future | ||
| 442 | } | ||
| 443 | |||
| 444 | 12 | return Cfdp::Status::SUCCESS; | |
| 445 | } | ||
| 446 | |||
| 447 | 7 | Status::T Engine::recvFin(Transaction* txn, const FinPdu& finPdu) { | |
| 448 | // FIN PDU has been validated during fromBuffer() | ||
| 449 | |||
| 450 | // Process TLVs if present | ||
| 451 | 7 | const Cfdp::TlvList& tlvList = finPdu.getTlvList(); | |
| 452 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
|
7 | for (U8 i = 0; i < tlvList.getNumTlv(); i++) { |
| 453 | ✗ | const Cfdp::Tlv& tlv = tlvList.getTlv(i); | |
| 454 | ✗ | if (tlv.getType() == Cfdp::TlvType::TLV_TYPE_ENTITY_ID) { | |
| 455 | // Entity ID TLV present - validation not currently performed | ||
| 456 | // Future enhancement: Add validation or logging if required | ||
| 457 | } | ||
| 458 | // Other TLV types can be processed here in the future | ||
| 459 | } | ||
| 460 | |||
| 461 | 7 | return Cfdp::Status::SUCCESS; | |
| 462 | } | ||
| 463 | |||
| 464 | 4 | Status::T Engine::recvNak(Transaction* txn, const NakPdu& pdu) { | |
| 465 | // NAK PDU has been validated during fromBuffer() | ||
| 466 | 4 | return Cfdp::Status::SUCCESS; | |
| 467 | } | ||
| 468 | |||
| 469 | ✗ | void Engine::recvDrop(Transaction* txn, const Fw::Buffer& buffer) { | |
| 470 | ✗ | this->m_manager->incrementRecvDropped(txn->getChannelId()); | |
| 471 | (void)buffer; // Unused - we're just dropping the PDU | ||
| 472 | ✗ | } | |
| 473 | |||
| 474 | 1 | void Engine::recvHold(Transaction* txn, const Fw::Buffer& buffer) { | |
| 475 | // anything received in this state is considered spurious | ||
| 476 | 1 | this->m_manager->incrementRecvSpurious(txn->getChannelId()); | |
| 477 | |||
| 478 | // | ||
| 479 | // Normally we do not expect PDUs for a transaction in holdover, because | ||
| 480 | // from the local point of view it is completed and done. But the reason | ||
| 481 | // for the holdover is because the remote side might not have gotten all | ||
| 482 | // the acks and could still be [re-]sending us PDUs for anything it does | ||
| 483 | // not know we got already. | ||
| 484 | // | ||
| 485 | // If an R2 sent FIN, it's possible that the peer missed the | ||
| 486 | // FIN-ACK and is sending another FIN. In that case we need to send | ||
| 487 | // another ACK. | ||
| 488 | // | ||
| 489 | |||
| 490 | // currently the only thing we will re-ack is the FIN. | ||
| 491 | |||
| 492 | // Use peekPduType to determine the PDU type | ||
| 493 | 1 | Cfdp::PduTypeEnum::T pduType = Cfdp::peekPduType(buffer); | |
| 494 | |||
| 495 | // Check if this is a FIN PDU for a Class 2 transaction | ||
| 496 |
2/6✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
|
1 | if (pduType == Cfdp::PduTypeEnum::FINISHED && txn->getClass() == Cfdp::Class::CLASS_2) { |
| 497 | // Deserialize FIN PDU | ||
| 498 | ✗ | FinPdu fin; | |
| 499 | ✗ | Fw::SerialBuffer sb2(const_cast<U8*>(buffer.getData()), buffer.getSize()); | |
| 500 | ✗ | sb2.setBuffLen(buffer.getSize()); | |
| 501 | |||
| 502 | ✗ | Fw::SerializeStatus deserStatus = fin.deserializeFrom(sb2); | |
| 503 | ✗ | if (deserStatus == Fw::FW_SERIALIZE_OK) { | |
| 504 | // Re-send the FIN-ACK | ||
| 505 | ✗ | this->sendAck(txn, AckTxnStatus::ACK_TXN_STATUS_TERMINATED, FileDirective::FILE_DIRECTIVE_FIN, | |
| 506 | ✗ | fin.getConditionCode(), txn->m_history->peer_eid, txn->m_history->seq_num); | |
| 507 | } | ||
| 508 | // Note: Deserialization errors are silently ignored in hold state | ||
| 509 | // as we're just trying to be helpful by re-acknowledging FIN if we can | ||
| 510 | ✗ | } | |
| 511 | 1 | } | |
| 512 | |||
| 513 | 81 | bool Engine::recvInit(Transaction* txn, const Fw::Buffer& buffer) { | |
| 514 | // Use peekPduType to determine the PDU type before deserializing | ||
| 515 |
1/1✓ Branch 1 taken 81 times.
|
81 | Cfdp::PduTypeEnum::T pduType = Cfdp::peekPduType(buffer); |
| 516 | |||
| 517 | // First parse header to get transaction information | ||
| 518 | // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only) | ||
| 519 |
3/3✓ Branch 5 taken 81 times.
✓ Branch 11 taken 81 times.
✓ Branch 14 taken 81 times.
|
81 | Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize()); |
| 520 |
2/2✓ Branch 5 taken 81 times.
✓ Branch 8 taken 81 times.
|
81 | sb.setBuffLen(buffer.getSize()); |
| 521 | |||
| 522 | 81 | Cfdp::PduHeader header; | |
| 523 |
1/1✓ Branch 1 taken 81 times.
|
81 | Fw::SerializeStatus status = header.fromSerialBuffer(sb); |
| 524 | |||
| 525 |
1/2✓ Branch 0 taken 81 times.
✗ Branch 1 not taken.
|
81 | if (status == Fw::FW_SERIALIZE_OK) { |
| 526 | 81 | TransactionSeq transactionSeq = header.getTransactionSeq(); | |
| 527 | 81 | EntityId sourceEid = header.getSourceEid(); | |
| 528 | 81 | Class::T txmMode = header.getTxmMode(); | |
| 529 | |||
| 530 | // only RX transactions dare tread here | ||
| 531 | 81 | txn->m_history->seq_num = transactionSeq; | |
| 532 | |||
| 533 | // peer_eid is always the remote partner. src_eid is always the transaction source. | ||
| 534 | // in this case, they are the same | ||
| 535 | 81 | txn->m_history->peer_eid = sourceEid; | |
| 536 | 81 | txn->m_history->src_eid = sourceEid; | |
| 537 | |||
| 538 | // all RX transactions will need a chunk list to track file segments | ||
| 539 |
1/2✓ Branch 2 taken 81 times.
✗ Branch 3 not taken.
|
81 | if (txn->m_chunks == nullptr) { |
| 540 |
1/1✓ Branch 4 taken 81 times.
|
81 | txn->m_chunks = txn->m_chan->findUnusedChunks(Direction::DIRECTION_RX); |
| 541 | } | ||
| 542 |
2/2✓ Branch 2 taken 1 times.
✓ Branch 3 taken 80 times.
|
81 | if (txn->m_chunks == nullptr) { |
| 543 |
1/1✓ Branch 9 taken 1 times.
|
1 | this->m_manager->log_WARNING_LO_ChunklistUnavailable(transactionSeq); |
| 544 | } else { | ||
| 545 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 77 times.
|
80 | if (pduType == Cfdp::PduTypeEnum::FILE_DATA) { |
| 546 | // file data PDU | ||
| 547 | // being idle and receiving a file data PDU means that no active transaction knew | ||
| 548 | // about the transaction in progress, so most likely PDUs were missed. | ||
| 549 | |||
| 550 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (txmMode == Cfdp::Class::CLASS_1) { |
| 551 | // R1, can't do anything without metadata first | ||
| 552 | ✗ | txn->m_state = TxnState::TXN_STATE_DROP; // drop all incoming | |
| 553 | // use inactivity timer to ultimately free the state | ||
| 554 | } else { | ||
| 555 | // R2 can handle missing metadata, so go ahead and create a temp file | ||
| 556 | 3 | txn->m_state = TxnState::TXN_STATE_R2; | |
| 557 | 3 | txn->m_txn_class = Cfdp::Class::CLASS_2; | |
| 558 |
1/1✓ Branch 2 taken 3 times.
|
3 | txn->rInit(); |
| 559 | 3 | return true; // Request re-dispatch to enter r2 state handler | |
| 560 | } | ||
| 561 |
2/2✓ Branch 0 taken 76 times.
✓ Branch 1 taken 1 times.
|
77 | } else if (pduType == Cfdp::PduTypeEnum::METADATA) { |
| 562 | // file directive PDU with metadata - this is the expected case for starting a new RX transaction | ||
| 563 |
1/1✓ Branch 2 taken 76 times.
|
76 | MetadataPdu md; |
| 564 |
3/3✓ Branch 5 taken 76 times.
✓ Branch 11 taken 76 times.
✓ Branch 14 taken 76 times.
|
76 | Fw::SerialBuffer sb2(const_cast<U8*>(buffer.getData()), buffer.getSize()); |
| 565 |
2/2✓ Branch 5 taken 76 times.
✓ Branch 8 taken 76 times.
|
76 | sb2.setBuffLen(buffer.getSize()); |
| 566 | |||
| 567 |
1/1✓ Branch 2 taken 76 times.
|
76 | Fw::SerializeStatus deserStatus = md.deserializeFrom(sb2); |
| 568 |
2/2✓ Branch 0 taken 75 times.
✓ Branch 1 taken 1 times.
|
76 | if (deserStatus == Fw::FW_SERIALIZE_OK) { |
| 569 |
1/1✓ Branch 4 taken 75 times.
|
75 | this->recvMd(txn, md); |
| 570 | |||
| 571 | // NOTE: whether or not class 1 or 2, get a free chunks. It's cheap, and simplifies cleanup path | ||
| 572 |
2/2✓ Branch 0 taken 66 times.
✓ Branch 1 taken 9 times.
|
75 | txn->m_state = txmMode == Cfdp::Class::CLASS_1 ? TxnState::TXN_STATE_R1 : TxnState::TXN_STATE_R2; |
| 573 | 75 | txn->m_txn_class = txmMode; | |
| 574 | 75 | txn->m_flags.rx.md_recv = true; | |
| 575 |
1/1✓ Branch 2 taken 75 times.
|
75 | txn->rInit(); // initialize R |
| 576 | } else { | ||
| 577 |
1/1✓ Branch 11 taken 1 times.
|
1 | m_manager->log_WARNING_LO_FailMetadataPduDeserialization(txn->getChannelId(), |
| 578 | static_cast<I32>(deserStatus)); | ||
| 579 | } | ||
| 580 | 76 | } else { | |
| 581 | // Unexpected PDU type in init state | ||
| 582 |
1/1✓ Branch 9 taken 1 times.
|
1 | this->m_manager->log_WARNING_LO_UnhandledPduInIdleState(); |
| 583 |
1/1✓ Branch 10 taken 1 times.
|
1 | this->m_manager->incrementRecvErrors(txn->getChannelId()); |
| 584 | } | ||
| 585 | } | ||
| 586 | |||
| 587 |
2/2✓ Branch 1 taken 3 times.
✓ Branch 2 taken 75 times.
|
78 | if (txn->m_state == TxnState::TXN_STATE_INIT) { |
| 588 | // state was not changed, so free the transaction | ||
| 589 |
1/1✓ Branch 4 taken 3 times.
|
3 | this->finishTransaction(txn, false); |
| 590 | } | ||
| 591 | } else { | ||
| 592 | ✗ | m_manager->log_WARNING_LO_FailPduHeaderDeserialization(txn->getChannelId(), status); | |
| 593 | } | ||
| 594 | 78 | return false; // No re-dispatch needed | |
| 595 | 81 | } | |
| 596 | |||
| 597 | 147 | void Engine::receivePdu(U8 chan_id, const Fw::Buffer& buffer) { | |
| 598 | 147 | Transaction* txn = nullptr; | |
| 599 | 147 | Channel* chan = nullptr; | |
| 600 | |||
| 601 | 147 | FW_ASSERT(chan_id < Cfdp::NumChannels, chan_id, Cfdp::NumChannels); | |
| 602 | |||
| 603 | 147 | chan = m_channels[chan_id]; | |
| 604 | 147 | FW_ASSERT(chan != nullptr); | |
| 605 | |||
| 606 | // Parse the header to get transaction routing info | ||
| 607 | // Avoid full PDU deserialization here to defer it until the appropriate handler | ||
| 608 | // const_cast: Fw::SerialBuffer requires non-const U8* even for deserialization (read-only) | ||
| 609 |
3/3✓ Branch 5 taken 147 times.
✓ Branch 11 taken 147 times.
✓ Branch 14 taken 147 times.
|
147 | Fw::SerialBuffer sb(const_cast<U8*>(buffer.getData()), buffer.getSize()); |
| 610 |
2/2✓ Branch 5 taken 147 times.
✓ Branch 8 taken 147 times.
|
147 | sb.setBuffLen(buffer.getSize()); |
| 611 | |||
| 612 | 147 | Cfdp::PduHeader header; | |
| 613 |
1/1✓ Branch 1 taken 147 times.
|
147 | Fw::SerializeStatus status = header.fromSerialBuffer(sb); |
| 614 | |||
| 615 |
2/2✓ Branch 0 taken 146 times.
✓ Branch 1 taken 1 times.
|
147 | if (status == Fw::FW_SERIALIZE_OK) { |
| 616 | // Increment received PDU counter for PDUs with valid headers | ||
| 617 |
1/1✓ Branch 8 taken 146 times.
|
146 | this->m_manager->incrementRecvPdu(chan_id); |
| 618 | |||
| 619 | 146 | TransactionSeq transactionSeq = header.getTransactionSeq(); | |
| 620 | 146 | EntityId sourceEid = header.getSourceEid(); | |
| 621 | 146 | EntityId destEid = header.getDestEid(); | |
| 622 | |||
| 623 | // Look up transaction by sequence number | ||
| 624 |
1/1✓ Branch 2 taken 146 times.
|
146 | txn = chan->findTransactionBySequenceNumber(transactionSeq, sourceEid); |
| 625 | |||
| 626 |
2/2✓ Branch 0 taken 84 times.
✓ Branch 1 taken 62 times.
|
146 | if (txn == nullptr) { |
| 627 | // A retransmitted FIN can arrive for a downlink transaction we sourced after | ||
| 628 | // that transaction has already completed and been recycled (e.g. the peer | ||
| 629 | // kept retransmitting FIN across a lossy/quiet link). Per CFDP the sender | ||
| 630 | // must still acknowledge it, so re-ACK statelessly from the FIN header. | ||
| 631 | // Such a FIN carries sourceEid == our local entity (we were the source) and | ||
| 632 | // destEid == the peer, so it would otherwise be dropped as InvalidDestinationEid. | ||
| 633 |
5/5✓ Branch 1 taken 84 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 83 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 83 times.
|
85 | if (Cfdp::peekPduType(buffer) == Cfdp::PduTypeEnum::FINISHED && |
| 634 |
2/3✓ Branch 8 taken 1 times.
✓ Branch 10 taken 1 times.
✗ Branch 11 not taken.
|
1 | sourceEid == this->m_manager->getLocalEidParam()) { |
| 635 |
1/1✓ Branch 2 taken 1 times.
|
1 | FinPdu fin; |
| 636 |
3/3✓ Branch 5 taken 1 times.
✓ Branch 11 taken 1 times.
✓ Branch 14 taken 1 times.
|
1 | Fw::SerialBuffer finSb(const_cast<U8*>(buffer.getData()), buffer.getSize()); |
| 637 |
2/2✓ Branch 5 taken 1 times.
✓ Branch 8 taken 1 times.
|
1 | finSb.setBuffLen(buffer.getSize()); |
| 638 |
2/3✓ Branch 2 taken 1 times.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
1 | if (fin.deserializeFrom(finSb) == Fw::FW_SERIALIZE_OK) { |
| 639 |
1/1✓ Branch 7 taken 1 times.
|
1 | this->sendFinAckStateless(*chan, transactionSeq, sourceEid, destEid, fin.getConditionCode()); |
| 640 |
1/1✓ Branch 9 taken 1 times.
|
1 | this->m_manager->log_DIAGNOSTIC_TxLateFinAcked(sourceEid, transactionSeq); |
| 641 | } else { | ||
| 642 | ✗ | this->m_manager->log_WARNING_LO_FailFinPduDeserialization( | |
| 643 | chan_id, static_cast<I32>(Fw::FW_DESERIALIZE_FORMAT_ERROR)); | ||
| 644 | } | ||
| 645 | 1 | } | |
| 646 | // if no match found, then it must be the case that we would be the destination entity id, so verify it | ||
| 647 |
3/3✓ Branch 8 taken 83 times.
✓ Branch 10 taken 82 times.
✓ Branch 11 taken 1 times.
|
83 | else if (destEid == this->m_manager->getLocalEidParam()) { |
| 648 | // we didn't find a match, so assign it to a transaction | ||
| 649 | // assume this is initiating an RX transaction, as TX transactions are only commanded | ||
| 650 |
1/1✓ Branch 6 taken 82 times.
|
82 | txn = this->startRxTransaction(chan->getChannelId()); |
| 651 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 81 times.
|
82 | if (txn == nullptr) { |
| 652 |
1/1✓ Branch 9 taken 1 times.
|
1 | this->m_manager->log_WARNING_LO_RxTransactionLimitReached(sourceEid, transactionSeq); |
| 653 | } | ||
| 654 | } else { | ||
| 655 |
1/1✓ Branch 9 taken 1 times.
|
1 | this->m_manager->log_WARNING_LO_InvalidDestinationEid(destEid); |
| 656 | } | ||
| 657 | } | ||
| 658 | |||
| 659 |
2/2✓ Branch 0 taken 143 times.
✓ Branch 1 taken 3 times.
|
146 | if (txn != nullptr) { |
| 660 | // found one! Send it to the transaction state processor | ||
| 661 |
1/1✓ Branch 4 taken 143 times.
|
143 | this->dispatchRecv(txn, buffer); |
| 662 | } else { | ||
| 663 | // Transaction limit reached - EVR already emitted by findOrStartRxTransaction | ||
| 664 | } | ||
| 665 | } else { | ||
| 666 | // Invalid PDU header, drop packet | ||
| 667 |
1/1✓ Branch 9 taken 1 times.
|
1 | m_manager->log_WARNING_LO_FailPduHeaderDeserialization(chan_id, static_cast<I32>(status)); |
| 668 | } | ||
| 669 | 294 | } | |
| 670 | |||
| 671 | 1 | void Engine::setChannelFlowState(U8 channelId, Flow::T flowState) { | |
| 672 | 1 | FW_ASSERT(channelId < Cfdp::NumChannels, channelId, Cfdp::NumChannels); | |
| 673 | 1 | m_channels[channelId]->setFlowState(flowState); | |
| 674 | 1 | } | |
| 675 | |||
| 676 | 3 | Status::T Engine::setSuspendResumeTransaction(U8 channelId, | |
| 677 | TransactionSeq transactionSeq, | ||
| 678 | EntityId entityId, | ||
| 679 | SuspendResume::T action) { | ||
| 680 | 3 | Status::T status = Status::ERROR; | |
| 681 | |||
| 682 | 3 | FW_ASSERT(channelId < Cfdp::NumChannels, channelId, Cfdp::NumChannels); | |
| 683 | |||
| 684 | 3 | Channel* chan = m_channels[channelId]; | |
| 685 | 3 | Transaction* txn = chan->findTransactionBySequenceNumber(transactionSeq, entityId); | |
| 686 | |||
| 687 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
|
3 | if (txn != nullptr) { |
| 688 | 2 | txn->m_flags.com.suspended = (action == SuspendResume::SUSPEND); | |
| 689 | 2 | status = Status::SUCCESS; | |
| 690 | } | ||
| 691 | |||
| 692 | 3 | return status; | |
| 693 | } | ||
| 694 | |||
| 695 | 2 | Status::T Engine::cancelTransactionBySeq(U8 channelId, TransactionSeq transactionSeq, EntityId entityId) { | |
| 696 | 2 | Status::T status = Status::ERROR; | |
| 697 | |||
| 698 | 2 | FW_ASSERT(channelId < Cfdp::NumChannels, channelId, Cfdp::NumChannels); | |
| 699 | |||
| 700 | 2 | Channel* chan = m_channels[channelId]; | |
| 701 | 2 | Transaction* txn = chan->findTransactionBySequenceNumber(transactionSeq, entityId); | |
| 702 | |||
| 703 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | if (txn != nullptr) { |
| 704 | 1 | this->cancelTransaction(txn); | |
| 705 | 1 | status = Status::SUCCESS; | |
| 706 | } | ||
| 707 | |||
| 708 | 2 | return status; | |
| 709 | } | ||
| 710 | |||
| 711 | 2 | Status::T Engine::abandonTransaction(U8 channelId, TransactionSeq transactionSeq, EntityId entityId) { | |
| 712 | 2 | Status::T status = Status::ERROR; | |
| 713 | |||
| 714 | 2 | FW_ASSERT(channelId < Cfdp::NumChannels, channelId, Cfdp::NumChannels); | |
| 715 | |||
| 716 | 2 | Channel* chan = m_channels[channelId]; | |
| 717 | 2 | Transaction* txn = chan->findTransactionBySequenceNumber(transactionSeq, entityId); | |
| 718 | |||
| 719 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | if (txn != nullptr) { |
| 720 | 1 | this->finishTransaction(txn, false); | |
| 721 | 1 | status = Status::SUCCESS; | |
| 722 | } | ||
| 723 | |||
| 724 | 2 | return status; | |
| 725 | } | ||
| 726 | |||
| 727 | 15 | void Engine::txFileInitiate(Transaction* txn, | |
| 728 | Class::T cfdp_class, | ||
| 729 | Keep::T keep, | ||
| 730 | U8 chan, | ||
| 731 | U8 priority, | ||
| 732 | EntityId dest_id) { | ||
| 733 | 15 | txn->initTxFile(cfdp_class, keep, chan, priority); | |
| 734 | |||
| 735 | // Increment sequence number for new transaction | ||
| 736 | 15 | ++this->m_seqNum; | |
| 737 | |||
| 738 | // Capture info for history | ||
| 739 | 15 | txn->m_history->seq_num = this->m_seqNum; | |
| 740 | 15 | txn->m_history->src_eid = m_manager->getLocalEidParam(); | |
| 741 | 15 | txn->m_history->peer_eid = dest_id; | |
| 742 | |||
| 743 | 15 | txn->m_chan->insertSortPrio(txn, QueueId::PEND); | |
| 744 | 15 | } | |
| 745 | |||
| 746 | 17 | Status::T Engine::txFile(const Fw::String& src_filename, | |
| 747 | const Fw::String& dst_filename, | ||
| 748 | Class::T cfdp_class, | ||
| 749 | Keep::T keep, | ||
| 750 | U8 chan_num, | ||
| 751 | U8 priority, | ||
| 752 | EntityId dest_id, | ||
| 753 | TransactionInitType initType) { | ||
| 754 | Transaction* txn; | ||
| 755 | 17 | Channel* chan = nullptr; | |
| 756 | |||
| 757 | 17 | FW_ASSERT(chan_num < Cfdp::NumChannels, chan_num, Cfdp::NumChannels); | |
| 758 | 17 | chan = m_channels[chan_num]; | |
| 759 | |||
| 760 | 17 | Status::T ret = Cfdp::Status::SUCCESS; | |
| 761 | |||
| 762 |
2/2✓ Branch 2 taken 15 times.
✓ Branch 3 taken 2 times.
|
17 | if (chan->getNumCmdTx() < MaxCommandedPlaybackFilesPerChan) { |
| 763 | 15 | txn = chan->findUnusedTransaction(Direction::DIRECTION_TX); | |
| 764 | } else { | ||
| 765 | 2 | txn = nullptr; | |
| 766 | } | ||
| 767 | |||
| 768 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 15 times.
|
17 | if (txn == nullptr) { |
| 769 | 2 | this->m_manager->log_WARNING_LO_MaxTxTransactionsReached(); | |
| 770 | 2 | ret = Cfdp::Status::ERROR; | |
| 771 | } else { | ||
| 772 | // NOTE: the caller of this function ensures the provided src and dst filenames are nullptr terminated | ||
| 773 | |||
| 774 | 15 | txn->m_history->fnames.src_filename = src_filename; | |
| 775 | 15 | txn->m_history->fnames.dst_filename = dst_filename; | |
| 776 | 15 | this->txFileInitiate(txn, cfdp_class, keep, chan_num, priority, dest_id); | |
| 777 | |||
| 778 | 15 | chan->incrementCmdTxCounter(); | |
| 779 | 15 | txn->m_flags.tx.cmd_tx = true; | |
| 780 | |||
| 781 | // Set transaction initiation type | ||
| 782 | 15 | txn->m_initType = initType; | |
| 783 | |||
| 784 | // Log transaction queued event | ||
| 785 | 15 | this->m_manager->log_ACTIVITY_LO_TxFileQueued(txn->m_history->fnames.src_filename, txn->m_history->seq_num); | |
| 786 | } | ||
| 787 | |||
| 788 | 17 | return ret; | |
| 789 | } | ||
| 790 | |||
| 791 | 82 | Transaction* Engine::startRxTransaction(U8 chan_num) { | |
| 792 | 82 | Channel* chan = nullptr; | |
| 793 | Transaction* txn; | ||
| 794 | |||
| 795 | 82 | FW_ASSERT(chan_num < Cfdp::NumChannels, chan_num, Cfdp::NumChannels); | |
| 796 | 82 | chan = m_channels[chan_num]; | |
| 797 | |||
| 798 | // if (CF_AppData.hk.Payload.channel_hk[chan_num].q_size[QueueId::RX] < CF_MAX_SIMULTANEOUS_RX) | ||
| 799 | // { | ||
| 800 | // txn = chan->findUnusedTransaction(Direction::DIRECTION_RX); | ||
| 801 | // } | ||
| 802 | // else | ||
| 803 | // { | ||
| 804 | // txn = nullptr; | ||
| 805 | // } | ||
| 806 | // Receive transactions are limited by MaxRxTransactions parameter | ||
| 807 | 82 | txn = chan->findUnusedTransaction(Direction::DIRECTION_RX); | |
| 808 | |||
| 809 |
2/2✓ Branch 0 taken 81 times.
✓ Branch 1 taken 1 times.
|
82 | if (txn != nullptr) { |
| 810 | // set default FIN status | ||
| 811 | 81 | txn->m_state_data.receive.r2.dc = FinDeliveryCode::FIN_DELIVERY_CODE_INCOMPLETE; | |
| 812 | 81 | txn->m_state_data.receive.r2.fs = FinFileStatus::FIN_FILE_STATUS_DISCARDED; | |
| 813 | |||
| 814 | 81 | txn->m_flags.com.q_index = QueueId::RX; | |
| 815 | 81 | chan->insertBackInQueue(static_cast<QueueId::T>(txn->m_flags.com.q_index), &txn->m_cl_node); | |
| 816 | } | ||
| 817 | |||
| 818 | 82 | return txn; | |
| 819 | } | ||
| 820 | |||
| 821 | 7 | Status::T Engine::playbackDirInitiate(Playback* pb, | |
| 822 | const Fw::String& src_filename, | ||
| 823 | const Fw::String& dst_filename, | ||
| 824 | Class::T cfdp_class, | ||
| 825 | Keep::T keep, | ||
| 826 | U8 chan, | ||
| 827 | U8 priority, | ||
| 828 | EntityId dest_id) { | ||
| 829 | 7 | Status::T status = Cfdp::Status::SUCCESS; | |
| 830 | Os::Directory::Status dirStatus; | ||
| 831 | |||
| 832 | // make sure the directory can be open | ||
| 833 | 7 | dirStatus = pb->dir.open(src_filename.toChar(), Os::Directory::READ); | |
| 834 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 5 times.
|
7 | if (dirStatus != Os::Directory::OP_OK) { |
| 835 | 2 | this->m_manager->log_WARNING_LO_PlaybackDirOpenFailed(src_filename, dirStatus); | |
| 836 | 2 | this->m_manager->incrementFaultDirectoryRead(chan); | |
| 837 | 2 | status = Cfdp::Status::ERROR; | |
| 838 | } else { | ||
| 839 | 5 | pb->diropen = true; | |
| 840 | 5 | pb->busy = true; | |
| 841 | 5 | pb->keep = keep; | |
| 842 | 5 | pb->priority = priority; | |
| 843 | 5 | pb->dest_id = dest_id; | |
| 844 | 5 | pb->cfdp_class = cfdp_class; | |
| 845 | |||
| 846 | // NOTE: the caller of this function ensures the provided src and dst filenames are nullptr terminated | ||
| 847 | 5 | pb->fnames.src_filename = src_filename; | |
| 848 | 5 | pb->fnames.dst_filename = dst_filename; | |
| 849 | } | ||
| 850 | |||
| 851 | // the executor will start the transfer next cycle | ||
| 852 | 7 | return status; | |
| 853 | } | ||
| 854 | |||
| 855 | 7 | Status::T Engine::playbackDir(const Fw::String& src_filename, | |
| 856 | const Fw::String& dst_filename, | ||
| 857 | Class::T cfdp_class, | ||
| 858 | Keep::T keep, | ||
| 859 | U8 chan, | ||
| 860 | U8 priority, | ||
| 861 | EntityId dest_id) { | ||
| 862 | U32 i; | ||
| 863 | Playback* pb; | ||
| 864 | Status::T status; | ||
| 865 | |||
| 866 | // Loop through the channel's playback directories to find an open slot | ||
| 867 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1 times.
|
10 | for (i = 0; i < MaxCommandedPlaybackDirectoriesPerChan; ++i) { |
| 868 | 9 | pb = m_channels[chan]->getPlayback(i); | |
| 869 |
3/4✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 3 times.
|
9 | if (!pb->busy) { |
| 870 | 6 | break; | |
| 871 | } | ||
| 872 | } | ||
| 873 | |||
| 874 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 6 times.
|
7 | if (i == MaxCommandedPlaybackDirectoriesPerChan) { |
| 875 | 1 | this->m_manager->log_WARNING_LO_PlaybackDirSlotUnavailable(); | |
| 876 | 1 | status = Cfdp::Status::ERROR; | |
| 877 | } else { | ||
| 878 | 6 | status = this->playbackDirInitiate(pb, src_filename, dst_filename, cfdp_class, keep, chan, priority, dest_id); | |
| 879 | } | ||
| 880 | |||
| 881 | 7 | return status; | |
| 882 | } | ||
| 883 | |||
| 884 | 5 | Status::T Engine::startPollDir(U8 chanId, | |
| 885 | U8 pollId, | ||
| 886 | const Fw::String& srcDir, | ||
| 887 | const Fw::String& dstDir, | ||
| 888 | Class::T cfdp_class, | ||
| 889 | U8 priority, | ||
| 890 | EntityId destEid, | ||
| 891 | U32 intervalSec) { | ||
| 892 | 5 | Status::T status = Cfdp::Status::SUCCESS; | |
| 893 | 5 | CfdpPollDir* pd = nullptr; | |
| 894 | |||
| 895 | 5 | FW_ASSERT(chanId < Cfdp::NumChannels, chanId, Cfdp::NumChannels); | |
| 896 | 5 | FW_ASSERT(pollId < MaxPollingDirPerChan, pollId, MaxPollingDirPerChan); | |
| 897 | |||
| 898 | // First check if the poll directory is already in use | ||
| 899 | 5 | pd = m_channels[chanId]->getPollDir(pollId); | |
| 900 |
2/2✓ Branch 6 taken 4 times.
✓ Branch 7 taken 1 times.
|
5 | if (pd->enabled == Fw::Enabled::DISABLED) { |
| 901 | // Populate arguments | ||
| 902 | 4 | pd->intervalSec = intervalSec; | |
| 903 | 4 | pd->priority = priority; | |
| 904 | 4 | pd->cfdpClass = cfdp_class; | |
| 905 | 4 | pd->destEid = destEid; | |
| 906 | 4 | pd->srcDir = srcDir; | |
| 907 | 4 | pd->dstDir = dstDir; | |
| 908 | |||
| 909 | // Set timer and enable polling | ||
| 910 | 4 | pd->intervalTimer.setTimer(pd->intervalSec); | |
| 911 | 4 | pd->enabled = Fw::Enabled::ENABLED; | |
| 912 | } else { | ||
| 913 | // Poll directory slot already in use | ||
| 914 | 1 | this->m_manager->log_WARNING_LO_PollDirBusy(chanId, pollId); | |
| 915 | 1 | status = Cfdp::Status::ERROR; | |
| 916 | } | ||
| 917 | |||
| 918 | 5 | return status; | |
| 919 | } | ||
| 920 | |||
| 921 | 5 | Status::T Engine::stopPollDir(U8 chanId, U8 pollId) { | |
| 922 | 5 | Status::T status = Cfdp::Status::SUCCESS; | |
| 923 | 5 | CfdpPollDir* pd = nullptr; | |
| 924 | |||
| 925 | 5 | FW_ASSERT(chanId < Cfdp::NumChannels, chanId, Cfdp::NumChannels); | |
| 926 | 5 | FW_ASSERT(pollId < MaxPollingDirPerChan, pollId, MaxPollingDirPerChan); | |
| 927 | |||
| 928 | // Check if the poll directory is in use | ||
| 929 | 5 | pd = m_channels[chanId]->getPollDir(pollId); | |
| 930 |
2/2✓ Branch 6 taken 4 times.
✓ Branch 7 taken 1 times.
|
5 | if (pd->enabled == Fw::Enabled::ENABLED) { |
| 931 | // Clear poll directory arguments | ||
| 932 | 4 | pd->intervalSec = 0; | |
| 933 | 4 | pd->priority = 0; | |
| 934 | 4 | pd->cfdpClass = static_cast<Class::T>(0); | |
| 935 | 4 | pd->destEid = static_cast<EntityId>(0); | |
| 936 | 4 | pd->srcDir = ""; | |
| 937 | 4 | pd->dstDir = ""; | |
| 938 | |||
| 939 | // Disable timer and polling | ||
| 940 | 4 | pd->intervalTimer.disableTimer(); | |
| 941 | 4 | pd->enabled = Fw::Enabled::DISABLED; | |
| 942 | } else { | ||
| 943 | // Poll directory not active - cannot stop | ||
| 944 | 1 | this->m_manager->log_WARNING_LO_PollDirNotActive(chanId, pollId); | |
| 945 | 1 | status = Cfdp::Status::ERROR; | |
| 946 | } | ||
| 947 | |||
| 948 | 5 | return status; | |
| 949 | } | ||
| 950 | |||
| 951 | 1947 | void Engine::cycle(void) { | |
| 952 | U32 i; | ||
| 953 | |||
| 954 |
2/2✓ Branch 0 taken 3894 times.
✓ Branch 1 taken 1947 times.
|
5841 | for (i = 0; i < Cfdp::NumChannels; ++i) { |
| 955 | 3894 | Channel* chan = m_channels[i]; | |
| 956 | 3894 | FW_ASSERT(chan != nullptr); | |
| 957 | |||
| 958 | 3894 | chan->resetOutgoingCounter(); | |
| 959 | |||
| 960 |
1/2✓ Branch 2 taken 3894 times.
✗ Branch 3 not taken.
|
3894 | if (chan->getFlowState() == Cfdp::Flow::NOT_FROZEN) { |
| 961 | // handle ticks before tx cycle. Do this because there may be a limited number of TX messages available | ||
| 962 | // this cycle, and it's important to respond to class 2 ACK/NAK more than it is to send new filedata | ||
| 963 | // PDUs. | ||
| 964 | |||
| 965 | // cycle all transactions (tick) | ||
| 966 | 3894 | chan->tickTransactions(); | |
| 967 | |||
| 968 | // cycle the current tx transaction | ||
| 969 | 3894 | chan->cycleTx(); | |
| 970 | |||
| 971 | 3894 | chan->processPlaybackDirectories(); | |
| 972 | 3894 | chan->processPollingDirectories(); | |
| 973 | } | ||
| 974 | } | ||
| 975 | 1947 | } | |
| 976 | |||
| 977 | 38 | void Engine::finishTransaction(Transaction* txn, bool keep_history) { | |
| 978 |
2/2✓ Branch 2 taken 1 times.
✓ Branch 3 taken 37 times.
|
38 | if (txn->m_flags.com.q_index == QueueId::FREE) { |
| 979 | 1 | this->m_manager->log_DIAGNOSTIC_ResetFreedTransaction(); | |
| 980 | 1 | return; | |
| 981 | } | ||
| 982 | |||
| 983 | // this should always be | ||
| 984 | 37 | FW_ASSERT(txn->m_chan != nullptr); | |
| 985 | |||
| 986 | // If this was on the TXA queue (transmit side) then we need to move it out | ||
| 987 | // so the tick processor will stop trying to actively transmit something - | ||
| 988 | // it should move on to the next transaction. | ||
| 989 | // | ||
| 990 | // RX transactions can stay on the RX queue, that does not hurt anything | ||
| 991 | // because they are only triggered when a PDU comes in matching that seq_num | ||
| 992 | // (RX queue is not separated into A/W parts) | ||
| 993 |
2/2✓ Branch 2 taken 5 times.
✓ Branch 3 taken 32 times.
|
37 | if (txn->m_flags.com.q_index == QueueId::TXA) { |
| 994 | 5 | txn->m_chan->dequeueTransaction(txn); | |
| 995 | 5 | txn->m_chan->insertSortPrio(txn, QueueId::TXW); | |
| 996 | } | ||
| 997 | |||
| 998 |
2/2✓ Branch 6 taken 28 times.
✓ Branch 7 taken 9 times.
|
37 | if (true == txn->m_fd.isOpen()) { |
| 999 | 28 | txn->m_fd.close(); | |
| 1000 | |||
| 1001 |
3/4✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 23 times.
|
28 | if (!txn->m_keep) { |
| 1002 | 5 | this->handleNotKeepFile(txn); | |
| 1003 | } | ||
| 1004 | } | ||
| 1005 | |||
| 1006 |
1/2✓ Branch 2 taken 37 times.
✗ Branch 3 not taken.
|
37 | if (txn->m_history != nullptr) { |
| 1007 | // Emit completion events for successful transactions | ||
| 1008 |
2/2✓ Branch 5 taken 24 times.
✓ Branch 6 taken 13 times.
|
37 | if (!TxnStatusIsError(txn->m_history->txn_stat)) { |
| 1009 |
2/2✓ Branch 4 taken 10 times.
✓ Branch 5 taken 14 times.
|
24 | if (txn->m_history->dir == Direction::DIRECTION_TX) { |
| 1010 |
3/4✗ Branch 16 not taken.
✓ Branch 17 taken 10 times.
✓ Branch 19 taken 10 times.
✓ Branch 23 taken 10 times.
|
60 | this->m_manager->log_ACTIVITY_HI_TxFileTransferCompleted( |
| 1011 | 20 | txn->m_txn_class, txn->m_history->seq_num, txn->m_history->src_eid, | |
| 1012 | 30 | txn->m_history->fnames.src_filename, txn->m_history->peer_eid, txn->m_history->fnames.dst_filename, | |
| 1013 | 10 | static_cast<U32>(txn->m_fsize)); | |
| 1014 |
1/2✓ Branch 4 taken 14 times.
✗ Branch 5 not taken.
|
14 | } else if (txn->m_history->dir == Direction::DIRECTION_RX) { |
| 1015 |
3/4✗ Branch 16 not taken.
✓ Branch 17 taken 14 times.
✓ Branch 19 taken 14 times.
✓ Branch 23 taken 14 times.
|
84 | this->m_manager->log_ACTIVITY_HI_RxFileTransferCompleted( |
| 1016 | 28 | txn->m_txn_class, txn->m_history->seq_num, txn->m_history->src_eid, | |
| 1017 | 28 | txn->m_history->fnames.src_filename, m_manager->getLocalEidParam(), | |
| 1018 | 14 | txn->m_history->fnames.dst_filename, static_cast<U32>(txn->m_fsize)); | |
| 1019 | } | ||
| 1020 | } else { | ||
| 1021 | // Log failure events for failed transactions | ||
| 1022 |
2/2✓ Branch 4 taken 6 times.
✓ Branch 5 taken 7 times.
|
13 | if (txn->m_history->dir == Direction::DIRECTION_TX) { |
| 1023 |
3/4✗ Branch 16 not taken.
✓ Branch 17 taken 6 times.
✓ Branch 19 taken 6 times.
✓ Branch 23 taken 6 times.
|
36 | this->m_manager->log_WARNING_LO_TxFileTransferFailed( |
| 1024 | 12 | txn->m_txn_class, txn->m_history->seq_num, txn->m_history->src_eid, | |
| 1025 | 18 | txn->m_history->fnames.src_filename, txn->m_history->peer_eid, txn->m_history->fnames.dst_filename, | |
| 1026 | 6 | static_cast<U8>(txn->m_history->txn_stat)); | |
| 1027 |
1/2✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
|
7 | } else if (txn->m_history->dir == Direction::DIRECTION_RX) { |
| 1028 |
3/4✗ Branch 16 not taken.
✓ Branch 17 taken 7 times.
✓ Branch 19 taken 7 times.
✓ Branch 23 taken 7 times.
|
42 | this->m_manager->log_WARNING_LO_RxFileTransferFailed( |
| 1029 | 14 | txn->m_txn_class, txn->m_history->seq_num, txn->m_history->src_eid, | |
| 1030 | 21 | txn->m_history->fnames.src_filename, txn->m_history->peer_eid, txn->m_history->fnames.dst_filename, | |
| 1031 | 7 | static_cast<U8>(txn->m_history->txn_stat)); | |
| 1032 | } | ||
| 1033 | } | ||
| 1034 | |||
| 1035 | // extra bookkeeping for tx direction only | ||
| 1036 |
5/6✓ Branch 4 taken 16 times.
✓ Branch 5 taken 21 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 16 times.
✓ Branch 9 taken 12 times.
✓ Branch 10 taken 4 times.
|
37 | if (txn->m_history->dir == Direction::DIRECTION_TX && txn->m_flags.tx.cmd_tx) { |
| 1037 | 12 | txn->m_chan->decrementCmdTxCounter(); | |
| 1038 | } | ||
| 1039 | |||
| 1040 | // Notify via port if this was a port-initiated transfer | ||
| 1041 |
2/2✓ Branch 2 taken 3 times.
✓ Branch 3 taken 34 times.
|
37 | if (txn->m_initType == TransactionInitType::INIT_BY_PORT) { |
| 1042 | // Map transaction status to SendFileStatus | ||
| 1043 | Svc::SendFileStatus::T status; | ||
| 1044 |
1/2✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
|
3 | if (TxnStatusIsError(txn->m_history->txn_stat)) { |
| 1045 | ✗ | status = Svc::SendFileStatus::STATUS_ERROR; | |
| 1046 | } else { | ||
| 1047 | 3 | status = Svc::SendFileStatus::STATUS_OK; | |
| 1048 | } | ||
| 1049 | |||
| 1050 | // Invoke the file complete notification | ||
| 1051 | 3 | this->m_manager->sendFileComplete(status); | |
| 1052 | } | ||
| 1053 | |||
| 1054 | 37 | txn->m_flags.com.keep_history = keep_history; | |
| 1055 | } | ||
| 1056 | |||
| 1057 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 37 times.
|
37 | if (txn->m_pb) { |
| 1058 | // a playback's transaction is now done, decrement the playback counter | ||
| 1059 | ✗ | FW_ASSERT(txn->m_pb->num_ts); | |
| 1060 | ✗ | --txn->m_pb->num_ts; | |
| 1061 | } | ||
| 1062 | |||
| 1063 | 37 | txn->m_chan->clearCurrentIfMatch(txn); | |
| 1064 | |||
| 1065 | // Put this transaction into the holdover state, inactivity timer will recycle it | ||
| 1066 | 37 | txn->m_state = TxnState::TXN_STATE_HOLD; | |
| 1067 | 37 | this->armInactTimer(txn); | |
| 1068 | } | ||
| 1069 | |||
| 1070 | 36 | void Engine::setTxnStatus(Transaction* txn, TxnStatus txn_stat) { | |
| 1071 |
2/2✓ Branch 5 taken 34 times.
✓ Branch 6 taken 2 times.
|
36 | if (!TxnStatusIsError(txn->m_history->txn_stat)) { |
| 1072 | 34 | txn->m_history->txn_stat = txn_stat; | |
| 1073 | } | ||
| 1074 | 36 | } | |
| 1075 | |||
| 1076 | 1 | void Engine::cancelTransaction(Transaction* txn) { | |
| 1077 | 1 | void (Transaction::* fns[static_cast<U32>(Direction::DIRECTION_NUM)])() = {nullptr}; | |
| 1078 | |||
| 1079 | 1 | fns[static_cast<U32>(Direction::DIRECTION_RX)] = &Transaction::rCancel; | |
| 1080 | 1 | fns[static_cast<U32>(Direction::DIRECTION_TX)] = &Transaction::sCancel; | |
| 1081 | |||
| 1082 |
2/4✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
|
1 | if (!txn->m_flags.com.canceled) { |
| 1083 | 1 | txn->m_flags.com.canceled = true; | |
| 1084 |
1/1✓ Branch 4 taken 1 times.
|
1 | this->setTxnStatus(txn, TxnStatus::TXN_STATUS_CANCEL_REQUEST_RECEIVED); |
| 1085 | |||
| 1086 | // this should always be true, just confirming before indexing into array | ||
| 1087 |
1/2✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
1 | if (txn->m_history->dir < Direction::DIRECTION_NUM) { |
| 1088 |
2/3✗ Branch 16 not taken.
✓ Branch 17 taken 1 times.
✓ Branch 38 taken 1 times.
|
1 | (txn->*fns[static_cast<U32>(txn->m_history->dir)])(); |
| 1089 | } | ||
| 1090 | } | ||
| 1091 | 1 | } | |
| 1092 | |||
| 1093 | 2 | bool Engine::isPollingDir(const Fw::StringBase& src_file, U8 chan_num) { | |
| 1094 | 2 | bool return_code = false; | |
| 1095 |
1/1✓ Branch 2 taken 2 times.
|
2 | Fw::String src_dir; |
| 1096 | CfdpPollDir* pd; | ||
| 1097 | U32 i; | ||
| 1098 | |||
| 1099 | // Extract directory portion (everything before last '/') | ||
| 1100 | 2 | FwSizeType lastSlashPos = 0; | |
| 1101 | 2 | bool foundSlash = false; | |
| 1102 |
3/3✓ Branch 11 taken 68 times.
✓ Branch 13 taken 66 times.
✓ Branch 14 taken 2 times.
|
68 | for (FwSizeType pos = 0; pos < src_file.length(); ++pos) { |
| 1103 |
3/3✓ Branch 11 taken 66 times.
✓ Branch 15 taken 6 times.
✓ Branch 16 taken 60 times.
|
66 | if (src_file.toChar()[pos] == '/') { |
| 1104 | 6 | lastSlashPos = pos; | |
| 1105 | 6 | foundSlash = true; | |
| 1106 | } | ||
| 1107 | } | ||
| 1108 | |||
| 1109 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (foundSlash) { |
| 1110 |
2/2✓ Branch 12 taken 2 times.
✓ Branch 15 taken 2 times.
|
2 | src_dir.format("%.*s", static_cast<int>(lastSlashPos), src_file.toChar()); |
| 1111 | } | ||
| 1112 | |||
| 1113 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | for (i = 0; i < MaxPollingDirPerChan; ++i) { |
| 1114 |
1/1✓ Branch 7 taken 2 times.
|
2 | pd = m_channels[chan_num]->getPollDir(i); |
| 1115 |
2/3✓ Branch 5 taken 2 times.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
|
2 | if (src_dir == pd->srcDir) { |
| 1116 | 2 | return_code = true; | |
| 1117 | 2 | break; | |
| 1118 | } | ||
| 1119 | } | ||
| 1120 | |||
| 1121 | 2 | return return_code; | |
| 1122 | 2 | } | |
| 1123 | |||
| 1124 | 8 | void Engine::handleNotKeepFile(Transaction* txn) { | |
| 1125 | 8 | Os::FileSystem::Status fileStatus = Os::FileSystem::OTHER_ERROR; | |
| 1126 |
1/1✓ Branch 2 taken 8 times.
|
8 | Fw::String failDir; |
| 1127 |
1/1✓ Branch 2 taken 8 times.
|
8 | Fw::String moveDir; |
| 1128 | |||
| 1129 | // Sender | ||
| 1130 |
2/2✓ Branch 4 taken 7 times.
✓ Branch 5 taken 1 times.
|
8 | if (txn->getHistory()->dir == Direction::DIRECTION_TX) { |
| 1131 |
3/3✓ Branch 5 taken 7 times.
✓ Branch 7 taken 6 times.
✓ Branch 8 taken 1 times.
|
7 | if (!TxnStatusIsError(txn->getHistory()->txn_stat)) { |
| 1132 | // If move directory is defined attempt move | ||
| 1133 |
2/2✓ Branch 8 taken 6 times.
✓ Branch 13 taken 6 times.
|
6 | moveDir = m_manager->getMoveDirParam(txn->getChannelId()); |
| 1134 |
3/3✓ Branch 2 taken 6 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 5 times.
|
6 | if (moveDir.length() > 0) { |
| 1135 |
1/1✓ Branch 10 taken 1 times.
|
1 | fileStatus = Os::FileSystem::moveFile(txn->m_history->fnames.src_filename.toChar(), moveDir.toChar()); |
| 1136 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (fileStatus != Os::FileSystem::OP_OK) { |
| 1137 |
1/1✓ Branch 13 taken 1 times.
|
1 | m_manager->log_WARNING_LO_FailKeepFileMove(txn->m_history->fnames.src_filename, moveDir, |
| 1138 | fileStatus); | ||
| 1139 | } | ||
| 1140 | } | ||
| 1141 | |||
| 1142 | // If move_dir is empty or move failed, delete the file | ||
| 1143 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if (fileStatus != Os::FileSystem::OP_OK) { |
| 1144 |
1/1✓ Branch 8 taken 6 times.
|
6 | fileStatus = Os::FileSystem::removeFile(txn->m_history->fnames.src_filename.toChar()); |
| 1145 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (fileStatus != Os::FileSystem::OP_OK) { |
| 1146 | ✗ | m_manager->log_WARNING_LO_FileRemoveFailed(txn->m_history->fnames.src_filename, fileStatus); | |
| 1147 | } | ||
| 1148 | } | ||
| 1149 | } else { | ||
| 1150 | // file inside a polling directory | ||
| 1151 |
2/3✓ Branch 10 taken 1 times.
✓ Branch 12 taken 1 times.
✗ Branch 13 not taken.
|
1 | if (this->isPollingDir(txn->m_history->fnames.src_filename, txn->getChannelId())) { |
| 1152 | // If fail directory is defined attempt move | ||
| 1153 |
2/2✓ Branch 8 taken 1 times.
✓ Branch 13 taken 1 times.
|
1 | failDir = m_manager->getFailDirParam(txn->getChannelId()); |
| 1154 |
2/3✓ Branch 2 taken 1 times.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
1 | if (failDir.length() > 0) { |
| 1155 | fileStatus = | ||
| 1156 |
1/1✓ Branch 10 taken 1 times.
|
1 | Os::FileSystem::moveFile(txn->m_history->fnames.src_filename.toChar(), failDir.toChar()); |
| 1157 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (fileStatus != Os::FileSystem::OP_OK) { |
| 1158 |
1/1✓ Branch 13 taken 1 times.
|
1 | m_manager->log_WARNING_LO_FailPollFileMove(txn->m_history->fnames.src_filename, failDir, |
| 1159 | fileStatus); | ||
| 1160 | } | ||
| 1161 | } | ||
| 1162 | |||
| 1163 | // If fail_dir is empty or move failed, delete the file | ||
| 1164 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (fileStatus != Os::FileSystem::OP_OK) { |
| 1165 |
1/1✓ Branch 8 taken 1 times.
|
1 | fileStatus = Os::FileSystem::removeFile(txn->m_history->fnames.src_filename.toChar()); |
| 1166 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (fileStatus != Os::FileSystem::OP_OK) { |
| 1167 | ✗ | m_manager->log_WARNING_LO_FileRemoveFailed(txn->m_history->fnames.src_filename, fileStatus); | |
| 1168 | } | ||
| 1169 | } | ||
| 1170 | } | ||
| 1171 | } | ||
| 1172 | } | ||
| 1173 | // Not Sender | ||
| 1174 | else { | ||
| 1175 |
1/1✓ Branch 9 taken 1 times.
|
1 | fileStatus = Os::FileSystem::removeFile(txn->m_history->fnames.dst_filename.toChar()); |
| 1176 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (fileStatus != Os::FileSystem::OP_OK) { |
| 1177 |
1/1✓ Branch 14 taken 1 times.
|
1 | m_manager->log_WARNING_LO_FileRemoveFailed(txn->m_history->fnames.dst_filename, fileStatus); |
| 1178 | } | ||
| 1179 | } | ||
| 1180 | 16 | } | |
| 1181 | |||
| 1182 | 7839 | Cfdp::ChannelTelemetry& Engine::getChannelTelemetryRef(U8 channelId) { | |
| 1183 | 7839 | return this->m_manager->getChannelTelemetryRef(channelId); | |
| 1184 | } | ||
| 1185 | |||
| 1186 | } // namespace Cfdp | ||
| 1187 | } // namespace Ccsds | ||
| 1188 | } // namespace Svc | ||
| 1189 |