fprime/Svc/FileDownlink/test/ut/FileDownlinkTester.cpp
Saransh Chopra 903677e97d
Fix clang-tidy checks (#4379)
* fix(.clang-tidy): `Checks` is supposed to be a comma separated list of checks

* fix clang-tidy warning

* fix a few lint warnings

* run clang-format

* more format
2025-11-03 17:45:54 -08:00

568 lines
21 KiB
C++

// ======================================================================
// \title FileDownlinkTester.cpp
// \author bocchino
// \brief cpp file for FileDownlink test harness implementation class
//
// \copyright
// Copyright 2009-2015, by the California Institute of Technology.
// ALL RIGHTS RESERVED. United States Government Sponsorship
// acknowledged.
// ======================================================================
#include <unistd.h>
#include <cerrno>
#include "FileDownlinkTester.hpp"
#define INSTANCE 0
#define CMD_SEQ 0
#define QUEUE_DEPTH 10
#define TIMEOUT_MS 1000
#define COOLDOWN_MS 500
#define CYCLE_MS 100
#define MAX_ALLOCATED 100
namespace Svc {
// ----------------------------------------------------------------------
// Construction and destruction
// ----------------------------------------------------------------------
FileDownlinkTester ::FileDownlinkTester()
: FileDownlinkGTestBase("Tester", MAX_HISTORY_SIZE), component("FileDownlink"), buffers_index(0) {
this->component.configure(TIMEOUT_MS, COOLDOWN_MS, CYCLE_MS, 10);
this->connectPorts();
this->initComponents();
}
FileDownlinkTester ::~FileDownlinkTester() {
for (U32 i = 0; i < buffers_index; i++) {
delete[] buffers[i];
}
}
// ----------------------------------------------------------------------
// Tests
// ----------------------------------------------------------------------
void FileDownlinkTester ::downlink() {
// Assert idle mode
ASSERT_EQ(FileDownlink::Mode::IDLE, this->component.m_mode.get());
// Create a file
const char* const sourceFileName = "source.bin";
const char* const destFileName = "dest.bin";
U8 data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
FileBuffer fileBufferOut(data, sizeof(data));
fileBufferOut.write(sourceFileName);
// Send the file and assert COMMAND_OK
this->sendFile(sourceFileName, destFileName, Fw::CmdResponse::OK);
// Assert telemetry
ASSERT_TLM_SIZE(4);
ASSERT_TLM_PacketsSent_SIZE(3);
for (size_t i = 0; i < 3; ++i) {
ASSERT_TLM_PacketsSent(i, i + 1);
}
ASSERT_TLM_FilesSent_SIZE(1);
ASSERT_TLM_FilesSent(0, 1);
// Assert events
ASSERT_EVENTS_SIZE(2);
ASSERT_EVENTS_FileSent_SIZE(1);
ASSERT_EVENTS_SendStarted_SIZE(1);
// printTextLogHistory(stdout);
ASSERT_EVENTS_SendStarted(0, 10, sourceFileName, destFileName);
ASSERT_EVENTS_FileSent(0, sourceFileName, destFileName);
// Validate the packet history
History<Fw::FilePacket::DataPacket> dataPackets(MAX_HISTORY_SIZE);
CFDP::Checksum checksum;
fileBufferOut.getChecksum(checksum);
validatePacketHistory(*this->fromPortHistory_bufferSendOut, dataPackets, Fw::FilePacket::T_END, 3, checksum, 0);
// Compare the outgoing and incoming files
FileBuffer fileBufferIn(dataPackets);
ASSERT_EQ(true, FileBuffer::compare(fileBufferIn, fileBufferOut));
// Assert idle mode
ASSERT_EQ(FileDownlink::Mode::IDLE, this->component.m_mode.get());
// Remove the outgoing file
this->removeFile(sourceFileName);
}
void FileDownlinkTester ::fileOpenError() {
const char* const sourceFileName = "missing_directory/source.bin";
const char* const destFileName = "dest.bin";
// Send the file and assert CmdResponse::EXECUTION_ERROR
this->sendFile(sourceFileName, destFileName,
(FILEDOWNLINK_COMMAND_FAILURES_DISABLED) ? Fw::CmdResponse::OK : Fw::CmdResponse::EXECUTION_ERROR);
// Assert telemetry
ASSERT_TLM_SIZE(1);
ASSERT_TLM_Warnings(0, 1);
// Assert events
ASSERT_EVENTS_SIZE(1);
ASSERT_EVENTS_FileOpenError_SIZE(1);
// ASSERT_EVENTS_FileDownlink_FileOpenError(0, sourceFileName);
}
void FileDownlinkTester ::cancelDownlink() {
// Create a file
const char* const sourceFileName = "source.bin";
const char* const destFileName = "dest.bin";
U8 data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
FileBuffer fileBufferOut(data, sizeof(data));
fileBufferOut.write(sourceFileName);
// Send a file
Fw::CmdStringArg sourceCmdStringArg(sourceFileName);
Fw::CmdStringArg destCmdStringArg(destFileName);
this->sendCmd_SendFile(INSTANCE, CMD_SEQ, sourceCmdStringArg, destCmdStringArg);
this->sendCmd_Cancel(INSTANCE, CMD_SEQ);
this->component.doDispatch(); // Dispatch start command
this->component.Run_handler(0, 0); // Enqueue and start processing file
this->component.doDispatch(); // Dispatch cancel command
// Assert cancelation response
ASSERT_CMD_RESPONSE_SIZE(1);
ASSERT_CMD_RESPONSE(0, FileDownlink::OPCODE_CANCEL, CMD_SEQ, Fw::CmdResponse::OK);
this->cmdResponseHistory->clear();
ASSERT_EQ(FileDownlink::Mode::CANCEL, this->component.m_mode.get());
this->component.doDispatch(); // Process return of original buffer and send cancel packet
this->component.doDispatch(); // Process return of cancel packet
// Ensure initial send file command also receives a response.
Fw::CmdResponse resp =
(FILEDOWNLINK_COMMAND_FAILURES_DISABLED) ? Fw::CmdResponse::OK : Fw::CmdResponse::EXECUTION_ERROR;
ASSERT_CMD_RESPONSE_SIZE(1);
ASSERT_CMD_RESPONSE(0, FileDownlink::OPCODE_SENDFILE, CMD_SEQ, resp);
// Assert telemetry
ASSERT_TLM_SIZE(2);
// Assert cancel event
ASSERT_EVENTS_SIZE(2); // Started and cancel
ASSERT_EVENTS_DownlinkCanceled_SIZE(1);
ASSERT_EQ(FileDownlink::Mode::COOLDOWN, this->component.m_mode.get());
this->removeFile(sourceFileName);
}
void FileDownlinkTester ::cancelInIdleMode() {
// Assert idle mode
ASSERT_EQ(FileDownlink::Mode::IDLE, this->component.m_mode.get());
// Send a cancel command
this->cancel(Fw::CmdResponse::OK);
this->component.Run_handler(0, 0);
// Assert idle mode
ASSERT_EQ(FileDownlink::Mode::IDLE, this->component.m_mode.get());
}
void FileDownlinkTester ::downlinkPartial() {
// Assert idle mode
ASSERT_EQ(FileDownlink::Mode::IDLE, this->component.m_mode.get());
// Create a file
const char* const sourceFileName = "source.bin";
const char* const destFileName = "dest.bin";
U8 data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
U8 dataSubset[] = {1, 2, 3, 4};
U32 offset = 1;
U32 length = 4;
FileBuffer fileBufferOut(data, sizeof(data));
fileBufferOut.write(sourceFileName);
FileBuffer fileBufferOutSubset(dataSubset, sizeof(dataSubset));
// Test send partial past end of file, should return COMMAND_OK but raise a warning event.
Fw::CmdResponse expResp =
FILEDOWNLINK_COMMAND_FAILURES_DISABLED ? Fw::CmdResponse::OK : Fw::CmdResponse::VALIDATION_ERROR;
this->sendFilePartial(sourceFileName, destFileName, expResp, sizeof(data), length);
ASSERT_EVENTS_SIZE(1);
ASSERT_EVENTS_DownlinkPartialFail(0, sourceFileName, destFileName, sizeof(data), sizeof(data));
this->cmdResponseHistory->clear();
this->clearEvents();
// Send the file and assert COMMAND_OK
this->sendFilePartial(sourceFileName, destFileName, Fw::CmdResponse::OK, offset, length);
// Assert telemetry
ASSERT_TLM_SIZE(4);
ASSERT_TLM_PacketsSent_SIZE(3);
for (size_t i = 0; i < 3; ++i) {
ASSERT_TLM_PacketsSent(i, i + 1);
}
ASSERT_TLM_FilesSent_SIZE(1);
ASSERT_TLM_FilesSent(0, 1);
// Assert events
ASSERT_EVENTS_SIZE(2); // Start and sent
ASSERT_EVENTS_SendStarted_SIZE(1);
ASSERT_EVENTS_FileSent_SIZE(1);
// printTextLogHistory(stdout);
ASSERT_EVENTS_FileSent(0, sourceFileName, destFileName);
ASSERT_EVENTS_SendStarted(0, length, sourceFileName, destFileName);
// Validate the packet history
History<Fw::FilePacket::DataPacket> dataPackets(MAX_HISTORY_SIZE);
CFDP::Checksum checksum;
checksum.update(dataSubset, offset, length);
validatePacketHistory(*this->fromPortHistory_bufferSendOut, dataPackets, Fw::FilePacket::T_END, 3, checksum, 1);
// Compare the outgoing and incoming files
FileBuffer fileBufferIn(dataPackets);
ASSERT_EQ(true, FileBuffer::compare(fileBufferIn, fileBufferOutSubset));
// Assert idle mode
ASSERT_EQ(FileDownlink::Mode::IDLE, this->component.m_mode.get());
// Remove the outgoing file
this->removeFile(sourceFileName);
}
void FileDownlinkTester ::timeout() {
// Assert idle mode
ASSERT_EQ(FileDownlink::Mode::IDLE, this->component.m_mode.get());
// Create a file
const char* const sourceFileName = "source.bin";
const char* const destFileName = "dest.bin";
U8 data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
FileBuffer fileBufferOut(data, sizeof(data));
fileBufferOut.write(sourceFileName);
Fw::CmdStringArg sourceCmdStringArg(sourceFileName);
Fw::CmdStringArg destCmdStringArg(destFileName);
this->sendCmd_SendFile(INSTANCE, CMD_SEQ, sourceCmdStringArg, destCmdStringArg);
this->component.doDispatch(); // Dispatch sendfile command
this->component.Run_handler(0, 0); // Pull file from queue
// Continue running the component without dispatching the responses
for (U32 i = 0; i < TIMEOUT_MS / CYCLE_MS; i++) {
this->component.Run_handler(0, 0);
ASSERT_CMD_RESPONSE_SIZE(0);
}
this->component.Run_handler(0, 0);
ASSERT_CMD_RESPONSE_SIZE(1);
Fw::CmdResponse expResp =
FILEDOWNLINK_COMMAND_FAILURES_DISABLED ? Fw::CmdResponse::OK : Fw::CmdResponse::EXECUTION_ERROR;
ASSERT_CMD_RESPONSE(0, FileDownlink::OPCODE_SENDFILE, CMD_SEQ, expResp);
// Assert telemetry
ASSERT_TLM_SIZE(1);
ASSERT_TLM_PacketsSent_SIZE(1);
// Assert events
ASSERT_EVENTS_SIZE(2);
ASSERT_EVENTS_DownlinkTimeout_SIZE(1);
ASSERT_EVENTS_SendStarted_SIZE(1);
// printTextLogHistory(stdout);
ASSERT_EVENTS_SendStarted(0, 10, sourceFileName, destFileName);
ASSERT_EVENTS_DownlinkTimeout(0, sourceFileName, destFileName);
// Assert idle mode
ASSERT_EQ(FileDownlink::Mode::COOLDOWN, this->component.m_mode.get());
// Remove the outgoing file
this->removeFile(sourceFileName);
}
void FileDownlinkTester ::sendFilePort() {
// Create a file
const char* const sourceFileName = "source.bin";
const char* const destFileName = "dest.bin";
U8 data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
FileBuffer fileBufferOut(data, sizeof(data));
fileBufferOut.write(sourceFileName);
Fw::String srcFileArg(sourceFileName);
Fw::String destFileArg(destFileName);
Svc::SendFileResponse resp = this->invoke_to_SendFile(0, srcFileArg, destFileArg, 0, 0);
ASSERT_EQ(resp.get_status(), SendFileStatus::STATUS_OK);
ASSERT_EQ(resp.get_context(), 0);
this->component.Run_handler(0, 0); // Dequeue file downlink request
while (this->component.m_mode.get() != FileDownlink::Mode::IDLE) {
if (this->component.m_mode.get() != FileDownlink::Mode::COOLDOWN) {
this->component.doDispatch();
}
this->component.Run_handler(0, 0);
}
ASSERT_from_FileComplete_SIZE(1);
ASSERT_from_FileComplete(0, Svc::SendFileResponse(SendFileStatus(SendFileStatus::STATUS_OK), 0));
// Assert telemetry
ASSERT_TLM_SIZE(4);
ASSERT_TLM_PacketsSent_SIZE(3);
for (size_t i = 0; i < 3; ++i) {
ASSERT_TLM_PacketsSent(i, i + 1);
}
ASSERT_TLM_FilesSent_SIZE(1);
ASSERT_TLM_FilesSent(0, 1);
// Assert events
ASSERT_EVENTS_SIZE(2);
ASSERT_EVENTS_FileSent_SIZE(1);
ASSERT_EVENTS_SendStarted_SIZE(1);
ASSERT_EVENTS_SendStarted(0, 10, sourceFileName, destFileName);
ASSERT_EVENTS_FileSent(0, sourceFileName, destFileName);
// Validate the packet history
History<Fw::FilePacket::DataPacket> dataPackets(MAX_HISTORY_SIZE);
CFDP::Checksum checksum;
fileBufferOut.getChecksum(checksum);
validatePacketHistory(*this->fromPortHistory_bufferSendOut, dataPackets, Fw::FilePacket::T_END, 3, checksum, 0);
// Compare the outgoing and incoming files
FileBuffer fileBufferIn(dataPackets);
ASSERT_EQ(true, FileBuffer::compare(fileBufferIn, fileBufferOut));
// Remove the outgoing file
this->removeFile(sourceFileName);
}
// ----------------------------------------------------------------------
// Handlers for from ports
// ----------------------------------------------------------------------
void FileDownlinkTester ::from_bufferSendOut_handler(const FwIndexType portNum, Fw::Buffer& buffer) {
ASSERT_LT(buffers_index, FW_NUM_ARRAY_ELEMENTS(this->buffers));
// Copy buffer before recycling
U8* data = new U8[buffer.getSize()];
this->buffers[buffers_index] = data; // NOLINT(clang-analyzer-security.ArrayBound)
buffers_index++;
::memcpy(data, buffer.getData(), buffer.getSize());
Fw::Buffer buffer_new = buffer;
buffer_new.setData(data);
pushFromPortEntry_bufferSendOut(buffer_new);
invoke_to_bufferReturn(0, buffer);
}
void FileDownlinkTester ::from_pingOut_handler(const FwIndexType portNum, U32 key) {
pushFromPortEntry_pingOut(key);
}
void FileDownlinkTester ::from_FileComplete_handler(const FwIndexType portNum, /*!< The port number*/
const Svc::SendFileResponse& response) {
pushFromPortEntry_FileComplete(response);
}
// ----------------------------------------------------------------------
// Private instance methods
// ----------------------------------------------------------------------
void FileDownlinkTester ::connectPorts() {
// cmdIn
this->connect_to_cmdIn(0, this->component.get_cmdIn_InputPort(0));
// Run
this->connect_to_Run(0, this->component.get_Run_InputPort(0));
// bufferReturn
this->connect_to_bufferReturn(0, this->component.get_bufferReturn_InputPort(0));
// Sendfile
this->connect_to_SendFile(0, this->component.get_SendFile_InputPort(0));
// timeCaller
this->component.set_timeCaller_OutputPort(0, this->get_from_timeCaller(0));
// cmdRegOut
this->component.set_cmdRegOut_OutputPort(0, this->get_from_cmdRegOut(0));
// eventOut
this->component.set_eventOut_OutputPort(0, this->get_from_eventOut(0));
// bufferSendOut
this->component.set_bufferSendOut_OutputPort(0, this->get_from_bufferSendOut(0));
// tlmOut
this->component.set_tlmOut_OutputPort(0, this->get_from_tlmOut(0));
// cmdResponseOut
this->component.set_cmdResponseOut_OutputPort(0, this->get_from_cmdResponseOut(0));
// LogText
this->component.set_textEventOut_OutputPort(0, this->get_from_textEventOut(0));
// FileComplete
this->component.set_FileComplete_OutputPort(0, this->get_from_FileComplete(0));
}
void FileDownlinkTester ::initComponents() {
this->init();
this->component.init(QUEUE_DEPTH, INSTANCE);
}
void FileDownlinkTester ::sendFile(const char* const sourceFileName,
const char* const destFileName,
const Fw::CmdResponse response) {
Fw::CmdStringArg sourceCmdStringArg(sourceFileName);
Fw::CmdStringArg destCmdStringArg(destFileName);
this->sendCmd_SendFile(INSTANCE, CMD_SEQ, sourceCmdStringArg, destCmdStringArg);
this->component.doDispatch();
this->component.Run_handler(0, 0);
while (this->component.m_mode.get() != FileDownlink::Mode::IDLE) {
if (this->component.m_mode.get() != FileDownlink::Mode::COOLDOWN) {
this->component.doDispatch();
}
this->component.Run_handler(0, 0);
}
ASSERT_CMD_RESPONSE_SIZE(1);
ASSERT_CMD_RESPONSE(0, FileDownlink::OPCODE_SENDFILE, CMD_SEQ, response);
}
void FileDownlinkTester ::sendFilePartial(const char* const sourceFileName, //!< The source file name
const char* const destFileName, //!< The destination file name
const Fw::CmdResponse response, //!< The expected command response
U32 startIndex, //!< The starting index
U32 length //!< The amount of bytes to downlink
) {
Fw::CmdStringArg sourceCmdStringArg(sourceFileName);
Fw::CmdStringArg destCmdStringArg(destFileName);
this->sendCmd_SendPartial(INSTANCE, CMD_SEQ, sourceCmdStringArg, destCmdStringArg, startIndex, length);
this->component.doDispatch();
this->component.Run_handler(0, 0);
while (this->component.m_mode.get() != FileDownlink::Mode::IDLE) {
if (this->component.m_mode.get() != FileDownlink::Mode::COOLDOWN) {
this->component.doDispatch();
}
this->component.Run_handler(0, 0);
}
ASSERT_CMD_RESPONSE_SIZE(1);
ASSERT_CMD_RESPONSE(0, FileDownlink::OPCODE_SENDPARTIAL, CMD_SEQ, response);
}
void FileDownlinkTester ::cancel(const Fw::CmdResponse response) {
// Command the File Downlink component to cancel a file downlink
this->sendCmd_Cancel(INSTANCE, CMD_SEQ);
this->component.doDispatch(); // Cancel command processes here
// Assert command response
ASSERT_CMD_RESPONSE_SIZE(1);
ASSERT_CMD_RESPONSE(0, FileDownlink::OPCODE_CANCEL, CMD_SEQ, response);
}
void FileDownlinkTester ::removeFile(const char* const name) {
const int status = ::unlink(name);
if (status != 0) {
// OK if file is not there
ASSERT_EQ(ENOENT, errno);
}
}
// ----------------------------------------------------------------------
// Private static methods
// ----------------------------------------------------------------------
void FileDownlinkTester ::validatePacketHistory(const History<FromPortEntry_bufferSendOut>& historyIn,
History<Fw::FilePacket::DataPacket>& historyOut,
const Fw::FilePacket::Type finalPacketType,
const size_t numPackets,
const CFDP::Checksum& checksum,
U32 startOffset) {
FW_ASSERT(numPackets > 0);
const size_t size = historyIn.size();
ASSERT_EQ(numPackets, size);
{
const Fw::Buffer& buffer = historyIn.at(0).fwBuffer;
validateStartPacket(buffer);
}
for (size_t i = 1; i < size - 1; ++i) {
const Fw::Buffer& buffer = historyIn.at(i).fwBuffer;
Fw::FilePacket::DataPacket dataPacket;
validateDataPacket(buffer, dataPacket, i, startOffset);
historyOut.push_back(dataPacket);
}
const size_t index = size - 1;
const Fw::Buffer& buffer = historyIn.at(index).fwBuffer;
switch (finalPacketType) {
case Fw::FilePacket::T_END: {
validateEndPacket(buffer, index, checksum);
break;
}
case Fw::FilePacket::T_CANCEL: {
validateCancelPacket(buffer, index);
break;
}
default:
FW_ASSERT(0);
}
}
void FileDownlinkTester ::validateFilePacket(const Fw::Buffer& buffer, Fw::FilePacket& filePacket) {
// buffer contains the FW_PACKET_FILE descriptor - we remove it before deserializing into the filePacket
Fw::Buffer packetDataBuffer(buffer.getData() + sizeof(FwPacketDescriptorType),
buffer.getSize() - sizeof(FwPacketDescriptorType));
const Fw::SerializeStatus status = filePacket.fromBuffer(packetDataBuffer);
ASSERT_EQ(Fw::FW_SERIALIZE_OK, status);
}
void FileDownlinkTester ::validateStartPacket(const Fw::Buffer& buffer) {
Fw::FilePacket filePacket;
validateFilePacket(buffer, filePacket);
const Fw::FilePacket::Header& header = filePacket.asHeader();
ASSERT_EQ(0U, header.m_sequenceIndex);
ASSERT_EQ(Fw::FilePacket::T_START, header.m_type);
}
void FileDownlinkTester ::validateDataPacket(const Fw::Buffer& buffer,
Fw::FilePacket::DataPacket& dataPacket,
const U32 sequenceIndex,
U32& byteOffset) {
Fw::FilePacket filePacket;
validateFilePacket(buffer, filePacket);
const Fw::FilePacket::Header& header = filePacket.asHeader();
ASSERT_EQ(sequenceIndex, header.m_sequenceIndex);
ASSERT_EQ(Fw::FilePacket::T_DATA, header.m_type);
dataPacket = filePacket.asDataPacket();
ASSERT_EQ(byteOffset, dataPacket.m_byteOffset);
byteOffset += dataPacket.m_dataSize;
}
void FileDownlinkTester ::validateEndPacket(const Fw::Buffer& buffer,
const U32 sequenceIndex,
const CFDP::Checksum& checksum) {
Fw::FilePacket filePacket;
validateFilePacket(buffer, filePacket);
const Fw::FilePacket::Header& header = filePacket.asHeader();
ASSERT_EQ(sequenceIndex, header.m_sequenceIndex);
ASSERT_EQ(Fw::FilePacket::T_END, header.m_type);
const Fw::FilePacket::EndPacket endPacket = filePacket.asEndPacket();
CFDP::Checksum computedChecksum;
endPacket.getChecksum(computedChecksum);
ASSERT_EQ(true, checksum == computedChecksum);
}
void FileDownlinkTester ::validateCancelPacket(const Fw::Buffer& buffer, const U32 sequenceIndex) {
Fw::FilePacket filePacket;
validateFilePacket(buffer, filePacket);
const Fw::FilePacket::Header& header = filePacket.asHeader();
ASSERT_EQ(sequenceIndex, header.m_sequenceIndex);
ASSERT_EQ(Fw::FilePacket::T_CANCEL, header.m_type);
}
} // namespace Svc