diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 57dd875791..8b8c21ca72 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -1616,7 +1616,7 @@ vwong vxworks VXWORKSLOGASSERT WAITALL -Watney +watney Wconversion Wdog weakref diff --git a/Svc/CMakeLists.txt b/Svc/CMakeLists.txt index c04257bba2..fef3e7c53a 100644 --- a/Svc/CMakeLists.txt +++ b/Svc/CMakeLists.txt @@ -23,6 +23,7 @@ add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/ComSplitter/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/ComStub/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/CmdDispatcher/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/CmdSequencer/") +add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/CmdSplitter/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Deframer/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/FatalHandler/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/FileDownlinkPorts/") diff --git a/Svc/CmdSplitter/CMakeLists.txt b/Svc/CmdSplitter/CMakeLists.txt new file mode 100644 index 0000000000..85a5c730a0 --- /dev/null +++ b/Svc/CmdSplitter/CMakeLists.txt @@ -0,0 +1,15 @@ +set(SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/CmdSplitter.fpp" + "${CMAKE_CURRENT_LIST_DIR}/CmdSplitter.cpp" +) +register_fprime_module() + +set(UT_SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/CmdSplitter.fpp" + "${CMAKE_CURRENT_LIST_DIR}/test/ut/TestMain.cpp" + "${CMAKE_CURRENT_LIST_DIR}/test/ut/Tester.cpp" + +) +set(UT_MOD_DEPS STest) +set(UT_AUTO_HELPERS ON) +register_fprime_ut() \ No newline at end of file diff --git a/Svc/CmdSplitter/CmdSplitter.cpp b/Svc/CmdSplitter/CmdSplitter.cpp new file mode 100644 index 0000000000..6d850e33d6 --- /dev/null +++ b/Svc/CmdSplitter/CmdSplitter.cpp @@ -0,0 +1,51 @@ +// ====================================================================== +// \title CmdSplitter.cpp +// \author watney +// \brief cpp file for CmdSplitter component implementation class +// ====================================================================== + +#include +#include +#include +#include + +namespace Svc { + +// ---------------------------------------------------------------------- +// Construction, initialization, and destruction +// ---------------------------------------------------------------------- + +CmdSplitter ::CmdSplitter(const char* const compName) : CmdSplitterComponentBase(compName) {} + +CmdSplitter ::~CmdSplitter() {} + +// ---------------------------------------------------------------------- +// Handler implementations for user-defined typed input ports +// ---------------------------------------------------------------------- + +void CmdSplitter ::CmdBuff_handler(const NATIVE_INT_TYPE portNum, Fw::ComBuffer& data, U32 context) { + Fw::CmdPacket cmdPkt; + Fw::SerializeStatus stat = cmdPkt.deserialize(data); + + if (stat != Fw::FW_SERIALIZE_OK) { + // Let the local command dispatcher deal with it + this->LocalCmd_out(0, data, context); + } else { + // Check if local or remote + if (cmdPkt.getOpCode() < CMD_SPLITTER_REMOTE_OPCODE_BASE) { + this->LocalCmd_out(0, data, context); + } else { + this->RemoteCmd_out(0, data, context); + } + } +} + +void CmdSplitter ::seqCmdStatus_handler(const NATIVE_INT_TYPE portNum, + FwOpcodeType opCode, + U32 cmdSeq, + const Fw::CmdResponse& response) { + // Forward the command status + this->forwardSeqCmdStatus_out(portNum, opCode, cmdSeq, response); +} + +} // end namespace Svc diff --git a/Svc/CmdSplitter/CmdSplitter.fpp b/Svc/CmdSplitter/CmdSplitter.fpp new file mode 100644 index 0000000000..6d408f05af --- /dev/null +++ b/Svc/CmdSplitter/CmdSplitter.fpp @@ -0,0 +1,27 @@ + +module Svc { + + @ A component for splitting incoming commands to local or remote + passive component CmdSplitter { + + # ---------------------------------------------------------------------- + # General ports + # ---------------------------------------------------------------------- + + @ Input port for local or remote commands + sync input port CmdBuff: Fw.Com + + @ Input port for receiving the command status + sync input port seqCmdStatus: Fw.CmdResponse + + @ Output port for forwarding the Command status + output port forwardSeqCmdStatus: Fw.CmdResponse + + @ Output port for local commands + output port LocalCmd: Fw.Com + + @ Output port for remote commands + output port RemoteCmd: Fw.Com + + } +} \ No newline at end of file diff --git a/Svc/CmdSplitter/CmdSplitter.hpp b/Svc/CmdSplitter/CmdSplitter.hpp new file mode 100644 index 0000000000..ae76cba6b6 --- /dev/null +++ b/Svc/CmdSplitter/CmdSplitter.hpp @@ -0,0 +1,54 @@ +// ====================================================================== +// \title CmdSplitter.hpp +// \author watney +// \brief hpp file for CmdSplitter component implementation class +// ====================================================================== + +#ifndef CmdSplitter_HPP +#define CmdSplitter_HPP + +#include +#include "Svc/CmdSplitter/CmdSplitterComponentAc.hpp" + +namespace Svc { + +class CmdSplitter : public CmdSplitterComponentBase { + public: + // ---------------------------------------------------------------------- + // Construction, initialization, and destruction + // ---------------------------------------------------------------------- + + //! Construct object CmdSplitter + //! + CmdSplitter(const char* const compName /*!< The component name*/ + ); + + //! Destroy object CmdSplitter + //! + ~CmdSplitter(); + + PRIVATE : + + // ---------------------------------------------------------------------- + // Handler implementations for user-defined typed input ports + // ---------------------------------------------------------------------- + + //! Handler implementation for CmdBuff + //! + void CmdBuff_handler(const NATIVE_INT_TYPE portNum, /*!< The port number */ + Fw::ComBuffer& data, /*!< Buffer containing packet data */ + U32 context /*!< Call context value; meaning chosen by user */ + ); + + //! Handler implementation for seqCmdStatus + //! + void seqCmdStatus_handler(const NATIVE_INT_TYPE portNum, /*!< The port number */ + FwOpcodeType opCode, /*!< Command Op Code */ + U32 cmdSeq, /*!< Command Sequence */ + const Fw::CmdResponse& response /*!< The command response argument */ + ); +}; + +} // end namespace Svc + +#endif diff --git a/Svc/CmdSplitter/docs/sdd.md b/Svc/CmdSplitter/docs/sdd.md new file mode 100644 index 0000000000..206b53fede --- /dev/null +++ b/Svc/CmdSplitter/docs/sdd.md @@ -0,0 +1,55 @@ +\page SvcCmdSplitter Svc::CmdSplitter Component +# Svc::CmdSplitter Component + +## 1. Introduction + +The `Svc::CmdSplitter` splits an uplinked command execution to two separate `Svc::CmdDispatcher` components: one "local" and the other "remote". This splitting is done by opcode where local commands are commands whose opcode is less than `Svc::CMD_SPLITTER_REMOTE_OPCODE_BASE` and remote commands are those opcodes equal to or larger than that configuration setting. `Svc::CmdSplitter` is intended to be used as part of the hub pattern to route command to a command dispatcher in the remote deployment. + +## 2. Requirements + +The requirements for `Svc::CmdSplitter` are as follows: + +| Requirement | Description | Verification Method | +|----------------------|------------------------------------------------------------------------------------------------------|---------------------| +| SVC-CMD-SPLITTER-001 | The `Svc::CmdSplitter` component shall accept incoming command buffers. | Unit Test | +| SVC-CMD-SPLITTER-002 | The `Svc::CmdSplitter` component shall route commands under a configured value to the "local" port. | Unit Test | +| SVC-CMD-SPLITTER-003 | The `Svc::CmdSplitter` component shall route commands under a configured value to the "remote" port. | Unit Test | +| SVC-CMD-SPLITTER-004 | The `Svc::CmdSplitter` component shall route commands to the "local" port when an error occurs. | Unit Test | +| SVC-CMD-SPLITTER-005 | The `Svc::CmdSplitter` forward command status responses. | Unit Test | + +## 3. Design + +### 3.1 Ports + +| Name | Type | Kind | Description | +|---------------------|----------------|------------|-----------------------------------------------------------------| +| CmdBuff | Fw.Com | sync input | Incoming command buffer. | +| seqCmdStatus | Fw.CmdResponse | sync input | Incoming command status from both local and remote dispatchers. | +| LocalCmd | Fw.Com | sync input | Outgoing command buffer for local command dispatcher. | +| RemoteCmd | Fw.Com | sync input | Outgoing command buffer for remote command dispatcher. | +| forwardSeqCmdStatus | Fw.CmdResponse | sync input | Outgoing forwarded command status. | + +### 3.2 Functional Description + +The `Svc::CmdSplitter` routes an incoming command buffer of type `Fw::ComBuffer` to a local or remote command dispatcher. This is done by comparing the command's opcode to the `Svc::CMD_SPLITTER_REMOTE_OPCODE_BASE` configuration value. All command responses are forwarded through. + +### 3.1 State + +`Svc::CmdSplitter` has no state machines nor internal state. + +### 3.2 Algorithms + +`Svc::CmdSplitter` has no significant algorithms. + +## 4. Unit Testing + +To see unit test coverage run `fprime-util check --coverage` in the `Svc::CmdSplitter` directory + +## 5. Change Log + +| Date | Description | +|------------|-------------| +| 2023-06-12 | Initial | + + + diff --git a/Svc/CmdSplitter/test/ut/TestMain.cpp b/Svc/CmdSplitter/test/ut/TestMain.cpp new file mode 100644 index 0000000000..15566c052e --- /dev/null +++ b/Svc/CmdSplitter/test/ut/TestMain.cpp @@ -0,0 +1,33 @@ +// ---------------------------------------------------------------------- +// TestMain.cpp +// ---------------------------------------------------------------------- + +#include "Tester.hpp" + +TEST(Nominal, Local) { + Svc::Tester tester; + tester.test_local_routing(); +} + +TEST(Nominal, Remote) { + Svc::Tester tester; + tester.test_remote_routing(); +} + +TEST(Nominal, Forwarding) { + Svc::Tester tester; + tester.test_response_forwarding(); +} + +TEST(Error, BadCommands) { + Svc::Tester tester; + tester.test_error_routing(); +} + + + + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/Svc/CmdSplitter/test/ut/Tester.cpp b/Svc/CmdSplitter/test/ut/Tester.cpp new file mode 100644 index 0000000000..6a44e98daf --- /dev/null +++ b/Svc/CmdSplitter/test/ut/Tester.cpp @@ -0,0 +1,123 @@ +// ====================================================================== +// \title CmdSplitter.hpp +// \author mstarch +// \brief cpp file for CmdSplitter test harness implementation class +// ====================================================================== + +#include "Tester.hpp" +#include +#include +#include +#include + +namespace Svc { + +// ---------------------------------------------------------------------- +// Construction and destruction +// ---------------------------------------------------------------------- + +Tester ::Tester() : CmdSplitterGTestBase("Tester", Tester::MAX_HISTORY_SIZE), component("CmdSplitter") { + this->initComponents(); + this->connectPorts(); +} + +Tester ::~Tester() {} + +// ---------------------------------------------------------------------- +// Tests +// ---------------------------------------------------------------------- + +Fw::ComBuffer Tester ::build_command_around_opcode(FwOpcodeType opcode) { + Fw::ComBuffer comBuffer; + EXPECT_EQ(comBuffer.serialize(static_cast(Fw::ComPacket::FW_PACKET_COMMAND)), Fw::FW_SERIALIZE_OK); + EXPECT_EQ(comBuffer.serialize(opcode), Fw::FW_SERIALIZE_OK); + + Fw::CmdArgBuffer args; + + U32 random_size = STest::Pick::lowerUpper(0, args.getBuffCapacity()); + args.resetSer(); + for (FwSizeType i = 0; i < random_size; i++) { + args.serialize(static_cast(STest::Pick::any())); + } + EXPECT_EQ(comBuffer.serialize(args), Fw::FW_SERIALIZE_OK); + return comBuffer; +} + +void Tester ::test_local_routing() { + REQUIREMENT("SVC-CMD-SPLITTER-001"); + REQUIREMENT("SVC-CMD-SPLITTER-002"); + + ASSERT_GT(Svc::CMD_SPLITTER_REMOTE_OPCODE_BASE, 0); // Must leave some room for local commands + FwOpcodeType local_opcode = static_cast(STest::Pick::lowerUpper( + 0, FW_MIN(Svc::CMD_SPLITTER_REMOTE_OPCODE_BASE - 1, std::numeric_limits::max()))); + Fw::ComBuffer testBuffer = this->build_command_around_opcode(local_opcode); + + U32 context = static_cast(STest::Pick::any()); + this->invoke_to_CmdBuff(0, testBuffer, context); + ASSERT_from_RemoteCmd_SIZE(0); + ASSERT_from_LocalCmd_SIZE(1); + ASSERT_from_LocalCmd(0, testBuffer, context); +} + +void Tester ::test_remote_routing() { + REQUIREMENT("SVC-CMD-SPLITTER-001"); + REQUIREMENT("SVC-CMD-SPLITTER-003"); + + ASSERT_LT(Svc::CMD_SPLITTER_REMOTE_OPCODE_BASE, + std::numeric_limits::max()); // Must leave some room for remote commands + FwOpcodeType local_opcode = static_cast( + STest::Pick::lowerUpper(Svc::CMD_SPLITTER_REMOTE_OPCODE_BASE, std::numeric_limits::max())); + Fw::ComBuffer testBuffer = this->build_command_around_opcode(local_opcode); + + U32 context = static_cast(STest::Pick::any()); + this->invoke_to_CmdBuff(0, testBuffer, context); + ASSERT_from_LocalCmd_SIZE(0); + ASSERT_from_RemoteCmd_SIZE(1); + ASSERT_from_RemoteCmd(0, testBuffer, context); +} + +void Tester ::test_error_routing() { + REQUIREMENT("SVC-CMD-SPLITTER-001"); + REQUIREMENT("SVC-CMD-SPLITTER-004"); + Fw::ComBuffer testBuffer; // Intentionally left empty + U32 context = static_cast(STest::Pick::any()); + this->invoke_to_CmdBuff(0, testBuffer, context); + ASSERT_from_RemoteCmd_SIZE(0); + ASSERT_from_LocalCmd_SIZE(1); + ASSERT_from_LocalCmd(0, testBuffer, context); +} + +void Tester ::test_response_forwarding() { + REQUIREMENT("SVC-CMD-SPLITTER-001"); + REQUIREMENT("SVC-CMD-SPLITTER-005"); + + FwOpcodeType opcode = + static_cast(STest::Pick::lowerUpper(0, std::numeric_limits::max())); + Fw::CmdResponse response; + response.e = static_cast(STest::Pick::lowerUpper(0, Fw::CmdResponse::NUM_CONSTANTS)); + U32 cmdSeq = static_cast(STest::Pick::any()); + this->invoke_to_seqCmdStatus(0, opcode, cmdSeq, response); + ASSERT_from_forwardSeqCmdStatus_SIZE(1); + ASSERT_from_forwardSeqCmdStatus(0, opcode, cmdSeq, response); +} + +// ---------------------------------------------------------------------- +// Handlers for typed from ports +// ---------------------------------------------------------------------- + +void Tester ::from_LocalCmd_handler(const NATIVE_INT_TYPE portNum, Fw::ComBuffer& data, U32 context) { + this->pushFromPortEntry_LocalCmd(data, context); +} + +void Tester ::from_RemoteCmd_handler(const NATIVE_INT_TYPE portNum, Fw::ComBuffer& data, U32 context) { + this->pushFromPortEntry_RemoteCmd(data, context); +} + +void Tester ::from_forwardSeqCmdStatus_handler(const NATIVE_INT_TYPE portNum, + FwOpcodeType opCode, + U32 cmdSeq, + const Fw::CmdResponse& response) { + this->pushFromPortEntry_forwardSeqCmdStatus(opCode, cmdSeq, response); +} + +} // end namespace Svc diff --git a/Svc/CmdSplitter/test/ut/Tester.hpp b/Svc/CmdSplitter/test/ut/Tester.hpp new file mode 100644 index 0000000000..02ad1d07d5 --- /dev/null +++ b/Svc/CmdSplitter/test/ut/Tester.hpp @@ -0,0 +1,111 @@ +// ====================================================================== +// \title CmdSplitter/test/ut/Tester.hpp +// \author mstarch +// \brief hpp file for CmdSplitter test harness implementation class +// ====================================================================== + +#ifndef TESTER_HPP +#define TESTER_HPP + +#include "GTestBase.hpp" +#include "Svc/CmdSplitter/CmdSplitter.hpp" + +namespace Svc { + +class Tester : public CmdSplitterGTestBase { + // ---------------------------------------------------------------------- + // Construction and destruction + // ---------------------------------------------------------------------- + + public: + // Maximum size of histories storing events, telemetry, and port outputs + static const NATIVE_INT_TYPE MAX_HISTORY_SIZE = 10; + // Instance ID supplied to the component instance under test + static const NATIVE_INT_TYPE TEST_INSTANCE_ID = 0; + + //! Construct object Tester + //! + Tester(); + + //! Destroy object Tester + //! + ~Tester(); + + public: + // ---------------------------------------------------------------------- + // Tests + // ---------------------------------------------------------------------- + + //! Test that commands under a limit route locally + //! + void test_local_routing(); + + //! Test the commands above the limit route remotely + //! + void test_remote_routing(); + + //! Test that errored command route locally + //! + void test_error_routing(); + + //! Test that command response forwarding works + //! + void test_response_forwarding(); + + private: + //! Helper to build a com buffer given an opcode + //! + Fw::ComBuffer build_command_around_opcode(FwOpcodeType opcode); + + // ---------------------------------------------------------------------- + // Handlers for typed from ports + // ---------------------------------------------------------------------- + + //! Handler for from_LocalCmd + //! + void from_LocalCmd_handler(const NATIVE_INT_TYPE portNum, /*!< The port number*/ + Fw::ComBuffer& data, /*!< Buffer containing packet data */ + U32 context /*!< Call context value; meaning chosen by user */ + ); + + //! Handler for from_RemoteCmd + //! + void from_RemoteCmd_handler(const NATIVE_INT_TYPE portNum, /*!< The port number*/ + Fw::ComBuffer& data, /*!< Buffer containing packet data */ + U32 context /*!< Call context value; meaning chosen by user */ + ); + + //! Handler for from_forwardSeqCmdStatus + //! + void from_forwardSeqCmdStatus_handler(const NATIVE_INT_TYPE portNum, /*!< The port number*/ + FwOpcodeType opCode, /*!< Command Op Code */ + U32 cmdSeq, /*!< Command Sequence */ + const Fw::CmdResponse& response /*!< The command response argument */ + ); + + private: + // ---------------------------------------------------------------------- + // Helper methods + // ---------------------------------------------------------------------- + + //! Connect ports + //! + void connectPorts(); + + //! Initialize components + //! + void initComponents(); + + private: + // ---------------------------------------------------------------------- + // Variables + // ---------------------------------------------------------------------- + + //! The component under test + //! + CmdSplitter component; +}; + +} // end namespace Svc + +#endif diff --git a/config/CmdSplitterCfg.hpp b/config/CmdSplitterCfg.hpp new file mode 100644 index 0000000000..048d773559 --- /dev/null +++ b/config/CmdSplitterCfg.hpp @@ -0,0 +1,16 @@ +/* + * CmdSplitterCfg.hpp: + * + * Used to configure the Svc::CmdSplitter component's remote opcode base. + * + */ + +#ifndef CONFIG_CMD_SPLITTER_CFG_HPP_ +#define CONFIG_CMD_SPLITTER_CFG_HPP_ + +namespace Svc { +//!< Base value for remote opcodes used by Svc::CmdSplitter +static const FwOpcodeType CMD_SPLITTER_REMOTE_OPCODE_BASE = 0x10000000; +}; + +#endif /* CONFIG_CMD_SPLITTER_CFG_HPP_ */