#include #include #include #include #include "fsfw/cfdp.h" #include "fsfw/cfdp/handler/PutRequest.h" #include "fsfw/cfdp/handler/SourceHandler.h" #include "fsfw/cfdp/pdu/EofPduCreator.h" #include "fsfw/cfdp/pdu/EofPduReader.h" #include "fsfw/cfdp/pdu/FileDataReader.h" #include "fsfw/cfdp/pdu/MetadataPduCreator.h" #include "fsfw/cfdp/pdu/MetadataPduReader.h" #include "fsfw/tmtcservices/TmTcMessage.h" #include "fsfw/util/SeqCountProvider.h" #include "mocks/AcceptsTmMock.h" #include "mocks/EventReportingProxyMock.h" #include "mocks/FilesystemMock.h" #include "mocks/MessageQueueMock.h" #include "mocks/StorageManagerMock.h" #include "mocks/cfdp/FaultHandlerMock.h" #include "mocks/cfdp/RemoteConfigTableMock.h" #include "mocks/cfdp/UserMock.h" TEST_CASE("CFDP Source Handler", "[cfdp]") { using namespace cfdp; using namespace returnvalue; using namespace std::filesystem; const size_t MAX_FILE_SEGMENT_SIZE = 255; MessageQueueId_t destQueueId = 2; AcceptsTmMock tmReceiver(destQueueId); MessageQueueMock mqMock(destQueueId); EntityId localId = EntityId(UnsignedByteField(2)); EntityId remoteId = EntityId(UnsignedByteField(5)); FaultHandlerMock fhMock; LocalEntityCfg localEntityCfg(localId, IndicationCfg(), fhMock); FilesystemMock fsMock; UserMock userMock(fsMock); SeqCountProviderU16 seqCountProvider; SourceHandlerParams dp(localEntityCfg, userMock, seqCountProvider); EventReportingProxyMock eventReporterMock; LocalPool::LocalPoolConfig storeCfg = {{10, 32}, {10, 64}, {10, 128}, {10, 1024}}; StorageManagerMock tcStore(2, storeCfg); StorageManagerMock tmStore(3, storeCfg); FsfwParams fp(tmReceiver, &mqMock, &eventReporterMock); fp.tcStore = &tcStore; fp.tmStore = &tmStore; auto sourceHandler = SourceHandler(dp, fp); RemoteEntityCfg cfg; cfg.maxFileSegmentLen = MAX_FILE_SEGMENT_SIZE; cfg.remoteId = remoteId; std::string srcFileName = "/tmp/cfdp-test.txt"; std::string destFileName = "/tmp/cfdp-test2.txt"; FilesystemParams srcFileNameFs(srcFileName.c_str()); fsMock.createFile(srcFileNameFs); cfdp::StringLv srcNameLv(srcFileNameFs.path, std::strlen(srcFileNameFs.path)); FilesystemParams destFileNameFs(destFileName.c_str()); cfdp::StringLv destNameLv(destFileNameFs.path, std::strlen(destFileNameFs.path)); PutRequest putRequest(remoteId, srcNameLv, destNameLv); CHECK(sourceHandler.initialize() == OK); auto onePduSentCheck = [&](const SourceHandler::FsmResult& fsmResult, TmTcMessage& tmtcMessage, const uint8_t** pduPtr) { CHECK(fsmResult.errors == 0); CHECK(fsmResult.packetsSent == 1); CHECK(mqMock.numberOfSentMessages() == 1); REQUIRE(mqMock.getNextSentMessage(destQueueId, tmtcMessage) == OK); auto accessor = tmStore.getData(tmtcMessage.getStorageId()); REQUIRE(accessor.first == OK); *pduPtr = accessor.second.data(); return std::move(accessor); }; auto genericMetadataCheck = [&](const SourceHandler::FsmResult& fsmResult, size_t expectedFileSize, uint16_t expectedSeqNum) { CHECK(fsmResult.errors == 0); CHECK(fsmResult.callStatus == CallStatus::CALL_AGAIN); TmTcMessage tmtcMessage; const uint8_t* pduPtr; auto accessor = onePduSentCheck(fsmResult, tmtcMessage, &pduPtr); CHECK(accessor.second.size() == 55); MetadataGenericInfo metadataInfo; MetadataPduReader metadataReader(pduPtr, accessor.second.size(), metadataInfo, nullptr, 0); REQUIRE(metadataReader.parseData() == OK); std::string srcNameRead = metadataReader.getSourceFileName().getString(); CHECK(srcNameRead == srcFileName); TransactionSeqNum seqNum; metadataReader.getTransactionSeqNum(seqNum); CHECK(seqNum.getValue() == expectedSeqNum); CHECK(userMock.transactionIndicRecvd.size() == 1); CHECK(userMock.transactionIndicRecvd.back() == TransactionId(localId, seqNum)); EntityId srcId; metadataReader.getSourceId(srcId); EntityId destId; metadataReader.getDestId(destId); CHECK(srcId.getValue() == localId.getValue()); CHECK(destId.getValue() == remoteId.getValue()); std::string destNameRead = metadataReader.getDestFileName().getString(); CHECK(destNameRead == destFileName); if (expectedFileSize == 0) { CHECK(metadataInfo.getChecksumType() == ChecksumType::NULL_CHECKSUM); } else { CHECK(metadataInfo.getChecksumType() == ChecksumType::CRC_32); } CHECK(metadataInfo.getFileSize().value() == expectedFileSize); CHECK(!metadataInfo.isClosureRequested()); mqMock.clearMessages(); }; auto genericEofCheck = [&](const SourceHandler::FsmResult& fsmResult, size_t expectedFileSize, uint32_t expectedChecksum, uint16_t expectedSeqNum) { CHECK(fsmResult.errors == 0); CHECK(fsmResult.callStatus == CallStatus::CALL_AGAIN); TmTcMessage tmtcMessage; const uint8_t* pduPtr; auto accessor = onePduSentCheck(fsmResult, tmtcMessage, &pduPtr); // 10 byte PDU header, 1 byte directive field, 1 byte condition code, 4 byte checksum, // 4 byte FSS CHECK(accessor.second.size() == 20); EofInfo eofInfo; EofPduReader eofReader(pduPtr, accessor.second.size(), eofInfo); REQUIRE(eofReader.parseData() == OK); TransactionSeqNum seqNum; eofReader.getTransactionSeqNum(seqNum); CHECK(seqNum.getValue() == expectedSeqNum); auto transactionId = TransactionId(localId, seqNum); CHECK(userMock.eofSentRecvd.size() == 1); CHECK(userMock.eofSentRecvd.back() == transactionId); CHECK(eofInfo.getChecksum() == expectedChecksum); CHECK(eofInfo.getConditionCode() == ConditionCode::NO_ERROR); CHECK(eofInfo.getFileSize().value() == expectedFileSize); }; auto genericNoticeOfCompletionCheck = [&](const SourceHandler::FsmResult& fsmResult, uint16_t expectedSeqNum) { CHECK(fsmResult.callStatus == CallStatus::DONE); CHECK(userMock.finishedRecvd.size() == 1); CHECK(userMock.finishedRecvd.back().first == TransactionId(localId, TransactionSeqNum(cfdp::WidthInBytes::TWO_BYTES, expectedSeqNum))); CHECK(sourceHandler.getStep() == SourceHandler::TransactionStep::IDLE); CHECK(sourceHandler.getState() == CfdpState::IDLE); }; SECTION("Test Basic") { CHECK(sourceHandler.getState() == CfdpState::IDLE); CHECK(sourceHandler.getStep() == SourceHandler::TransactionStep::IDLE); } SECTION("Transfer empty file") { CHECK(sourceHandler.transactionStart(putRequest, cfg) == OK); size_t expectedFileSize = 0; const SourceHandler::FsmResult& fsmResult = sourceHandler.stateMachine(); // Verify metadata PDU was sent. genericMetadataCheck(fsmResult, expectedFileSize, 0); sourceHandler.stateMachine(); // Verify EOF PDU was sent. No file data PDU is sent for an empty file and the checksum is 0. genericEofCheck(fsmResult, expectedFileSize, 0, 0); // Verify notice of completion. sourceHandler.stateMachine(); genericNoticeOfCompletionCheck(fsmResult, 0); } SECTION("Transfer small file") { uint16_t expectedSeqNum = 0; fsMock.createFile(srcFileNameFs); std::string fileContent = "hello world\n"; size_t expectedFileSize = fileContent.size(); FileOpParams params(srcFileName.c_str(), expectedFileSize); fsMock.writeToFile(params, reinterpret_cast(fileContent.data())); CHECK(sourceHandler.transactionStart(putRequest, cfg) == OK); const SourceHandler::FsmResult& fsmResult = sourceHandler.stateMachine(); // Verify metadata PDU was sent. genericMetadataCheck(fsmResult, expectedFileSize, expectedSeqNum); // Verify that a small file data PDU was sent. sourceHandler.stateMachine(); TmTcMessage tmtcMessage; const uint8_t* pduPtr; auto accessor = onePduSentCheck(fsmResult, tmtcMessage, &pduPtr); FileDataInfo fdInfo; FileDataReader fdReader(pduPtr, accessor.second.size(), fdInfo); // 10 byte PDU header, 4 byte offset, 12 bytes file data. CHECK(accessor.second.size() == 26); CHECK(fdReader.parseData() == OK); CHECK(fdInfo.getOffset().value() == 0); size_t fileSize = 0; const uint8_t* fileData = fdInfo.getFileData(&fileSize); REQUIRE(fileSize == fileContent.size()); CHECK(fileData != nullptr); std::string dataReadBack(reinterpret_cast(fileData), fileSize); CHECK(dataReadBack == fileContent); mqMock.clearMessages(); sourceHandler.stateMachine(); etl::crc32 crcCalc; crcCalc.add(fileContent.data(), fileContent.data() + fileContent.size()); // Verify EOF PDU was sent. genericEofCheck(fsmResult, expectedFileSize, crcCalc.value(), expectedSeqNum); // Verify notice of completion. sourceHandler.stateMachine(); genericNoticeOfCompletionCheck(fsmResult, expectedSeqNum); } SECTION("Transfer two segment file") { uint16_t expectedSeqNum = 0; // Create 400 bytes of random data. This should result in two file segments, with one // having the maximum size. std::random_device dev; std::mt19937 rng(dev()); std::uniform_int_distribution distU8(0, 255); std::array largerFileData{}; for (auto& val : largerFileData) { val = distU8(rng); } size_t expectedFileSize = largerFileData.size(); fsMock.createFile(srcFileNameFs); FileOpParams params(srcFileName.c_str(), expectedFileSize); fsMock.writeToFile(params, reinterpret_cast(largerFileData.data())); CHECK(sourceHandler.transactionStart(putRequest, cfg) == OK); const SourceHandler::FsmResult& fsmResult = sourceHandler.stateMachine(); // Verify metadata PDU was sent. genericMetadataCheck(fsmResult, expectedFileSize, expectedSeqNum); // Check first file data PDU. It should have the maximum file segment size. sourceHandler.stateMachine(); TmTcMessage tmtcMessage; const uint8_t* pduPtr; FileDataInfo fdInfo; { CHECK(fsmResult.callStatus == CallStatus::CALL_AGAIN); auto accessor = onePduSentCheck(fsmResult, tmtcMessage, &pduPtr); FileDataReader fdReader(pduPtr, accessor.second.size(), fdInfo); // 10 byte PDU header, 4 byte offset, 255 byte file data CHECK(accessor.second.size() == 269); CHECK(fdReader.parseData() == OK); CHECK(fdInfo.getOffset().value() == 0); size_t fileSize = 0; const uint8_t* fileData = fdInfo.getFileData(&fileSize); // Maximum file segment size. REQUIRE(fileSize == MAX_FILE_SEGMENT_SIZE); for (unsigned i = 0; i < fileSize; i++) { CHECK(fileData[i] == largerFileData[i]); } } mqMock.clearMessages(); // Check second file data PDU. sourceHandler.stateMachine(); { CHECK(fsmResult.callStatus == CallStatus::CALL_AGAIN); auto accessor = onePduSentCheck(fsmResult, tmtcMessage, &pduPtr); FileDataReader fdReader(pduPtr, accessor.second.size(), fdInfo); // 10 byte PDU header, 4 byte offset, remaining file data (400 - 255 == 145). CHECK(accessor.second.size() == 10 + 4 + largerFileData.size() - MAX_FILE_SEGMENT_SIZE); CHECK(fdReader.parseData() == OK); CHECK(fdInfo.getOffset().value() == MAX_FILE_SEGMENT_SIZE); size_t fileDataSize = 0; const uint8_t* fileData = fdInfo.getFileData(&fileDataSize); // Maximum file segment size. REQUIRE(fileDataSize == largerFileData.size() - MAX_FILE_SEGMENT_SIZE); for (unsigned i = 0; i < fileDataSize; i++) { CHECK(fileData[i] == largerFileData[MAX_FILE_SEGMENT_SIZE + i]); } } mqMock.clearMessages(); // Check EOF and verify checksum. sourceHandler.stateMachine(); etl::crc32 crcCalc; crcCalc.add(largerFileData.data(), largerFileData.data() + largerFileData.size()); // Verify EOF PDU was sent. genericEofCheck(fsmResult, expectedFileSize, crcCalc.value(), expectedSeqNum); // Verify notice of completion. sourceHandler.stateMachine(); genericNoticeOfCompletionCheck(fsmResult, expectedSeqNum); } }