#include #include #include #include #include "cfdp/PduSenderMock.h" #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 "mock/AcceptsTmMock.h" #include "mock/EventReportingProxyMock.h" #include "mock/FilesystemMock.h" #include "mock/MessageQueueMock.h" #include "mock/StorageManagerMock.h" #include "mock/cfdp/FaultHandlerMock.h" #include "mock/cfdp/RemoteConfigTableMock.h" #include "mock/cfdp/UserMock.h" TEST_CASE("CFDP Source Handler", "[cfdp]") { using namespace cfdp; using namespace returnvalue; using namespace std::filesystem; constexpr size_t MAX_FILE_SEGMENT_SIZE = 255; MessageQueueId_t destQueueId = 2; AcceptsTmMock tmReceiver(destQueueId); // MessageQueueMock mqMock(destQueueId); auto localId = EntityId(UnsignedByteField(2)); auto remoteId = EntityId(UnsignedByteField(5)); FaultHandlerMock fhMock; LocalEntityCfg localEntityCfg(localId, IndicationCfg(), fhMock); FilesystemMock fsMock; UserMock userMock(fsMock); SeqCountProviderU16 seqCountProvider; SourceHandlerParams dp(localEntityCfg, userMock, seqCountProvider); PduSenderMock pduSender; EventReportingProxyMock eventReporterMock; LocalPool::LocalPoolConfig storeCfg = {{10, 32}, {10, 64}, {10, 128}, {10, 1024}}; FsfwParams fp(&eventReporterMock); auto sourceHandler = SourceHandler(pduSender, 4096, 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"; fsMock.createFile(srcFileName.c_str()); cfdp::StringLv srcNameLv(srcFileName.c_str(), srcFileName.length()); 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) { CHECK(fsmResult.errors == 0); CHECK(fsmResult.packetsSent == 1); }; auto genericMetadataCheck = [&](const SourceHandler::FsmResult& fsmResult, size_t expectedFileSize, uint16_t expectedSeqNum) { CHECK(fsmResult.errors == 0); auto optNextPacket = pduSender.getNextSentPacket(); CHECK(optNextPacket.has_value()); const auto& [pduType, fileDirective, rawPdu] = *optNextPacket; CHECK(rawPdu.size() == 55); MetadataGenericInfo metadataInfo; MetadataPduReader metadataReader(rawPdu.data(), rawPdu.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()); }; auto genericEofCheck = [&](const SourceHandler::FsmResult& fsmResult, size_t expectedFileSize, uint32_t expectedChecksum, uint16_t expectedSeqNum) { onePduSentCheck(fsmResult); TmTcMessage tmtcMessage; auto optNextPacket = pduSender.getNextSentPacket(); CHECK(optNextPacket.has_value()); const auto& [pduType, fileDirective, rawPdu] = *optNextPacket; // 10 byte PDU header, 1 byte directive field, 1 byte condition code, 4 byte checksum, // 4 byte FSS CHECK(rawPdu.size() == 20); EofInfo eofInfo; EofPduReader eofReader(rawPdu.data(), rawPdu.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, const uint16_t expectedSeqNum) { 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.stateMachineNoPacket(); // Verify metadata PDU was sent. genericMetadataCheck(fsmResult, expectedFileSize, 0); sourceHandler.stateMachineNoPacket(); // 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.stateMachineNoPacket(); genericNoticeOfCompletionCheck(fsmResult, 0); } SECTION("Transfer small file") { uint16_t expectedSeqNum = 0; fsMock.createFile(srcFileName.c_str()); std::string fileContent = "hello world\n"; size_t expectedFileSize = fileContent.size(); fsMock.writeToFile(srcFileName.c_str(), 0, reinterpret_cast(fileContent.data()), expectedFileSize); CHECK(sourceHandler.transactionStart(putRequest, cfg) == OK); const SourceHandler::FsmResult& fsmResult = sourceHandler.stateMachineNoPacket(); // Verify metadata PDU was sent. genericMetadataCheck(fsmResult, expectedFileSize, expectedSeqNum); // Verify that a small file data PDU was sent. sourceHandler.stateMachineNoPacket(); onePduSentCheck(fsmResult); auto optNextPacket = pduSender.getNextSentPacket(); CHECK(optNextPacket.has_value()); const auto& [pduType, fileDirective, rawPdu] = *optNextPacket; FileDataInfo fdInfo; FileDataReader fdReader(rawPdu.data(), rawPdu.size(), fdInfo); // 10 byte PDU header, 4 byte offset, 12 bytes file data. CHECK(rawPdu.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); sourceHandler.stateMachineNoPacket(); 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.stateMachineNoPacket(); 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(srcFileName.c_str()); fsMock.writeToFile(srcFileName.c_str(), 0, largerFileData.data(), largerFileData.size()); CHECK(sourceHandler.transactionStart(putRequest, cfg) == OK); const SourceHandler::FsmResult& fsmResult = sourceHandler.stateMachineNoPacket(); // Verify metadata PDU was sent. genericMetadataCheck(fsmResult, expectedFileSize, expectedSeqNum); // Check first file data PDU. It should have the maximum file segment size. sourceHandler.stateMachineNoPacket(); onePduSentCheck(fsmResult); auto optNextPacket = pduSender.getNextSentPacket(); CHECK(optNextPacket.has_value()); const auto& [pduType, fileDirective, rawPdu] = *optNextPacket; FileDataInfo fdInfo; { // auto accessor = onePduSentCheck(fsmResult, tmtcMessage, &pduPtr); FileDataReader fdReader(rawPdu.data(), rawPdu.size(), fdInfo); // 10 byte PDU header, 4 byte offset, 255 byte file data CHECK(rawPdu.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]); } } // Check second file data PDU. sourceHandler.stateMachineNoPacket(); { optNextPacket = pduSender.getNextSentPacket(); CHECK(optNextPacket.has_value()); const auto& [pduType, fileDirective, rawPdu] = *optNextPacket; FileDataReader fdReader(rawPdu.data(), rawPdu.size(), fdInfo); // 10 byte PDU header, 4 byte offset, remaining file data (400 - 255 == 145). CHECK(rawPdu.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]); } } // Check EOF and verify checksum. sourceHandler.stateMachineNoPacket(); 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.stateMachineNoPacket(); genericNoticeOfCompletionCheck(fsmResult, expectedSeqNum); } }