#include "SourceHandler.h" #include #include #include "fsfw/cfdp/pdu/EofPduCreator.h" #include "fsfw/cfdp/pdu/FileDataCreator.h" #include "fsfw/cfdp/pdu/MetadataPduCreator.h" #include "fsfw/filesystem/HasFileSystemIF.h" #include "fsfw/globalfunctions/arrayprinter.h" #include "fsfw/objectmanager.h" #include "fsfw/serviceinterface.h" #include "fsfw/tasks/TaskFactory.h" #include "fsfw/tmtcservices/TmTcMessage.h" using namespace returnvalue; cfdp::SourceHandler::SourceHandler(SourceHandlerParams params, FsfwParams fsfwParams) : sourceParams(std::move(params)), fsfwParams(fsfwParams) { // The entity ID portion of the transaction ID will always remain fixed. transactionParams.id.entityId = sourceParams.cfg.localId; transactionParams.pduConf.sourceId = sourceParams.cfg.localId; if (sourceParams.seqCountProvider.bitWidth() == 8) { transactionParams.pduConf.seqNum.setWidth(cfdp::WidthInBytes::ONE_BYTE); } else if (sourceParams.seqCountProvider.bitWidth() == 16) { transactionParams.pduConf.seqNum.setWidth(cfdp::WidthInBytes::TWO_BYTES); } else if (sourceParams.seqCountProvider.bitWidth() == 32) { transactionParams.pduConf.seqNum.setWidth(cfdp::WidthInBytes::FOUR_BYTES); } else if (sourceParams.seqCountProvider.bitWidth() == 64) { transactionParams.pduConf.seqNum.setWidth(cfdp::WidthInBytes::EIGHT_BYTES); } else { #if FSFW_CPP_OSTREAM_ENABLED == 1 sif::error << "cfdp::SourceHandler: Seq count provider bit width " << sourceParams.seqCountProvider.bitWidth() << " not allowed" << std::endl; #else sif::printError("cfdp::SourceHandler: Seq count provider bit width %d not allowed\n", sourceParams.seqCountProvider.bitWidth()); #endif // Yeah, what am I supposed to do here? Can't throw an exception in the FSFW.. transactionParams.pduConf.seqNum.setWidth(cfdp::WidthInBytes::ONE_BYTE); } transactionParams.pduConf.seqNum.setValue(0); } cfdp::SourceHandler::FsmResult& cfdp::SourceHandler::fsmNacked() { ReturnValue_t result; if (step == TransactionStep::IDLE) { step = TransactionStep::TRANSACTION_START; } if (step == TransactionStep::TRANSACTION_START) { sourceParams.user.transactionIndication(transactionParams.id); result = checksumGeneration(); if (result != OK) { addError(result); } step = TransactionStep::SENDING_METADATA; } if (step == TransactionStep::SENDING_METADATA) { result = prepareAndSendMetadataPdu(); if (result != OK) { addError(result); } fsmResult.callStatus = CallStatus::CALL_AGAIN; return fsmResult; } if (step == TransactionStep::SENDING_FILE_DATA) { bool noFdPdu = false; result = prepareAndSendNextFileDataPdu(noFdPdu); if (result == OK and !noFdPdu) { fsmResult.callStatus = CallStatus::CALL_AGAIN; return fsmResult; } } if (step == TransactionStep::SENDING_EOF) { result = prepareAndSendEofPdu(); if (result != OK) { addError(result); } if (sourceParams.cfg.indicCfg.eofSentIndicRequired) { sourceParams.user.eofSentIndication(transactionParams.id); } if (transactionParams.closureRequested) { step = TransactionStep::WAIT_FOR_FINISH; fsmResult.callStatus = CallStatus::CALL_AFTER_DELAY; } else { step = TransactionStep::NOTICE_OF_COMPLETION; fsmResult.callStatus = CallStatus::CALL_AGAIN; } return fsmResult; } if (step == TransactionStep::WAIT_FOR_FINISH) { // TODO: In case this is a request with closure, wait for finish. // Done, issue notice of completion step = TransactionStep::NOTICE_OF_COMPLETION; } if (step == TransactionStep::NOTICE_OF_COMPLETION) { noticeOfCompletion(); reset(); } return fsmResult; } const cfdp::SourceHandler::FsmResult& cfdp::SourceHandler::stateMachine() { fsmResult.packetsSent = 0; fsmResult.errors = 0; fsmResult.callStatus = CallStatus::DONE; if (state == cfdp::CfdpState::IDLE) { return fsmResult; } if (state == cfdp::CfdpState::BUSY_CLASS_1_NACKED) { return fsmNacked(); } return fsmResult; } ReturnValue_t cfdp::SourceHandler::checksumGeneration() { if (transactionParams.fileSize.value() == 0) { // NULL checksum for empty file. transactionParams.crc = 0; return OK; } std::array buf{}; etl::crc32 crcCalc; uint64_t currentOffset = 0; FileOpParams params(transactionParams.sourceName.data(), transactionParams.fileSize.value()); while (currentOffset < transactionParams.fileSize.value()) { uint64_t readLen; if (currentOffset + buf.size() > transactionParams.fileSize.value()) { readLen = transactionParams.fileSize.value() - currentOffset; } else { readLen = buf.size(); } if (readLen > 0) { params.offset = currentOffset; params.size = readLen; auto result = sourceParams.user.vfs.readFromFile(params, buf.data(), buf.size()); if (result != OK) { addError(result); return FAILED; } crcCalc.add(buf.begin(), buf.begin() + readLen); } currentOffset += readLen; } transactionParams.crc = crcCalc.value(); return OK; } ReturnValue_t cfdp::SourceHandler::transactionStart(PutRequest& putRequest, RemoteEntityCfg& cfg) { if (state != CfdpState::IDLE) { return SOURCE_TRANSACTION_PENDING; } if (cfg.remoteId != putRequest.getDestId()) { return WRONG_REMOTE_CFG_ENTITY_ID; } if (putRequest.getSourceName().getValueLen() == 0) { return SOURCE_NAME_EMPTY; } if (putRequest.getDestName().getValueLen() == 0) { return DEST_NAME_EMPTY; } const char* srcNamePtr = putRequest.getSourceName().getCString(transactionParams.sourceNameSize); const char* destNamePtr = putRequest.getDestName().getCString(transactionParams.destNameSize); std::strncpy(transactionParams.sourceName.data(), srcNamePtr, transactionParams.sourceNameSize); std::strncpy(transactionParams.destName.data(), destNamePtr, transactionParams.destNameSize); // Add 0 termination. The source and dest name size can not be larger than UINT8_MAX, so this // operation is safe. transactionParams.sourceName[transactionParams.sourceNameSize] = '\0'; transactionParams.destName[transactionParams.destNameSize] = '\0'; FilesystemParams params(transactionParams.sourceName.data()); if (!sourceParams.user.vfs.fileExists(params)) { return FILE_DOES_NOT_EXIST; } if (cfg.maxFileSegmentLen > fileBuf.size() or cfg.maxFileSegmentLen == 0) { return FILE_SEGMENT_LEN_INVALID; } // If transmission mode is not set, use default transmission mode for the remote entity. if (not putRequest.getTransmissionMode(transactionParams.pduConf.mode)) { transactionParams.pduConf.mode = cfg.defaultTransmissionMode; } // If closure request field is not set, use default option for the remote entity. if (not putRequest.getClosureRequested(transactionParams.closureRequested)) { transactionParams.closureRequested = cfg.closureRequested; } const EntityId& destId = putRequest.getDestId(); // The width of the source and destination ID must be the same. Use the larger ID value to // ensure the width is large enough for both IDs if (destId.getWidth() > transactionParams.pduConf.sourceId.getWidth()) { transactionParams.pduConf.destId = destId; transactionParams.pduConf.sourceId.setWidth(destId.getWidth()); } else { transactionParams.pduConf.destId.setValueAndWidth(transactionParams.pduConf.sourceId.getWidth(), destId.getValue()); } // Only used for PDU forwarding, file is sent to file receiver regularly here. transactionParams.pduConf.direction = Direction::TOWARDS_RECEIVER; transactionParams.pduConf.seqNum.setValue(sourceParams.seqCountProvider.getAndIncrement()); transactionParams.id.seqNum = transactionParams.pduConf.seqNum; if (transactionParams.pduConf.mode == TransmissionMode::ACKNOWLEDGED) { state = cfdp::CfdpState::BUSY_CLASS_2_ACKED; } else if (transactionParams.pduConf.mode == TransmissionMode::UNACKNOWLEDGED) { state = cfdp::CfdpState::BUSY_CLASS_1_NACKED; } step = TransactionStep::IDLE; uint64_t fileSize = 0; sourceParams.user.vfs.getFileSize(params, fileSize); transactionParams.pduConf.largeFile = false; if (fileSize > UINT32_MAX) { transactionParams.pduConf.largeFile = true; } if (fileSize == 0) { transactionParams.checksumType = ChecksumType::NULL_CHECKSUM; } else { transactionParams.checksumType = ChecksumType::CRC_32; } transactionParams.fileSize.setFileSize(fileSize, transactionParams.pduConf.largeFile); transactionParams.progress = 0; transactionParams.remoteCfg = cfg; return OK; } ReturnValue_t cfdp::SourceHandler::prepareAndSendMetadataPdu() { cfdp::StringLv sourceName(transactionParams.sourceName.data(), transactionParams.sourceNameSize); cfdp::StringLv destName(transactionParams.destName.data(), transactionParams.destNameSize); auto metadataInfo = MetadataGenericInfo(transactionParams.closureRequested, transactionParams.checksumType, transactionParams.fileSize); auto metadataPdu = MetadataPduCreator(transactionParams.pduConf, metadataInfo, sourceName, destName, nullptr, 0); ReturnValue_t result = sendGenericPdu(metadataPdu); if (result != OK) { return result; } // Advance FSM if everything works step = TransactionStep::SENDING_FILE_DATA; return OK; } ReturnValue_t cfdp::SourceHandler::prepareAndSendNextFileDataPdu(bool& noFileDataPdu) { cfdp::Fss offset(transactionParams.progress); uint64_t readLen; uint64_t fileSize = transactionParams.fileSize.value(); noFileDataPdu = false; if (fileSize == 0) { // We are done, no need to send file data PDUs for an empty file. step = TransactionStep::SENDING_EOF; noFileDataPdu = true; return OK; } if (fileSize < transactionParams.remoteCfg.maxFileSegmentLen) { readLen = transactionParams.fileSize.value(); } else { if (transactionParams.progress + transactionParams.remoteCfg.maxFileSegmentLen > fileSize) { readLen = fileSize - transactionParams.progress; } else { readLen = transactionParams.remoteCfg.maxFileSegmentLen; } } FileOpParams fileParams(transactionParams.sourceName.data(), readLen); fileParams.offset = transactionParams.progress; ReturnValue_t result = sourceParams.user.vfs.readFromFile(fileParams, fileBuf.data(), fileBuf.size()); if (result != returnvalue::OK) { addError(result); return result; } auto fileDataInfo = FileDataInfo(offset, fileBuf.data(), readLen); auto fileDataPdu = FileDataCreator(transactionParams.pduConf, fileDataInfo); result = sendGenericPdu(fileDataPdu); if (result != OK) { return result; } transactionParams.progress += readLen; if (transactionParams.progress >= fileSize) { // Advance FSM after all file data PDUs were sent. step = TransactionStep::SENDING_EOF; } return OK; } ReturnValue_t cfdp::SourceHandler::prepareAndSendEofPdu() { auto eofInfo = EofInfo(ConditionCode::NO_ERROR, transactionParams.crc, transactionParams.fileSize); auto eofPdu = EofPduCreator(transactionParams.pduConf, eofInfo); ReturnValue_t result = sendGenericPdu(eofPdu); if (result != OK) { return result; } return OK; } ReturnValue_t cfdp::SourceHandler::initialize() { if (fsfwParams.tmStore == nullptr) { fsfwParams.tmStore = ObjectManager::instance()->get(objects::TM_STORE); if (fsfwParams.tmStore == nullptr) { return FAILED; } } if (fsfwParams.tcStore == nullptr) { fsfwParams.tcStore = ObjectManager::instance()->get(objects::TC_STORE); if (fsfwParams.tcStore == nullptr) { return FAILED; } } if (fsfwParams.msgQueue == nullptr) { return FAILED; } return OK; } ReturnValue_t cfdp::SourceHandler::sendGenericPdu(const SerializeIF& pdu) { uint8_t* dataPtr; store_address_t storeId; ReturnValue_t result = fsfwParams.tmStore->getFreeElement(&storeId, pdu.getSerializedSize(), &dataPtr); if (result != OK) { addError(result); fsmResult.callStatus = CallStatus::CALL_AFTER_DELAY; if (result == StorageManagerIF::DATA_STORAGE_FULL) { return TM_STORE_FULL; } return result; } size_t serializedLen = 0; result = pdu.serializeBe(dataPtr, serializedLen, pdu.getSerializedSize()); if (result != OK) { addError(result); return result; } TmTcMessage tmMsg(storeId); result = fsfwParams.msgQueue->sendMessage(fsfwParams.packetDest.getReportReceptionQueue(), &tmMsg); if (result != OK) { fsmResult.callStatus = CallStatus::CALL_AFTER_DELAY; } if (result == MessageQueueIF::FULL) { return TARGET_MSG_QUEUE_FULL; } else if (result == OK) { fsmResult.packetsSent += 1; } return result; } ReturnValue_t cfdp::SourceHandler::noticeOfCompletion() { if (sourceParams.cfg.indicCfg.transactionFinishedIndicRequired) { cfdp::TransactionFinishedParams params(transactionParams.id, ConditionCode::NO_ERROR, FileDeliveryCode::DATA_COMPLETE, FileDeliveryStatus::RETAINED_IN_FILESTORE); sourceParams.user.transactionFinishedIndication(params); } return OK; } ReturnValue_t cfdp::SourceHandler::reset() { step = TransactionStep::IDLE; state = cfdp::CfdpState::IDLE; fsmResult.callStatus = CallStatus::DONE; transactionParams.reset(); return OK; } cfdp::CfdpState cfdp::SourceHandler::getState() const { return state; } cfdp::SourceHandler::TransactionStep cfdp::SourceHandler::getStep() const { return step; } void cfdp::SourceHandler::addError(ReturnValue_t error) { if (fsmResult.errors < fsmResult.errorCodes.size()) { fsmResult.errorCodes[fsmResult.errors] = error; fsmResult.errors++; fsmResult.result = error; } }