diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index ae00209d09..ddcfe6f9ce 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -217,6 +217,7 @@ dawbarton DDDTHH ddl ddmm +dealloc Debian deconstructor Deerin @@ -265,6 +266,8 @@ doxyrules doxysearch Doxywizard dpi +DPMANAGER +DPWRITER DRAINBUFFERS drv dsdl @@ -285,6 +288,7 @@ EGB EHAs elist ELOG +Elts emoji endcode endcond @@ -389,6 +393,7 @@ getquaternion gettime gettimeofday getty +getu ghprb gitmodules gmock diff --git a/.github/workflows/fpp-tests.yml b/.github/workflows/fpp-tests.yml index 0a95f43fbf..8c68156136 100644 --- a/.github/workflows/fpp-tests.yml +++ b/.github/workflows/fpp-tests.yml @@ -41,3 +41,10 @@ jobs: run: | fprime-util check shell: bash + - name: "Archive Logs" + uses: actions/upload-artifact@v3 + if: always() + with: + name: FppTest-Logs + path: ./FppTest/build-fprime-automatic-native-ut/Testing/Temporary/*.log + retention-days: 5 diff --git a/Autocoders/Python/src/fprime_ac/generators/visitors/PortHVisitor.py b/Autocoders/Python/src/fprime_ac/generators/visitors/PortHVisitor.py index bb9cd72f24..f9fd3a6c39 100644 --- a/Autocoders/Python/src/fprime_ac/generators/visitors/PortHVisitor.py +++ b/Autocoders/Python/src/fprime_ac/generators/visitors/PortHVisitor.py @@ -178,6 +178,8 @@ def _get_args_sum_string(self, obj): "bool", "FwBuffSizeType", "FwChanIdType", + "FwDpBuffSizeType", + "FwDpIdType", "FwEnumStoreType", "FwEventIdType", "FwIndexType", diff --git a/Autocoders/Python/src/fprime_ac/utils/TypesList.py b/Autocoders/Python/src/fprime_ac/utils/TypesList.py index 914dc52399..ccd9e159b0 100644 --- a/Autocoders/Python/src/fprime_ac/utils/TypesList.py +++ b/Autocoders/Python/src/fprime_ac/utils/TypesList.py @@ -18,6 +18,8 @@ port_types_list = [ "FwBuffSizeType", "FwChanIdType", + "FwDpBuffSizeType", + "FwDpIdType", "FwEnumStoreType", "FwEventIdType", "FwIndexType", diff --git a/FppTest/CMakeLists.txt b/FppTest/CMakeLists.txt index efbf5a1f96..af9e15ff2e 100644 --- a/FppTest/CMakeLists.txt +++ b/FppTest/CMakeLists.txt @@ -11,17 +11,16 @@ project(FppTest C CXX) include("${CMAKE_CURRENT_LIST_DIR}/../cmake/FPrime.cmake") include("${CMAKE_CURRENT_LIST_DIR}/../cmake/FPrime-Code.cmake") -if (BUILD_TESTING AND NOT __FPRIME_NO_UT_GEN__) - add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/enum/") - add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/array/") - add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/struct/") - add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/component/") -endif() - +add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/array/") +add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/component/") +add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/dp/") +add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/enum/") +add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/struct/") set(SOURCE_FILES "source.cpp") set(MOD_DEPS - ${PROJECT_NAME}/enum ${PROJECT_NAME}/array + ${PROJECT_NAME}/dp + ${PROJECT_NAME}/enum ${PROJECT_NAME}/struct ${PROJECT_NAME}/component/empty ${PROJECT_NAME}/component/active diff --git a/FppTest/dp/CMakeLists.txt b/FppTest/dp/CMakeLists.txt new file mode 100644 index 0000000000..17c6cc1729 --- /dev/null +++ b/FppTest/dp/CMakeLists.txt @@ -0,0 +1,15 @@ +set(SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/DpTest.cpp" + "${CMAKE_CURRENT_LIST_DIR}/DpTest.fpp" +) + +register_fprime_module() + +set(UT_SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/DpTest.fpp" + "${CMAKE_CURRENT_LIST_DIR}/test/ut/TestMain.cpp" + "${CMAKE_CURRENT_LIST_DIR}/test/ut/Tester.cpp" + "${CMAKE_CURRENT_LIST_DIR}/test/ut/TesterHelpers.cpp" +) +set(UT_MOD_DEPS STest) +register_fprime_ut() diff --git a/FppTest/dp/DpTest.cpp b/FppTest/dp/DpTest.cpp new file mode 100644 index 0000000000..fb24d706bc --- /dev/null +++ b/FppTest/dp/DpTest.cpp @@ -0,0 +1,168 @@ +// ====================================================================== +// \title DpTest.cpp +// \author bocchino +// \brief cpp file for DpTest component implementation class +// ====================================================================== + +#include + +#include "FppTest/dp/DpTest.hpp" +#include "Fw/Types/Assert.hpp" + +namespace FppTest { + +// ---------------------------------------------------------------------- +// Construction, initialization, and destruction +// ---------------------------------------------------------------------- + +DpTest ::DpTest(const char* const compName, + U32 u32RecordData, + U16 dataRecordData, + const U8ArrayRecordData& u8ArrayRecordData, + const U32ArrayRecordData& u32ArrayRecordData, + const DataArrayRecordData& dataArrayRecordData) + : DpTestComponentBase(compName), + u32RecordData(u32RecordData), + dataRecordData(dataRecordData), + u8ArrayRecordData(u8ArrayRecordData), + u32ArrayRecordData(u32ArrayRecordData), + dataArrayRecordData(dataArrayRecordData), + sendTime(Fw::ZERO_TIME) {} + +void DpTest ::init(const NATIVE_INT_TYPE queueDepth, const NATIVE_INT_TYPE instance) { + DpTestComponentBase::init(queueDepth, instance); +} + +DpTest ::~DpTest() {} + +// ---------------------------------------------------------------------- +// Handler implementations for user-defined typed input ports +// ---------------------------------------------------------------------- + +void DpTest::schedIn_handler(const NATIVE_INT_TYPE portNum, NATIVE_UINT_TYPE context) { + // Request a buffer for Container 1 + this->dpRequest_Container1(CONTAINER_1_DATA_SIZE); + // Request a buffer for Container 2 + this->dpRequest_Container2(CONTAINER_2_DATA_SIZE); + // Request a buffer for Container 3 + this->dpRequest_Container3(CONTAINER_3_DATA_SIZE); + // Get a buffer for Container 1 + { + DpContainer container; + Fw::Success status = this->dpGet_Container1(CONTAINER_1_DATA_SIZE, container); + FW_ASSERT(status == Fw::Success::SUCCESS, status); + // Check the container + this->checkContainer(container, ContainerId::Container1, CONTAINER_1_PACKET_SIZE); + } + // Get a buffer for Container 2 + { + DpContainer container; + Fw::Success status = this->dpGet_Container2(CONTAINER_2_DATA_SIZE, container); + FW_ASSERT(status == Fw::Success::SUCCESS); + // Check the container + this->checkContainer(container, ContainerId::Container2, CONTAINER_2_PACKET_SIZE); + } + // Get a buffer for Container 3 + { + DpContainer container; + Fw::Success status = this->dpGet_Container3(CONTAINER_3_DATA_SIZE, container); + // This one should fail + FW_ASSERT(status == Fw::Success::FAILURE); + } +} + +// ---------------------------------------------------------------------- +// Data product handler implementations +// ---------------------------------------------------------------------- + +void DpTest ::dpRecv_Container1_handler(DpContainer& container, Fw::Success::T status) { + if (status == Fw::Success::SUCCESS) { + auto serializeStatus = Fw::FW_SERIALIZE_OK; + for (FwSizeType i = 0; i < CONTAINER_1_DATA_SIZE; ++i) { + serializeStatus = container.serializeRecord_U32Record(this->u32RecordData); + if (serializeStatus == Fw::FW_SERIALIZE_NO_ROOM_LEFT) { + break; + } + FW_ASSERT(serializeStatus == Fw::FW_SERIALIZE_OK, status); + } + // Use the time stamp from the time get port + this->dpSend(container); + } +} + +void DpTest ::dpRecv_Container2_handler(DpContainer& container, Fw::Success::T status) { + if (status == Fw::Success::SUCCESS) { + const DpTest_Data dataRecord(this->dataRecordData); + auto serializeStatus = Fw::FW_SERIALIZE_OK; + for (FwSizeType i = 0; i < CONTAINER_2_DATA_SIZE; ++i) { + serializeStatus = container.serializeRecord_DataRecord(dataRecord); + if (serializeStatus == Fw::FW_SERIALIZE_NO_ROOM_LEFT) { + break; + } + FW_ASSERT(serializeStatus == Fw::FW_SERIALIZE_OK, status); + } + // Provide an explicit time stamp + this->dpSend(container, this->sendTime); + } +} + +void DpTest ::dpRecv_Container3_handler(DpContainer& container, Fw::Success::T status) { + if (status == Fw::Success::SUCCESS) { + auto serializeStatus = Fw::FW_SERIALIZE_OK; + for (FwSizeType i = 0; i < CONTAINER_3_DATA_SIZE; ++i) { + serializeStatus = + container.serializeRecord_U8ArrayRecord(this->u8ArrayRecordData.data(), this->u8ArrayRecordData.size()); + if (serializeStatus == Fw::FW_SERIALIZE_NO_ROOM_LEFT) { + break; + } + FW_ASSERT(serializeStatus == Fw::FW_SERIALIZE_OK, status); + } + // Use the time stamp from the time get port + this->dpSend(container); + } +} + +void DpTest ::dpRecv_Container4_handler(DpContainer& container, Fw::Success::T status) { + if (status == Fw::Success::SUCCESS) { + auto serializeStatus = Fw::FW_SERIALIZE_OK; + for (FwSizeType i = 0; i < CONTAINER_4_DATA_SIZE; ++i) { + serializeStatus = container.serializeRecord_U32ArrayRecord(this->u32ArrayRecordData.data(), + this->u32ArrayRecordData.size()); + if (serializeStatus == Fw::FW_SERIALIZE_NO_ROOM_LEFT) { + break; + } + FW_ASSERT(serializeStatus == Fw::FW_SERIALIZE_OK, status); + } + // Use the time stamp from the time get port + this->dpSend(container); + } +} + +void DpTest ::dpRecv_Container5_handler(DpContainer& container, Fw::Success::T status) { + if (status == Fw::Success::SUCCESS) { + auto serializeStatus = Fw::FW_SERIALIZE_OK; + for (FwSizeType i = 0; i < CONTAINER_5_DATA_SIZE; ++i) { + serializeStatus = container.serializeRecord_DataArrayRecord(this->dataArrayRecordData.data(), + this->dataArrayRecordData.size()); + if (serializeStatus == Fw::FW_SERIALIZE_NO_ROOM_LEFT) { + break; + } + FW_ASSERT(serializeStatus == Fw::FW_SERIALIZE_OK, status); + } + // Use the time stamp from the time get port + this->dpSend(container); + } +} + +// ---------------------------------------------------------------------- +// Private helper functions +// ---------------------------------------------------------------------- + +void DpTest::checkContainer(const DpContainer& container, FwDpIdType localId, FwSizeType size) const { + FW_ASSERT(container.getBaseId() == this->getIdBase(), container.getBaseId(), this->getIdBase()); + FW_ASSERT(container.getId() == container.getBaseId() + localId, container.getId(), container.getBaseId(), + ContainerId::Container1); + FW_ASSERT(container.getBuffer().getSize() == size, container.getBuffer().getSize(), size); +} + +} // end namespace FppTest diff --git a/FppTest/dp/DpTest.fpp b/FppTest/dp/DpTest.fpp new file mode 100644 index 0000000000..f8cc19f6c4 --- /dev/null +++ b/FppTest/dp/DpTest.fpp @@ -0,0 +1,82 @@ +module FppTest { + + @ A component for testing data product code gen + active component DpTest { + + # ---------------------------------------------------------------------- + # Types + # ---------------------------------------------------------------------- + + @ Data for a DataRecord + struct Data { + @ A U16 field + u16Field: U16 + } + + # ---------------------------------------------------------------------- + # Special ports + # ---------------------------------------------------------------------- + + @ Data product get port + product get port productGetOut + + @ Data product request port + product request port productRequestOut + + @ Data product receive port + async product recv port productRecvIn + + @ Data product send port + product send port productSendOut + + @ Time get port + time get port timeGetOut + + # ---------------------------------------------------------------------- + # General ports + # ---------------------------------------------------------------------- + + @ A schedIn port to run the data product generation + async input port schedIn: Svc.Sched + + # ---------------------------------------------------------------------- + # Records + # ---------------------------------------------------------------------- + + @ Record 1 + product record U32Record: U32 id 100 + + @ Record 2 + product record DataRecord: Data id 200 + + @ Record 3 + product record U8ArrayRecord: U8 array id 300 + + @ Record 4 + product record U32ArrayRecord: U32 array id 400 + + @ Record 5 + product record DataArrayRecord: Data array id 500 + + # ---------------------------------------------------------------------- + # Containers + # ---------------------------------------------------------------------- + + @ Container 1 + product container Container1 id 100 default priority 10 + + @ Container 2 + product container Container2 id 200 default priority 20 + + @ Container 3 + product container Container3 id 300 default priority 30 + + @ Container 4 + product container Container4 id 400 default priority 40 + + @ Container 5 + product container Container5 id 500 default priority 50 + + } + +} diff --git a/FppTest/dp/DpTest.hpp b/FppTest/dp/DpTest.hpp new file mode 100644 index 0000000000..61a831cd1e --- /dev/null +++ b/FppTest/dp/DpTest.hpp @@ -0,0 +1,154 @@ +// ====================================================================== +// \title DpTest.hpp +// \author bocchino +// \brief hpp file for DpTest component implementation class +// ====================================================================== + +#ifndef FppTest_DpTest_HPP +#define FppTest_DpTest_HPP + +#include + +#include "FppTest/dp/DpTestComponentAc.hpp" + +namespace FppTest { + +class DpTest : public DpTestComponentBase { + public: + // ---------------------------------------------------------------------- + // Constants + // ---------------------------------------------------------------------- + + static constexpr FwSizeType CONTAINER_1_DATA_SIZE = 100; + static constexpr FwSizeType CONTAINER_1_PACKET_SIZE = DpContainer::getPacketSizeForDataSize(CONTAINER_1_DATA_SIZE); + static constexpr FwSizeType CONTAINER_2_DATA_SIZE = 1000; + static constexpr FwSizeType CONTAINER_2_PACKET_SIZE = DpContainer::getPacketSizeForDataSize(CONTAINER_2_DATA_SIZE); + static constexpr FwSizeType CONTAINER_3_DATA_SIZE = 1000; + static constexpr FwSizeType CONTAINER_3_PACKET_SIZE = DpContainer::getPacketSizeForDataSize(CONTAINER_3_DATA_SIZE); + static constexpr FwSizeType CONTAINER_4_DATA_SIZE = 1000; + static constexpr FwSizeType CONTAINER_4_PACKET_SIZE = DpContainer::getPacketSizeForDataSize(CONTAINER_4_DATA_SIZE); + static constexpr FwSizeType CONTAINER_5_DATA_SIZE = 1000; + static constexpr FwSizeType CONTAINER_5_PACKET_SIZE = DpContainer::getPacketSizeForDataSize(CONTAINER_5_DATA_SIZE); + + public: + // ---------------------------------------------------------------------- + // Types + // ---------------------------------------------------------------------- + + using U8ArrayRecordData = std::array; + using U32ArrayRecordData = std::array; + using DataArrayRecordData = std::array; + + public: + // ---------------------------------------------------------------------- + // Construction, initialization, and destruction + // ---------------------------------------------------------------------- + + //! Construct object DpTest + DpTest(const char* const compName, //!< The component name + U32 u32RecordData, //!< The U32Record data + U16 dataRecordData, //!< The DataRecord data + const U8ArrayRecordData& u8ArrayRecordData, //!< The U8ArrayRecord data + const U32ArrayRecordData& u32ArrayRecordData, //!< The U32ArrayRecord data + const DataArrayRecordData& dataArrayRecordData //!< The DataArrayRecord data + ); + + //! Initialize object DpTest + void init(const NATIVE_INT_TYPE queueDepth, //!< The queue depth + const NATIVE_INT_TYPE instance = 0 //!< The instance number + ); + + //! Destroy object DpTest + ~DpTest(); + + public: + // ---------------------------------------------------------------------- + // Public interface methods + // ---------------------------------------------------------------------- + + //! Set the send time + void setSendTime(Fw::Time time) { this->sendTime = time; } + + PRIVATE: + // ---------------------------------------------------------------------- + // Handler implementations for user-defined typed input ports + // ---------------------------------------------------------------------- + + //! Handler implementation for schedIn + void schedIn_handler(const NATIVE_INT_TYPE portNum, //!< The port number + NATIVE_UINT_TYPE context //!< The call order + ) override; + + PRIVATE: + // ---------------------------------------------------------------------- + // Data product handler implementations + // ---------------------------------------------------------------------- + + //! Receive a data product container of type Container1 + //! \return Serialize status + void dpRecv_Container1_handler(DpContainer& container, //!< The container + Fw::Success::T //!< The container status + ) override; + + //! Receive a data product container of type Container2 + //! \return Serialize status + void dpRecv_Container2_handler(DpContainer& container, //!< The container + Fw::Success::T //!< The container status + ) override; + + //! Receive a data product container of type Container3 + //! \return Serialize status + void dpRecv_Container3_handler(DpContainer& container, //!< The container + Fw::Success::T //!< The container status + ) override; + + //! Receive a data product container of type Container4 + //! \return Serialize status + void dpRecv_Container4_handler(DpContainer& container, //!< The container + Fw::Success::T //!< The container status + ) override; + + //! Receive a data product container of type Container5 + //! \return Serialize status + void dpRecv_Container5_handler(DpContainer& container, //!< The container + Fw::Success::T //!< The container status + ) override; + + PRIVATE: + // ---------------------------------------------------------------------- + // Private helper functions + // ---------------------------------------------------------------------- + + //! Check a container for validity + void checkContainer(const DpContainer& container, //!< The container + FwDpIdType localId, //!< The expected local id + FwSizeType size //!< The expected size + ) const; + + PRIVATE: + // ---------------------------------------------------------------------- + // Private member variables + // ---------------------------------------------------------------------- + + //! U32Record data + const U32 u32RecordData; + + //! DataRecord data + const U16 dataRecordData; + + //! U8ArrayRecord data + const U8ArrayRecordData& u8ArrayRecordData; + + //! U32ArrayRecord data + const U32ArrayRecordData& u32ArrayRecordData; + + //! DataArrayRecord data + const DataArrayRecordData& dataArrayRecordData; + + //! Send time for testing + Fw::Time sendTime; +}; + +} // end namespace FppTest + +#endif diff --git a/FppTest/dp/test/ut/TestMain.cpp b/FppTest/dp/test/ut/TestMain.cpp new file mode 100644 index 0000000000..d1034cb308 --- /dev/null +++ b/FppTest/dp/test/ut/TestMain.cpp @@ -0,0 +1,81 @@ +// ---------------------------------------------------------------------- +// TestMain.cpp +// ---------------------------------------------------------------------- + +#include "FppTest/dp/test/ut/Tester.hpp" +#include "Fw/Test/UnitTest.hpp" +#include "STest/Random/Random.hpp" + +using namespace FppTest; + +TEST(schedIn, OK) { + COMMENT("schedIn OK"); + Tester tester; + tester.schedIn_OK(); +} + +TEST(productRecvIn, Container1_SUCCESS) { + COMMENT("Receive Container1 SUCCESS"); + Tester tester; + tester.productRecvIn_Container1_SUCCESS(); +} + +TEST(productRecvIn, Container1_FAILURE) { + COMMENT("Receive Container1 FAILURE"); + Tester tester; + tester.productRecvIn_Container1_FAILURE(); +} + +TEST(productRecvIn, Container2_SUCCESS) { + COMMENT("Receive Container2 SUCCESS"); + Tester tester; + tester.productRecvIn_Container2_SUCCESS(); +} + +TEST(productRecvIn, Container2_FAILURE) { + COMMENT("Receive Container2 FAILURE"); + Tester tester; + tester.productRecvIn_Container2_FAILURE(); +} + +TEST(productRecvIn, Container3_SUCCESS) { + COMMENT("Receive Container3 SUCCESS"); + Tester tester; + tester.productRecvIn_Container3_SUCCESS(); +} + +TEST(productRecvIn, Container3_FAILURE) { + COMMENT("Receive Container3 FAILURE"); + Tester tester; + tester.productRecvIn_Container3_FAILURE(); +} + +TEST(productRecvIn, Container4_SUCCESS) { + COMMENT("Receive Container4 SUCCESS"); + Tester tester; + tester.productRecvIn_Container4_SUCCESS(); +} + +TEST(productRecvIn, Container4_FAILURE) { + COMMENT("Receive Container4 FAILURE"); + Tester tester; + tester.productRecvIn_Container4_FAILURE(); +} + +TEST(productRecvIn, Container5_SUCCESS) { + COMMENT("Receive Container5 SUCCESS"); + Tester tester; + tester.productRecvIn_Container5_SUCCESS(); +} + +TEST(productRecvIn, Container5_FAILURE) { + COMMENT("Receive Container5 FAILURE"); + Tester tester; + tester.productRecvIn_Container5_FAILURE(); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + STest::Random::seed(); + return RUN_ALL_TESTS(); +} diff --git a/FppTest/dp/test/ut/Tester.cpp b/FppTest/dp/test/ut/Tester.cpp new file mode 100644 index 0000000000..361e69d08e --- /dev/null +++ b/FppTest/dp/test/ut/Tester.cpp @@ -0,0 +1,317 @@ +// ====================================================================== +// \title DpTest.hpp +// \author bocchino +// \brief cpp file for DpTest test harness implementation class +// ====================================================================== + +#include +#include + +#include "FppTest/dp/test/ut/Tester.hpp" +#include "STest/Pick/Pick.hpp" + +namespace FppTest { + +// ---------------------------------------------------------------------- +// Construction and destruction +// ---------------------------------------------------------------------- + +Tester::Tester() + : DpTestGTestBase("Tester", Tester::MAX_HISTORY_SIZE), + container1Data{}, + container1Buffer(this->container1Data, sizeof this->container1Data), + container2Data{}, + container2Buffer(this->container2Data, sizeof this->container2Data), + container3Data{}, + container3Buffer(this->container3Data, sizeof this->container3Data), + container4Data{}, + container4Buffer(this->container4Data, sizeof this->container4Data), + container5Data{}, + container5Buffer(this->container5Data, sizeof this->container5Data), + component("DpTest", + STest::Pick::any(), + STest::Pick::any(), + this->u8ArrayRecordData, + this->u32ArrayRecordData, + this->dataArrayRecordData) { + this->initComponents(); + this->connectPorts(); + this->component.setIdBase(ID_BASE); + // Fill in arrays with random data + for (U8& elt : this->u8ArrayRecordData) { + elt = static_cast(STest::Pick::any()); + } + for (U32& elt : this->u32ArrayRecordData) { + elt = static_cast(STest::Pick::any()); + } + for (DpTest_Data& elt : this->dataArrayRecordData) { + elt.set(static_cast(STest::Pick::any())); + } +} + +Tester::~Tester() {} + +// ---------------------------------------------------------------------- +// Tests +// ---------------------------------------------------------------------- + +void Tester::schedIn_OK() { + this->invoke_to_schedIn(0, 0); + this->component.doDispatch(); + ASSERT_PRODUCT_REQUEST_SIZE(3); + ASSERT_PRODUCT_REQUEST(0, ID_BASE + DpTest::ContainerId::Container1, FwSizeType(DpTest::CONTAINER_1_PACKET_SIZE)); + ASSERT_PRODUCT_REQUEST(1, ID_BASE + DpTest::ContainerId::Container2, FwSizeType(DpTest::CONTAINER_2_PACKET_SIZE)); + ASSERT_PRODUCT_REQUEST(2, ID_BASE + DpTest::ContainerId::Container3, FwSizeType(DpTest::CONTAINER_3_PACKET_SIZE)); + ASSERT_PRODUCT_GET_SIZE(3); + ASSERT_PRODUCT_GET(0, ID_BASE + DpTest::ContainerId::Container1, FwSizeType(DpTest::CONTAINER_1_PACKET_SIZE)); + ASSERT_PRODUCT_GET(1, ID_BASE + DpTest::ContainerId::Container2, FwSizeType(DpTest::CONTAINER_2_PACKET_SIZE)); + ASSERT_PRODUCT_GET(2, ID_BASE + DpTest::ContainerId::Container3, FwSizeType(DpTest::CONTAINER_3_PACKET_SIZE)); +} + +void Tester::productRecvIn_Container1_SUCCESS() { + Fw::Buffer buffer; + FwSizeType expectedNumElts; + // Invoke the port and check the header + this->productRecvIn_InvokeAndCheckHeader(DpTest::ContainerId::Container1, sizeof(U32), + DpTest::ContainerPriority::Container1, this->container1Buffer, buffer, + expectedNumElts); + // Check the data + Fw::SerializeBufferBase& serialRepr = buffer.getSerializeRepr(); + Fw::TestUtil::DpContainerHeader::checkDeserialAtOffset(serialRepr, Fw::DpContainer::DATA_OFFSET); + for (FwSizeType i = 0; i < expectedNumElts; ++i) { + FwDpIdType id; + U32 elt; + auto status = serialRepr.deserialize(id); + ASSERT_EQ(status, Fw::FW_SERIALIZE_OK); + const FwDpIdType expectedId = this->component.getIdBase() + DpTest::RecordId::U32Record; + ASSERT_EQ(id, expectedId); + status = serialRepr.deserialize(elt); + ASSERT_EQ(status, Fw::FW_SERIALIZE_OK); + ASSERT_EQ(elt, this->component.u32RecordData); + } +} + +void Tester::productRecvIn_Container1_FAILURE() { + productRecvIn_CheckFailure(DpTest::ContainerId::Container1, this->container1Buffer); +} + +void Tester::productRecvIn_Container2_SUCCESS() { + Fw::Buffer buffer; + FwSizeType expectedNumElts; + // Invoke the port and check the header + this->productRecvIn_InvokeAndCheckHeader(DpTest::ContainerId::Container2, DpTest_Data::SERIALIZED_SIZE, + DpTest::ContainerPriority::Container2, this->container2Buffer, buffer, + expectedNumElts); + // Check the data + Fw::SerializeBufferBase& serialRepr = buffer.getSerializeRepr(); + Fw::TestUtil::DpContainerHeader::checkDeserialAtOffset(serialRepr, Fw::DpContainer::DATA_OFFSET); + for (FwSizeType i = 0; i < expectedNumElts; ++i) { + FwDpIdType id; + DpTest_Data elt; + auto status = serialRepr.deserialize(id); + ASSERT_EQ(status, Fw::FW_SERIALIZE_OK); + const FwDpIdType expectedId = this->component.getIdBase() + DpTest::RecordId::DataRecord; + ASSERT_EQ(id, expectedId); + status = serialRepr.deserialize(elt); + ASSERT_EQ(status, Fw::FW_SERIALIZE_OK); + ASSERT_EQ(elt.getu16Field(), this->component.dataRecordData); + } +} + +void Tester::productRecvIn_Container2_FAILURE() { + productRecvIn_CheckFailure(DpTest::ContainerId::Container2, this->container2Buffer); +} + +void Tester::productRecvIn_Container3_SUCCESS() { + Fw::Buffer buffer; + FwSizeType expectedNumElts; + const FwSizeType dataEltSize = sizeof(FwSizeType) + this->u8ArrayRecordData.size(); + // Invoke the port and check the header + this->productRecvIn_InvokeAndCheckHeader(DpTest::ContainerId::Container3, dataEltSize, + DpTest::ContainerPriority::Container3, this->container3Buffer, buffer, + expectedNumElts); + + // Check the data + Fw::SerializeBufferBase& serialRepr = buffer.getSerializeRepr(); + Fw::TestUtil::DpContainerHeader::checkDeserialAtOffset(serialRepr, Fw::DpContainer::DATA_OFFSET); + for (FwSizeType i = 0; i < expectedNumElts; ++i) { + FwDpIdType id; + auto status = serialRepr.deserialize(id); + ASSERT_EQ(status, Fw::FW_SERIALIZE_OK); + const FwDpIdType expectedId = this->component.getIdBase() + DpTest::RecordId::U8ArrayRecord; + ASSERT_EQ(id, expectedId); + FwSizeType size; + status = serialRepr.deserialize(size); + ASSERT_EQ(status, Fw::FW_SERIALIZE_OK); + ASSERT_EQ(size, this->u8ArrayRecordData.size()); + const U8* const buffAddr = serialRepr.getBuffAddr(); + for (FwSizeType j = 0; j < size; ++j) { + U8 byte; + status = serialRepr.deserialize(byte); + ASSERT_EQ(status, Fw::FW_SERIALIZE_OK); + ASSERT_EQ(byte, this->u8ArrayRecordData.at(j)); + } + } +} + +void Tester::productRecvIn_Container3_FAILURE() { + productRecvIn_CheckFailure(DpTest::ContainerId::Container3, this->container3Buffer); +} + +void Tester::productRecvIn_Container4_SUCCESS() { + Fw::Buffer buffer; + FwSizeType expectedNumElts; + const FwSizeType dataEltSize = sizeof(FwSizeType) + this->u32ArrayRecordData.size() * sizeof(U32); + // Invoke the port and check the header + this->productRecvIn_InvokeAndCheckHeader(DpTest::ContainerId::Container4, dataEltSize, + DpTest::ContainerPriority::Container4, this->container4Buffer, buffer, + expectedNumElts); + + // Check the data + Fw::SerializeBufferBase& serialRepr = buffer.getSerializeRepr(); + Fw::TestUtil::DpContainerHeader::checkDeserialAtOffset(serialRepr, Fw::DpContainer::DATA_OFFSET); + for (FwSizeType i = 0; i < expectedNumElts; ++i) { + FwDpIdType id; + auto status = serialRepr.deserialize(id); + ASSERT_EQ(status, Fw::FW_SERIALIZE_OK); + const FwDpIdType expectedId = this->component.getIdBase() + DpTest::RecordId::U32ArrayRecord; + ASSERT_EQ(id, expectedId); + FwSizeType size; + status = serialRepr.deserialize(size); + ASSERT_EQ(status, Fw::FW_SERIALIZE_OK); + ASSERT_EQ(size, this->u32ArrayRecordData.size()); + const U8* const buffAddr = serialRepr.getBuffAddr(); + for (FwSizeType j = 0; j < size; ++j) { + U32 elt; + status = serialRepr.deserialize(elt); + ASSERT_EQ(status, Fw::FW_SERIALIZE_OK); + ASSERT_EQ(elt, this->u32ArrayRecordData.at(j)); + } + } +} + +void Tester::productRecvIn_Container4_FAILURE() { + productRecvIn_CheckFailure(DpTest::ContainerId::Container4, this->container4Buffer); +} + +void Tester::productRecvIn_Container5_SUCCESS() { + Fw::Buffer buffer; + FwSizeType expectedNumElts; + const FwSizeType dataEltSize = sizeof(FwSizeType) + this->dataArrayRecordData.size() * DpTest_Data::SERIALIZED_SIZE; + // Invoke the port and check the header + this->productRecvIn_InvokeAndCheckHeader(DpTest::ContainerId::Container5, dataEltSize, + DpTest::ContainerPriority::Container5, this->container5Buffer, buffer, + expectedNumElts); + + // Check the data + Fw::SerializeBufferBase& serialRepr = buffer.getSerializeRepr(); + Fw::TestUtil::DpContainerHeader::checkDeserialAtOffset(serialRepr, Fw::DpContainer::DATA_OFFSET); + for (FwSizeType i = 0; i < expectedNumElts; ++i) { + FwDpIdType id; + auto status = serialRepr.deserialize(id); + ASSERT_EQ(status, Fw::FW_SERIALIZE_OK); + const FwDpIdType expectedId = this->component.getIdBase() + DpTest::RecordId::DataArrayRecord; + ASSERT_EQ(id, expectedId); + FwSizeType size; + status = serialRepr.deserialize(size); + ASSERT_EQ(status, Fw::FW_SERIALIZE_OK); + ASSERT_EQ(size, this->dataArrayRecordData.size()); + const U8* const buffAddr = serialRepr.getBuffAddr(); + for (FwSizeType j = 0; j < size; ++j) { + DpTest_Data elt; + status = serialRepr.deserialize(elt); + ASSERT_EQ(status, Fw::FW_SERIALIZE_OK); + ASSERT_EQ(elt, this->dataArrayRecordData.at(j)); + } + } +} + +void Tester::productRecvIn_Container5_FAILURE() { + productRecvIn_CheckFailure(DpTest::ContainerId::Container5, this->container5Buffer); +} + +// ---------------------------------------------------------------------- +// Helper methods +// ---------------------------------------------------------------------- + +Fw::Time Tester::randomizeTestTime() { + const U32 seconds = STest::Pick::any(); + const U32 useconds = STest::Pick::startLength(0, 1000000); + const Fw::Time time(seconds, useconds); + this->setTestTime(time); + this->component.setSendTime(time); + return time; +} + +void Tester::productRecvIn_InvokeAndCheckHeader(FwDpIdType id, + FwSizeType dataEltSize, + FwDpPriorityType priority, + Fw::Buffer inputBuffer, + Fw::Buffer& outputBuffer, + FwSizeType& expectedNumElts) { + const auto globalId = ID_BASE + id; + // Set the test time + const Fw::Time timeTag = this->randomizeTestTime(); + // Invoke the productRecvIn port + this->sendProductResponse(globalId, inputBuffer, Fw::Success::SUCCESS); + this->component.doDispatch(); + // Check the port history size + ASSERT_PRODUCT_SEND_SIZE(1); + // Compute the expected data size + const auto& entry = this->productSendHistory->at(0); + const auto bufferSize = entry.buffer.getSize(); + FW_ASSERT(bufferSize >= Fw::DpContainer::MIN_PACKET_SIZE); + const auto dataCapacity = bufferSize - Fw::DpContainer::MIN_PACKET_SIZE; + const auto eltSize = sizeof(FwDpIdType) + dataEltSize; + expectedNumElts = dataCapacity / eltSize; + const auto expectedDataSize = expectedNumElts * eltSize; + // DP state should be the default value + Fw::DpState dpState; + // Set up the expected user data + Fw::DpContainer::Header::UserData userData; + memset(&userData[0], 0, sizeof userData); + // Check the history entry + // This sets the output buffer and sets the deserialization pointer + // to the start of the data payload + ASSERT_PRODUCT_SEND(0, globalId, priority, timeTag, 0, userData, dpState, expectedDataSize, outputBuffer); +} + +void Tester::productRecvIn_CheckFailure(FwDpIdType id, Fw::Buffer buffer) { + // Invoke the port + const auto globalId = ID_BASE + id; + this->sendProductResponse(globalId, buffer, Fw::Success::FAILURE); + this->component.doDispatch(); + // Check the port history size + ASSERT_PRODUCT_SEND_SIZE(0); +} + +// ---------------------------------------------------------------------- +// Handlers for typed from ports +// ---------------------------------------------------------------------- + +Fw::Success::T Tester::productGet_handler(FwDpIdType id, FwSizeType size, Fw::Buffer& buffer) { + this->pushProductGetEntry(id, size); + Fw::Success status = Fw::Success::FAILURE; + FW_ASSERT(id >= ID_BASE, id, ID_BASE); + const FwDpIdType localId = id - ID_BASE; + switch (localId) { + case DpTest::ContainerId::Container1: + FW_ASSERT(size == DpTest::CONTAINER_1_PACKET_SIZE); + buffer = this->container1Buffer; + status = Fw::Success::SUCCESS; + break; + case DpTest::ContainerId::Container2: + FW_ASSERT(size == DpTest::CONTAINER_2_PACKET_SIZE); + buffer = this->container2Buffer; + status = Fw::Success::SUCCESS; + break; + case DpTest::ContainerId::Container3: + // Make this one fail for testing purposes + break; + default: + break; + } + return status; +} + +} // end namespace FppTest diff --git a/FppTest/dp/test/ut/Tester.hpp b/FppTest/dp/test/ut/Tester.hpp new file mode 100644 index 0000000000..d941707881 --- /dev/null +++ b/FppTest/dp/test/ut/Tester.hpp @@ -0,0 +1,171 @@ +// ====================================================================== +// \title DpTest/test/ut/Tester.hpp +// \author bocchino +// \brief hpp file for DpTest test harness implementation class +// ====================================================================== + +#ifndef FppTest_DpTest_Tester_HPP +#define FppTest_DpTest_Tester_HPP + +#include "DpTestGTestBase.hpp" +#include "FppTest/dp/DpTest.hpp" +#include "Fw/Dp/test/util/DpContainerHeader.hpp" +#include "STest/Pick/Pick.hpp" + +namespace FppTest { + +class Tester : public DpTestGTestBase { + // ---------------------------------------------------------------------- + // Construction and destruction + // ---------------------------------------------------------------------- + + public: + // Maximum size of histories storing events, telemetry, and port outputs + static constexpr FwSizeType MAX_HISTORY_SIZE = 10; + // Instance ID supplied to the component instance under test + static constexpr FwSizeType TEST_INSTANCE_ID = 0; + // Queue depth supplied to component instance under test + static constexpr FwSizeType TEST_INSTANCE_QUEUE_DEPTH = 10; + // The component id base + static constexpr FwDpIdType ID_BASE = 100; + + //! Construct object Tester + //! + Tester(); + + //! Destroy object Tester + //! + ~Tester(); + + public: + // ---------------------------------------------------------------------- + // Tests + // ---------------------------------------------------------------------- + + //! schedIn OK + void schedIn_OK(); + + //! productRecvIn with Container 1 (SUCCESS) + void productRecvIn_Container1_SUCCESS(); + + //! productRecvIn with Container 1 (FAILURE) + void productRecvIn_Container1_FAILURE(); + + //! productRecvIn with Container 2 (SUCCESS) + void productRecvIn_Container2_SUCCESS(); + + //! productRecvIn with Container 2 (FAILURE) + void productRecvIn_Container2_FAILURE(); + + //! productRecvIn with Container 3 (SUCCESS) + void productRecvIn_Container3_SUCCESS(); + + //! productRecvIn with Container 3 (FAILURE) + void productRecvIn_Container3_FAILURE(); + + //! productRecvIn with Container 4 (SUCCESS) + void productRecvIn_Container4_SUCCESS(); + + //! productRecvIn with Container 4 (FAILURE) + void productRecvIn_Container4_FAILURE(); + + //! productRecvIn with Container 5 (SUCCESS) + void productRecvIn_Container5_SUCCESS(); + + //! productRecvIn with Container 5 (FAILURE) + void productRecvIn_Container5_FAILURE(); + + PRIVATE: + // ---------------------------------------------------------------------- + // Handlers for data product ports + // ---------------------------------------------------------------------- + + Fw::Success::T productGet_handler(FwDpIdType id, //!< The container ID + FwSizeType size, //!< The size of the requested buffer + Fw::Buffer& buffer //!< The buffer + ) override; + + PRIVATE: + // ---------------------------------------------------------------------- + // Helper methods + // ---------------------------------------------------------------------- + + //! Connect ports + //! + void connectPorts(); + + //! Initialize components + //! + void initComponents(); + + //! Set and return a random time + //! \return The time + Fw::Time randomizeTestTime(); + + //! Invoke productRecvIn and check header + //! This sets the output buffer to the received buffer and sets the + //! deserialization pointer to the start of the data payload + void productRecvIn_InvokeAndCheckHeader(FwDpIdType id, //!< The container id + FwSizeType dataEltSize, //!< The data element size + FwDpPriorityType priority, //!< The priority + Fw::Buffer inputBuffer, //!< The buffer to send + Fw::Buffer& outputBuffer, //!< The buffer received (output) + FwSizeType& expectedNumElts //!< The expected number of elements (output) + ); + + //! Check received buffer with failure status + void productRecvIn_CheckFailure(FwDpIdType id, //!< The container id + Fw::Buffer buffer //!< The buffer + ); + + PRIVATE: + // ---------------------------------------------------------------------- + // Variables + // ---------------------------------------------------------------------- + + //! Buffer data for Container 1 + U8 container1Data[DpTest::CONTAINER_1_PACKET_SIZE]; + + //! Buffer for Container 1 + const Fw::Buffer container1Buffer; + + //! Buffer data for Container 2 + U8 container2Data[DpTest::CONTAINER_2_PACKET_SIZE]; + + //! Buffer for Container 2 + const Fw::Buffer container2Buffer; + + //! Buffer data for Container 3 + U8 container3Data[DpTest::CONTAINER_3_PACKET_SIZE]; + + //! Buffer for Container 3 + const Fw::Buffer container3Buffer; + + //! Buffer data for Container 4 + U8 container4Data[DpTest::CONTAINER_4_PACKET_SIZE]; + + //! Buffer for Container 4 + const Fw::Buffer container4Buffer; + + //! Buffer data for Container 5 + U8 container5Data[DpTest::CONTAINER_5_PACKET_SIZE]; + + //! Buffer for Container 5 + const Fw::Buffer container5Buffer; + + //! Data for U8 array record + DpTest::U8ArrayRecordData u8ArrayRecordData; + + //! Data for U32 array record + DpTest::U32ArrayRecordData u32ArrayRecordData; + + //! Data for Data array record + DpTest::DataArrayRecordData dataArrayRecordData; + + //! The component under test + DpTest component; +}; + +} // end namespace FppTest + +#endif diff --git a/FppTest/dp/test/ut/TesterHelpers.cpp b/FppTest/dp/test/ut/TesterHelpers.cpp new file mode 100644 index 0000000000..7df0133bc6 --- /dev/null +++ b/FppTest/dp/test/ut/TesterHelpers.cpp @@ -0,0 +1,41 @@ +// ====================================================================== +// \title DpTest/test/ut/TesterHelpers.cpp +// \author Auto-generated +// \brief cpp file for DpTest component test harness base class +// +// NOTE: this file was automatically generated +// +// ====================================================================== +#include "Tester.hpp" + +namespace FppTest { +// ---------------------------------------------------------------------- +// Helper methods +// ---------------------------------------------------------------------- + +void Tester ::connectPorts() { + // productRecvIn + this->connect_to_productRecvIn(0, this->component.get_productRecvIn_InputPort(0)); + + // schedIn + this->connect_to_schedIn(0, this->component.get_schedIn_InputPort(0)); + + // productGetOut + this->component.set_productGetOut_OutputPort(0, this->get_from_productGetOut(0)); + + // productRequestOut + this->component.set_productRequestOut_OutputPort(0, this->get_from_productRequestOut(0)); + + // productSendOut + this->component.set_productSendOut_OutputPort(0, this->get_from_productSendOut(0)); + + // timeGetOut + this->component.set_timeGetOut_OutputPort(0, this->get_from_timeGetOut(0)); +} + +void Tester ::initComponents() { + this->init(); + this->component.init(Tester::TEST_INSTANCE_QUEUE_DEPTH, Tester::TEST_INSTANCE_ID); +} + +} // end namespace FppTest diff --git a/Fw/Buffer/Buffer.fpp b/Fw/Buffer/Buffer.fpp index 308edca50c..48dd549caf 100644 --- a/Fw/Buffer/Buffer.fpp +++ b/Fw/Buffer/Buffer.fpp @@ -1,12 +1,18 @@ module Fw { + @ The buffer type type Buffer + @ Port for sending a buffer port BufferSend( + @ The buffer ref fwBuffer: Fw.Buffer ) + @ Port for getting a buffer + @ Returns the buffer port BufferGet( + @ The requested size $size: U32 ) -> Fw.Buffer diff --git a/Fw/CMakeLists.txt b/Fw/CMakeLists.txt index 8d34393ace..86291b9083 100644 --- a/Fw/CMakeLists.txt +++ b/Fw/CMakeLists.txt @@ -2,24 +2,25 @@ set(FPRIME_FRAMEWORK_MODULES Fw_Prm Fw_Cmd Fw_Log Fw_Tlm Fw_Com Fw_Time Fw_Port Fw_Types Fw_Cfg CACHE INTERNAL "Fw mods") # Port subdirectories add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Buffer/") -add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Com/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Cmd/") +add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Com/") +add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Dp/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Log/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Logger/") +add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Prm/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Time/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Tlm/") -add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Prm/") # Framework subdirectories add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Cfg/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Comp/") +add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/FilePacket/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Obj/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Port/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Ports/SuccessCondition") -add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Types/") -add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/FilePacket/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/SerializableFile/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Test/") +add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Types/") # Setup an interface target for Fw for efficiency add_library(Fw INTERFACE) diff --git a/Fw/Com/ComPacket.hpp b/Fw/Com/ComPacket.hpp index 86630f626a..2179eb9da8 100644 --- a/Fw/Com/ComPacket.hpp +++ b/Fw/Com/ComPacket.hpp @@ -24,6 +24,7 @@ namespace Fw { FW_PACKET_LOG, // !< Log type - outgoing FW_PACKET_FILE, // !< File type - incoming and outgoing FW_PACKET_PACKETIZED_TLM, // !< Packetized telemetry packet type + FW_PACKET_DP, //!< Data product packet FW_PACKET_IDLE, // !< Idle packet FW_PACKET_UNKNOWN = 0xFF // !< Unknown packet } ComPacketType; diff --git a/Fw/Dp/CMakeLists.txt b/Fw/Dp/CMakeLists.txt new file mode 100644 index 0000000000..3e0752d4c6 --- /dev/null +++ b/Fw/Dp/CMakeLists.txt @@ -0,0 +1,13 @@ +set(SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/Dp.fpp" + "${CMAKE_CURRENT_LIST_DIR}/DpContainer.cpp" +) +set(MOD_DEPS Utils/Hash) +register_fprime_module() + +set(UT_SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/Dp.fpp" + "${CMAKE_CURRENT_LIST_DIR}/test/ut/TestMain.cpp" +) +set(UT_MOD_DEPS STest) +register_fprime_ut() diff --git a/Fw/Dp/Dp.fpp b/Fw/Dp/Dp.fpp new file mode 100644 index 0000000000..8f564c34bf --- /dev/null +++ b/Fw/Dp/Dp.fpp @@ -0,0 +1,62 @@ +module Fw { + + # ---------------------------------------------------------------------- + # Types + # ---------------------------------------------------------------------- + + enum DpState: U8 { + @ The untransmitted state + UNTRANSMITTED + @ The transmitted state + TRANSMITTED + } default UNTRANSMITTED + + # ---------------------------------------------------------------------- + # Ports + # ---------------------------------------------------------------------- + + @ Port for synchronously getting a data product buffer + @ Returns the status + @ + @ On return, buffer should be set to a valid buffer large enough + @ to hold a data product packet with the requested data size (if + @ status is SUCCESS) or an invalid buffer (if status is FAILURE). + port DpGet( + @ The container ID (input) + $id: FwDpIdType + @ The data size of the requested buffer (input) + dataSize: FwSizeType + @ The buffer (output) + ref buffer: Fw.Buffer + ) -> Fw.Success + + @ Port for sending a request for a data product buffer to + @ back a data product container. The request is for a buffer + @ large enough to hold a data product packet with the requested + @ data size. + port DpRequest( + @ The container ID + $id: FwDpIdType + @ The data size of the requested buffer + dataSize: FwSizeType + ) + + @ Port for receiving a response to a buffer request + port DpResponse( + @ The container ID + $id: FwDpIdType + @ The buffer + buffer: Fw.Buffer + @ The status + status: Fw.Success + ) + + @ Port for sending a data product buffer + port DpSend( + @ The container ID + $id: FwDpIdType + @ The buffer + buffer: Fw.Buffer + ) + +} diff --git a/Fw/Dp/DpContainer.cpp b/Fw/Dp/DpContainer.cpp new file mode 100644 index 0000000000..2b4baa53f1 --- /dev/null +++ b/Fw/Dp/DpContainer.cpp @@ -0,0 +1,124 @@ +// ====================================================================== +// \title DpContainer.cpp +// \author bocchino +// \brief cpp file for DpContainer +// ====================================================================== + +#include + +#include "Fw/Com/ComPacket.hpp" +#include "Fw/Dp/DpContainer.hpp" +#include "Fw/Types/Assert.hpp" + +namespace Fw { + +// ---------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------- + +DpContainer::DpContainer(FwDpIdType id, const Fw::Buffer& buffer) + : id(id), priority(0), procTypes(0), dpState(), dataSize(0), buffer(), dataBuffer() { + // Initialize the user data field + this->initUserDataField(); + // Set the packet buffer + // This action also updates the data buffer + this->setBuffer(buffer); +} + +DpContainer::DpContainer() : id(0), priority(0), procTypes(0), dataSize(0), buffer(), dataBuffer() { + // Initialize the user data field + this->initUserDataField(); +} + +// ---------------------------------------------------------------------- +// Public member functions +// ---------------------------------------------------------------------- + +Fw::SerializeStatus DpContainer::moveSerToOffset(FwSizeType offset //!< The offset +) { + Fw::SerializeBufferBase& serializeRepr = this->buffer.getSerializeRepr(); + return serializeRepr.moveSerToOffset(offset); +} + +void DpContainer::serializeHeader() { + Fw::SerializeBufferBase& serializeRepr = this->buffer.getSerializeRepr(); + // Reset serialization + serializeRepr.resetSer(); + // Serialize the packet type + Fw::SerializeStatus status = + serializeRepr.serialize(static_cast(Fw::ComPacket::FW_PACKET_DP)); + FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status); + // Serialize the container id + status = serializeRepr.serialize(this->id); + FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status); + // Serialize the priority + status = serializeRepr.serialize(this->priority); + FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status); + // Serialize the time tag + status = serializeRepr.serialize(this->timeTag); + FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status); + // Serialize the processing types + status = serializeRepr.serialize(this->procTypes); + FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status); + // Serialize the user data + const bool omitLength = true; + status = serializeRepr.serialize(this->userData, sizeof userData, omitLength); + FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status); + // Serialize the data product state + status = serializeRepr.serialize(this->dpState); + FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status); + // Serialize the data size + status = serializeRepr.serialize(this->dataSize); + FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status); + // Update the header hash + this->updateHeaderHash(); +} + +void DpContainer::setBuffer(const Buffer& buffer) { + // Set the buffer + this->buffer = buffer; + // Check that the buffer is large enough to hold a data product packet + FW_ASSERT(buffer.getSize() >= MIN_PACKET_SIZE, buffer.getSize(), MIN_PACKET_SIZE); + // Initialize the data buffer + U8* const buffAddr = buffer.getData(); + const FwSizeType dataCapacity = buffer.getSize() - MIN_PACKET_SIZE; + // Check that data buffer is in bounds for packet buffer + FW_ASSERT(DATA_OFFSET + dataCapacity <= buffer.getSize()); + U8* const dataAddr = &buffAddr[DATA_OFFSET]; + this->dataBuffer.setExtBuffer(dataAddr, dataCapacity); +} + +void DpContainer::updateHeaderHash() { + Utils::HashBuffer hashBuffer; + U8* const buffAddr = this->buffer.getData(); + Utils::Hash::hash(buffAddr, Header::SIZE, hashBuffer); + ExternalSerializeBuffer serialBuffer(&buffAddr[HEADER_HASH_OFFSET], HASH_DIGEST_LENGTH); + const Fw::SerializeStatus status = hashBuffer.copyRaw(serialBuffer, HASH_DIGEST_LENGTH); + FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status); +} + +void DpContainer::updateDataHash() { + Utils::HashBuffer hashBuffer; + U8* const buffAddrBase = this->buffer.getData(); + const U8* const dataAddr = &buffAddrBase[DATA_OFFSET]; + const FwSizeType dataSize = this->getDataSize(); + const FwSizeType bufferSize = buffer.getSize(); + FW_ASSERT(DATA_OFFSET + dataSize <= bufferSize, DATA_OFFSET + dataSize, bufferSize); + Utils::Hash::hash(dataAddr, dataSize, hashBuffer); + const FwSizeType dataHashOffset = this->getDataHashOffset(); + U8* const dataHashAddr = &buffAddrBase[dataHashOffset]; + FW_ASSERT(dataHashOffset + HASH_DIGEST_LENGTH <= bufferSize, dataHashOffset + HASH_DIGEST_LENGTH, bufferSize); + ExternalSerializeBuffer serialBuffer(dataHashAddr, HASH_DIGEST_LENGTH); + const Fw::SerializeStatus status = hashBuffer.copyRaw(serialBuffer, HASH_DIGEST_LENGTH); + FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status); +} + +// ---------------------------------------------------------------------- +// Private member functions +// ---------------------------------------------------------------------- + +void DpContainer::initUserDataField() { + (void)::memset(this->userData, 0, sizeof this->userData); +} + +} // namespace Fw diff --git a/Fw/Dp/DpContainer.hpp b/Fw/Dp/DpContainer.hpp new file mode 100644 index 0000000000..7a5a8f4b81 --- /dev/null +++ b/Fw/Dp/DpContainer.hpp @@ -0,0 +1,224 @@ +// ====================================================================== +// \title DpContainer.hpp +// \author bocchino +// \brief hpp file for DpContainer +// ====================================================================== + +#ifndef Fw_DpContainer_HPP +#define Fw_DpContainer_HPP + +#include "Fw/Buffer/Buffer.hpp" +#include "Fw/Dp/DpStateEnumAc.hpp" +#include "Fw/Time/Time.hpp" +#include "Utils/Hash/Hash.hpp" +#include "config/FppConstantsAc.hpp" +#include "config/ProcTypeEnumAc.hpp" + +namespace Fw { + +//! A data product Container +class DpContainer { + public: + // ---------------------------------------------------------------------- + // Constants and Types + // ---------------------------------------------------------------------- + + //! A DpContainer packet header + struct Header { + //! The type of user data + using UserData = U8[DpCfg::CONTAINER_USER_DATA_SIZE]; + //! The offset for the packet descriptor field + static constexpr FwSizeType PACKET_DESCRIPTOR_OFFSET = 0; + //! The offset for the id field + static constexpr FwSizeType ID_OFFSET = PACKET_DESCRIPTOR_OFFSET + sizeof(FwPacketDescriptorType); + //! The offset for the priority field + static constexpr FwDpPriorityType PRIORITY_OFFSET = ID_OFFSET + sizeof(FwDpIdType); + //! The offset for the time tag field + static constexpr FwSizeType TIME_TAG_OFFSET = PRIORITY_OFFSET + sizeof(FwDpPriorityType); + //! The offset for the processing types field + static constexpr FwSizeType PROC_TYPES_OFFSET = TIME_TAG_OFFSET + Time::SERIALIZED_SIZE; + //! The offset for the user data field + static constexpr FwSizeType USER_DATA_OFFSET = PROC_TYPES_OFFSET + sizeof(DpCfg::ProcType::SerialType); + //! The offset of the data product state field + static constexpr FwSizeType DP_STATE_OFFSET = USER_DATA_OFFSET + DpCfg::CONTAINER_USER_DATA_SIZE; + //! The offset for the data size field + static constexpr FwSizeType DATA_SIZE_OFFSET = DP_STATE_OFFSET + DpState::SERIALIZED_SIZE; + //! The header size + static constexpr FwSizeType SIZE = DATA_SIZE_OFFSET + sizeof(FwSizeType); + }; + + //! The header hash offset + static constexpr FwSizeType HEADER_HASH_OFFSET = Header::SIZE; + //! The data offset + static constexpr FwSizeType DATA_OFFSET = HEADER_HASH_OFFSET + HASH_DIGEST_LENGTH; + //! The minimum packet size + //! Reserve space for the header, the header hash, and the data hash + //! This is also the number of non-data bytes in the packet + static constexpr FwSizeType MIN_PACKET_SIZE = Header::SIZE + 2 * HASH_DIGEST_LENGTH; + + public: + // ---------------------------------------------------------------------- + // Constructor + // ---------------------------------------------------------------------- + + //! Constructor for initialized container + DpContainer(FwDpIdType id, //!< The container id + const Fw::Buffer& buffer //!< The buffer + ); + + //! Constructor for container with default initialization + DpContainer(); + + public: + // ---------------------------------------------------------------------- + // Public member functions + // ---------------------------------------------------------------------- + + //! Get the container id + //! \return The id + FwDpIdType getId() const { return this->id; } + + //! Get the data size + //! \return The data size + FwSizeType getDataSize() const { return this->dataSize; } + + //! Get the packet buffer + //! \return The buffer + Fw::Buffer getBuffer() const { return this->buffer; } + + //! Get the packet size corresponding to the data size + FwSizeType getPacketSize() const { return getPacketSizeForDataSize(this->dataSize); } + + //! Get the priority + //! \return The priority + FwDpPriorityType getPriority() const { return this->priority; } + + //! Get the time tag + //! \return The time tag + Fw::Time getTimeTag() const { return this->timeTag; } + + //! Get the processing types + //! \return The processing types + DpCfg::ProcType::SerialType getProcTypes() const { return this->procTypes; } + + //! Move the packet serialization to the specified offset + //! \return The serialize status + Fw::SerializeStatus moveSerToOffset(FwSizeType offset //!< The offset + ); + + //! Serialize the header into the packet buffer and update the header hash + void serializeHeader(); + + //! Set the id + void setId(FwDpIdType id //!< The id + ) { + this->id = id; + } + + //! Set the priority + void setPriority(FwDpPriorityType priority //!< The priority + ) { + this->priority = priority; + } + + //! Set the time tag + void setTimeTag(Fw::Time timeTag //!< The time tag + ) { + this->timeTag = timeTag; + } + + //! Set the processing types bit mask + void setProcTypes(DpCfg::ProcType::SerialType procTypes //!< The processing types + ) { + this->procTypes = procTypes; + } + + //! Set the data product state + void setDpState(DpState dpState //!< The data product state + ) { + this->dpState = dpState; + } + + //! Set the data size + void setDataSize(FwSizeType dataSize //!< The data size + ) { + this->dataSize = dataSize; + } + + //! Set the packet buffer + void setBuffer(const Buffer& buffer //!< The packet buffer + ); + + //! Update the header hash + void updateHeaderHash(); + + //! Get the data hash offset + FwSizeType getDataHashOffset() const { + // Data hash goes after the header, the header hash, and the data + return Header::SIZE + HASH_DIGEST_LENGTH + this->dataSize; + } + + //! Update the data hash + void updateDataHash(); + + public: + // ---------------------------------------------------------------------- + // Public static functions + // ---------------------------------------------------------------------- + + //! Get the packet size for a given data size + static constexpr FwSizeType getPacketSizeForDataSize(FwSizeType dataSize //!< The data size + ) { + return Header::SIZE + dataSize + 2 * HASH_DIGEST_LENGTH; + } + + PRIVATE: + // ---------------------------------------------------------------------- + // Private member functions + // ---------------------------------------------------------------------- + + //! Initialize the user data field + void initUserDataField(); + + public: + // ---------------------------------------------------------------------- + // Public member variables + // ---------------------------------------------------------------------- + + //! The user data + Header::UserData userData; + + PROTECTED: + // ---------------------------------------------------------------------- + // Protected member variables + // ---------------------------------------------------------------------- + + //! The container id + //! This is a system-global id (component-local id + component base id) + FwDpIdType id; + + //! The priority + FwDpPriorityType priority; + + //! The time tag + Time timeTag; + + //! The processing types + DpCfg::ProcType::SerialType procTypes; + + //! The data product state + DpState dpState; + + //! The data size + FwSizeType dataSize; + + //! The packet buffer + Buffer buffer; + + //! The data buffer + Fw::ExternalSerializeBuffer dataBuffer; +}; + +} // end namespace Fw + +#endif diff --git a/Fw/Dp/docs/sdd.md b/Fw/Dp/docs/sdd.md new file mode 100644 index 0000000000..a7faf871d8 --- /dev/null +++ b/Fw/Dp/docs/sdd.md @@ -0,0 +1,127 @@ +\page FwDp Framework Support for Data Products +# Framework Support for Data Products + +## 1. Introduction + +This build module defines FPP ports and C++ classes that support +the collection and storage of data products. +For more information on data products and records, see the +[data products documentation](../../../docs/Design/data-products.md). + +## 2. Configuration + +The following types and constants are configurable via the file +[`config/DpCfg.hpp`](../../../config/DpCfg.hpp): + +| Name | Kind | Description | +| ---- | ---- | ---- | +| `Fw::DpCfg::ProcType` | Type | The enumeration type that defines the bit mask for selecting a type of processing. The processing is applied to a container before writing it to disk. | +| `Fw::DpCfg::CONTAINER_DATA_SIZE` | Constant | The size of the user-configurable data in the container packet header. | + +## 3. FPP Types + +This build module defines the following FPP types: + +1. `DpState`: An enumeration describing the state of a data product. + +## 4. FPP Ports + +This build module defines the following FPP ports: + +1. `DpGet`: A port for synchronously getting a buffer to back + a data product container. + +1. `DpRequest`: A port for sending a request for a buffer to back + a data product container. + +1. `DpResponse`: A port for receiving a response to a buffer request. + +1. `DpSend`: A port for sending a buffer holding data products. + +For more information, see the file [`Dp.fpp`](../Dp.fpp) in the parent +directory. + +## 5. C++ Classes + +This module defines a C++ class `DpContainer`. +`DpContainer` is the base class for a data product container. +When you specify a container _C_ in an FPP component model, +the auto-generated C++ for the component defines a container +class for _C_. +The container class is derived from `DpContainer`. +It provides all the generic operations defined in `DpContainer` +plus the operations that are specific to _C_, for example +serializing the specific types of data that _C_ can store. + + +### 5.1. Serialized Container Format + +In serialized form, each data product container consists of the following +elements: a header, a header hash, data, and a data hash. + +#### 5.1.1. Header + +The data product header has the following format. + +|Field Name|Data Type|Serialized Size|Description| +|----------|---------|---------------|-----------| +|`PacketDescriptor`|`FwPacketDescriptorType`|`sizeof(FwPacketDescriptorType)`|The F Prime packet descriptor [`FW_PACKET_DP`](../../../Fw/Com/ComPacket.hpp)| +|`Id`|`FwDpIdType`|`sizeof(FwDpIdType)`|The container ID. This is a system-global ID (component-local ID + component base ID)| +|`Priority`|`FwDpPriorityType`|`sizeof(FwDpPriorityType)`|The container default priority| +|`TimeTag`|`Fw::Time`|`Fw::Time::SERIALIZED_SIZE`|The time tag associated with the container| +|`ProcTypes`|`Fw::DpCfg::ProcType::SerialType`|`sizeof(Fw::DpCfg::ProcType::SerialType)`|The processing types, represented as a bit mask| +|`UserData`|`Header::UserData`|`DpCfg::CONTAINER_USER_DATA_SIZE`|User-configurable data| +|`DpState`|`DpState`|`DpState::SERIALIZED_SIZE`|The data product state +|`DataSize`|`FwSizeType`|`sizeof(FwSizeType)`|The size of the data payload in bytes| + +`Header::UserData` is an array of `U8` of size `Fw::DpCfg::CONTAINER_USER_DATA_SIZE`. + +#### 5.1.2. Header Hash + +The header hash has the following format. + +|Field Name|Serialized Size|Description| +|----------|---------------|-----------| +|`Header Hash`|[`HASH_DIGEST_LENGTH`](../../../Utils/Hash/README.md)|The hash value guarding the header.| + +#### 5.1.3. Data + +The data is a sequence of records. +The serialized format of each record _R_ depends on whether _R_ is a +single-value record or an array record. + +**Single-value records:** +A single-value record is specified in FPP in the form `product record` _name_ `:` _type_. +The record has name _name_ and represents one item of data of type _type_. +The type may be any FPP type, including a struct or array type. +Single-value records with _type = T_ have the following format: + +|Field Name|Data Type|Serialized Size|Description| +|----------|---------|---------------|-----------| +|`Id`|`FwDpIdType`|`sizeof(FwDpIdType)`|The record ID| +|`Data`|_T_|`sizeof(`_T_`)` if _T_ is a primitive type; otherwise _T_`::SERIALIZED_SIZE`|The serialized data| + +**Array records:** +An array record is specified in FPP in the form `product record` _name_ `:` _type_ `array`. +The record has name _name_ and represents an array of items of type _type_. +The type may be any FPP type, including a struct or array type. +Array records with _type = T_ have the following format: + +|Field Name|Data Type|Serialized Size|Description| +|----------|---------|---------------|-----------| +|`Id`|`FwDpIdType`|`sizeof(FwDpIdType)`|The record ID| +|`Size`|`FwSizeType`|`sizeof(FwSizeType)`|The number _n_ of elements in the record| +|`Data`|Array of _n_ _T_|_n_ * [`sizeof(`_T_`)` if _T_ is a primitive type; otherwise _T_`::SERIALIZED_SIZE`]|_n_ elements, each of type _T_| + +#### 5.1.4. Data Hash + +The data hash has the following format. + +|Field Name|Serialized Size|Description| +|----------|---------------|-----------| +|`Data Hash`|[`HASH_DIGEST_LENGTH`](../../../Utils/Hash/README.md)|The hash value guarding the data.| + +### 5.2. Further Information + +For more information on the `DpContainer` class, see the file [`DpContainer.hpp`](../DpContainer.hpp) in +the parent directory. diff --git a/Fw/Dp/test/ut/TestMain.cpp b/Fw/Dp/test/ut/TestMain.cpp new file mode 100644 index 0000000000..a4224d73d2 --- /dev/null +++ b/Fw/Dp/test/ut/TestMain.cpp @@ -0,0 +1,116 @@ +// ---------------------------------------------------------------------- +// TestMain.cpp +// ---------------------------------------------------------------------- + +#include +#include + +#include "gtest/gtest.h" + +#include "Fw/Dp/DpContainer.hpp" +#include "Fw/Dp/test/util/DpContainerHeader.hpp" +#include "Fw/Test/UnitTest.hpp" +#include "STest/Pick/Pick.hpp" +#include "STest/Random/Random.hpp" + +using namespace Fw; + +constexpr FwSizeType DATA_SIZE = 100; +constexpr FwSizeType PACKET_SIZE = DpContainer::getPacketSizeForDataSize(DATA_SIZE); +U8 bufferData[PACKET_SIZE]; +DpContainer::Header::UserData userData; + +void checkHeader(FwDpIdType id, Fw::Buffer& buffer, DpContainer& container) { + // Check the packet size + const FwSizeType expectedPacketSize = Fw::DpContainer::MIN_PACKET_SIZE; + ASSERT_EQ(container.getPacketSize(), expectedPacketSize); + // Set the priority + const FwDpPriorityType priority = STest::Pick::lowerUpper(0, std::numeric_limits::max()); + container.setPriority(priority); + // Set the time tag + const U32 seconds = STest::Pick::any(); + const U32 useconds = STest::Pick::startLength(0, 1000000); + Fw::Time timeTag(seconds, useconds); + container.setTimeTag(timeTag); + // Set the processing types + const FwSizeType numProcTypeStates = 1 << DpCfg::ProcType::NUM_CONSTANTS; + const DpCfg::ProcType::SerialType procTypes = STest::Pick::startLength(0, numProcTypeStates); + container.setProcTypes(procTypes); + // Set the user data + for (U8& data : userData) { + data = static_cast(STest::Pick::any()); + } + FW_ASSERT(sizeof userData == sizeof container.userData); + (void)::memcpy(container.userData, userData, sizeof container.userData); + // Set the DP state + const DpState dpState(static_cast(STest::Pick::startLength(0, DpState::NUM_CONSTANTS))); + container.setDpState(dpState); + // Set the data size + container.setDataSize(DATA_SIZE); + // Serialize the header + container.serializeHeader(); + TestUtil::DpContainerHeader header; + // Update the data hash + container.updateDataHash(); + // Deserialize the header and check the hashes + header.deserialize(__FILE__, __LINE__, buffer); + // Check the deserialized header fields + header.check(__FILE__, __LINE__, buffer, id, priority, timeTag, procTypes, userData, dpState, DATA_SIZE); +} + +void checkBuffers(DpContainer& container, FwSizeType bufferSize) { + // Check the packet buffer + ASSERT_EQ(container.buffer.getSize(), bufferSize); + // Check the data buffer + U8 *const buffPtr = container.buffer.getData(); + U8 *const dataPtr = &buffPtr[Fw::DpContainer::DATA_OFFSET]; + const FwSizeType dataCapacity = container.buffer.getSize() - Fw::DpContainer::MIN_PACKET_SIZE; + ASSERT_EQ(container.dataBuffer.getBuffAddr(), dataPtr); + ASSERT_EQ(container.dataBuffer.getBuffCapacity(), dataCapacity); +} + +void fillWithData(Fw::Buffer& buffer) { + U8 *const buffAddrBase = buffer.getData(); + U8 *const dataAddr = &buffAddrBase[DpContainer::DATA_OFFSET]; + for (FwSizeType i = 0; i < DATA_SIZE; i++) { + dataAddr[i] = static_cast(STest::Pick::any()); + } +} + +TEST(Header, BufferInConstructor) { + COMMENT("Test header serialization with buffer in constructor"); + // Create a buffer + Fw::Buffer buffer(bufferData, sizeof bufferData); + // Fill with data + fillWithData(buffer); + // Use the buffer to create a container + const FwDpIdType id = STest::Pick::lowerUpper(0, std::numeric_limits::max()); + DpContainer container(id, buffer); + // Check the header + checkHeader(id, buffer, container); + // Check the buffers + checkBuffers(container, sizeof bufferData); +} + +TEST(Header, BufferSet) { + COMMENT("Test header serialization with buffer set"); + // Create a buffer + Fw::Buffer buffer(bufferData, sizeof bufferData); + // Fill with data + fillWithData(buffer); + // Use the buffer to create a container + const FwDpIdType id = STest::Pick::lowerUpper(0, std::numeric_limits::max()); + DpContainer container; + container.setId(id); + container.setBuffer(buffer); + // Check the header + checkHeader(id, buffer, container); + // Check the buffers + checkBuffers(container, sizeof bufferData); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + STest::Random::seed(); + return RUN_ALL_TESTS(); +} diff --git a/Fw/Dp/test/util/DpContainerHeader.hpp b/Fw/Dp/test/util/DpContainerHeader.hpp new file mode 100644 index 0000000000..bd6ca1d728 --- /dev/null +++ b/Fw/Dp/test/util/DpContainerHeader.hpp @@ -0,0 +1,200 @@ +// ====================================================================== +// \title DpContainerHeader.hpp +// \author bocchino +// \brief hpp file for DpContainer header test utility +// ====================================================================== + +#ifndef Fw_TestUtil_DpContainerHeader_HPP +#define Fw_TestUtil_DpContainerHeader_HPP + +#include "gtest/gtest.h" + +#include "FpConfig.hpp" +#include "Fw/Com/ComPacket.hpp" +#include "Fw/Dp/DpContainer.hpp" + +#define DP_CONTAINER_HEADER_ASSERT_MSG(actual, expected) \ + << file << ":" << line << "\n" \ + << " Actual value is " << actual << "\n" \ + << " Expected value is " << expected +#define DP_CONTAINER_HEADER_ASSERT_EQ(actual, expected) \ + ASSERT_EQ(actual, expected) DP_CONTAINER_HEADER_ASSERT_MSG(actual, expected) +#define DP_CONTAINER_HEADER_ASSERT_GE(actual, expected) \ + ASSERT_GE(actual, expected) DP_CONTAINER_HEADER_ASSERT_MSG(actual, expected) + +namespace Fw { +namespace TestUtil { + +//! A container packet header for testing +struct DpContainerHeader { + DpContainerHeader() : id(0), priority(0), dpState(), dataSize(0) {} + + //! Move the buffer deserialization to the specified offset + static void moveDeserToOffset(const char* const file, //!< The call site file name + const U32 line, //!< The call site line number + Buffer& buffer, //!< The buffer + FwSizeType offset //!< The offset + ) { + Fw::SerializeBufferBase& serializeRepr = buffer.getSerializeRepr(); + // Reset deserialization + Fw::SerializeStatus status = serializeRepr.setBuffLen(buffer.getSize()); + DP_CONTAINER_HEADER_ASSERT_EQ(status, FW_SERIALIZE_OK); + status = serializeRepr.moveDeserToOffset(offset); + DP_CONTAINER_HEADER_ASSERT_EQ(status, FW_SERIALIZE_OK); + } + + //! Deserialize a header from a packet buffer + //! Check that the serialization succeeded at every step + //! Check the header hash and the data hash + void deserialize(const char* const file, //!< The call site file name + const U32 line, //!< The call site line number + Fw::Buffer& buffer //!< The packet buffer + ) { + Fw::SerializeBufferBase& serializeRepr = buffer.getSerializeRepr(); + // Deserialize the packet descriptor + FwPacketDescriptorType packetDescriptor = Fw::ComPacket::FW_PACKET_UNKNOWN; + // Deserialize the packet descriptor + DpContainerHeader::moveDeserToOffset(file, line, buffer, DpContainer::Header::PACKET_DESCRIPTOR_OFFSET); + Fw::SerializeStatus status = serializeRepr.deserialize(packetDescriptor); + DP_CONTAINER_HEADER_ASSERT_EQ(status, FW_SERIALIZE_OK); + DP_CONTAINER_HEADER_ASSERT_EQ(packetDescriptor, Fw::ComPacket::FW_PACKET_DP); + // Deserialize the container id + DpContainerHeader::moveDeserToOffset(file, line, buffer, DpContainer::Header::ID_OFFSET); + status = serializeRepr.deserialize(this->id); + DP_CONTAINER_HEADER_ASSERT_EQ(status, FW_SERIALIZE_OK); + // Deserialize the priority + DpContainerHeader::moveDeserToOffset(file, line, buffer, DpContainer::Header::PRIORITY_OFFSET); + status = serializeRepr.deserialize(this->priority); + DP_CONTAINER_HEADER_ASSERT_EQ(status, FW_SERIALIZE_OK); + // Deserialize the time tag + DpContainerHeader::moveDeserToOffset(file, line, buffer, DpContainer::Header::TIME_TAG_OFFSET); + status = serializeRepr.deserialize(this->timeTag); + DP_CONTAINER_HEADER_ASSERT_EQ(status, FW_SERIALIZE_OK); + // Deserialize the processing type + DpContainerHeader::moveDeserToOffset(file, line, buffer, DpContainer::Header::PROC_TYPES_OFFSET); + status = serializeRepr.deserialize(this->procTypes); + DP_CONTAINER_HEADER_ASSERT_EQ(status, FW_SERIALIZE_OK); + // Deserialize the user data + DpContainerHeader::moveDeserToOffset(file, line, buffer, DpContainer::Header::USER_DATA_OFFSET); + NATIVE_UINT_TYPE size = sizeof this->userData; + const bool omitLength = true; + status = serializeRepr.deserialize(this->userData, size, omitLength); + DP_CONTAINER_HEADER_ASSERT_EQ(status, FW_SERIALIZE_OK); + DP_CONTAINER_HEADER_ASSERT_EQ(size, sizeof this->userData); + // Deserialize the data product state + DpContainerHeader::moveDeserToOffset(file, line, buffer, DpContainer::Header::DP_STATE_OFFSET); + status = serializeRepr.deserialize(this->dpState); + DP_CONTAINER_HEADER_ASSERT_EQ(status, FW_SERIALIZE_OK); + // Deserialize the data size + DpContainerHeader::moveDeserToOffset(file, line, buffer, DpContainer::Header::DATA_SIZE_OFFSET); + status = serializeRepr.deserialize(this->dataSize); + DP_CONTAINER_HEADER_ASSERT_EQ(status, FW_SERIALIZE_OK); + // After deserializing time, the deserialization index should be at + // the header hash offset + checkDeserialAtOffset(serializeRepr, DpContainer::HEADER_HASH_OFFSET); + // Check the header hash + checkHeaderHash(file, line, buffer); + // Check the data hash + this->checkDataHash(file, line, buffer); + // Move the deserialization pointer to the data offset + DpContainerHeader::moveDeserToOffset(file, line, buffer, DpContainer::DATA_OFFSET); + } + + //! Check the header hash + static void checkHeaderHash(const char* const file, //!< The call site file name + const U32 line, //!< The call site line number + Fw::Buffer& buffer //!< The packet buffer + ) { + Utils::HashBuffer computedHashBuffer; + U8* const buffAddr = buffer.getData(); + Utils::Hash::hash(buffAddr, DpContainer::Header::SIZE, computedHashBuffer); + Utils::HashBuffer storedHashBuffer(&buffAddr[DpContainer::HEADER_HASH_OFFSET], HASH_DIGEST_LENGTH); + DP_CONTAINER_HEADER_ASSERT_EQ(computedHashBuffer, storedHashBuffer); + } + + //! Check the data hash + void checkDataHash(const char* const file, //!< The call site file name + const U32 line, //!< The call site line number + Fw::Buffer& buffer //!< The packet buffer + ) { + Utils::HashBuffer computedHashBuffer; + U8* const buffAddrBase = buffer.getData(); + U8* const dataAddr = &buffAddrBase[DpContainer::DATA_OFFSET]; + Utils::Hash::hash(dataAddr, this->dataSize, computedHashBuffer); + DpContainer container(this->id, buffer); + container.setDataSize(this->dataSize); + const FwSizeType dataHashOffset = container.getDataHashOffset(); + Utils::HashBuffer storedHashBuffer(&buffAddrBase[dataHashOffset], HASH_DIGEST_LENGTH); + DP_CONTAINER_HEADER_ASSERT_EQ(computedHashBuffer, storedHashBuffer); + } + + //! Check a packet header against a buffer + void check(const char* const file, //!< The call site file name + const U32 line, //!< The call site line number + const Fw::Buffer& buffer, //!< The buffer + FwDpIdType id, //!< The expected id + FwDpPriorityType priority, //!< The expected priority + const Fw::Time& timeTag, //!< The expected time tag + DpCfg::ProcType::SerialType procTypes, //!< The expected processing types + const DpContainer::Header::UserData& userData, //!< The expected user data + DpState dpState, //!< The expected dp state + FwSizeType dataSize //!< The expected data size + ) const { + // Check the buffer size + const FwSizeType bufferSize = buffer.getSize(); + const FwSizeType minBufferSize = Fw::DpContainer::MIN_PACKET_SIZE; + DP_CONTAINER_HEADER_ASSERT_GE(bufferSize, minBufferSize); + // Check the container id + DP_CONTAINER_HEADER_ASSERT_EQ(this->id, id); + // Check the priority + DP_CONTAINER_HEADER_ASSERT_EQ(this->priority, priority); + // Check the time tag + DP_CONTAINER_HEADER_ASSERT_EQ(this->timeTag, timeTag); + // Check the deserialized processing types + DP_CONTAINER_HEADER_ASSERT_EQ(this->procTypes, procTypes); + // Check the user data + for (FwSizeType i = 0; i < DpCfg::CONTAINER_USER_DATA_SIZE; ++i) { + DP_CONTAINER_HEADER_ASSERT_EQ(this->userData[i], userData[i]); + } + // Check the deserialized data product state + DP_CONTAINER_HEADER_ASSERT_EQ(this->dpState, dpState); + // Check the data size + DP_CONTAINER_HEADER_ASSERT_EQ(this->dataSize, dataSize); + } + + //! Check that the serialize repr is at the specified deserialization offset + static void checkDeserialAtOffset(SerializeBufferBase& serialRepr, //!< The serialize repr + FwSizeType offset //!< The offset + ) { + const U8* buffAddr = serialRepr.getBuffAddr(); + const U8* buffAddrLeft = serialRepr.getBuffAddrLeft(); + ASSERT_EQ(buffAddrLeft, &buffAddr[offset]); + } + + //! The container id + FwDpIdType id; + + //! The priority + FwDpPriorityType priority; + + //! The time tag + Time timeTag; + + //! The processing types + DpCfg::ProcType::SerialType procTypes; + + //! The user data + U8 userData[DpCfg::CONTAINER_USER_DATA_SIZE]; + + //! The data product state + DpState dpState; + + //! The data size + FwSizeType dataSize; +}; + +} // namespace TestUtil + +} // end namespace Fw + +#endif diff --git a/Fw/Types/Serializable.cpp b/Fw/Types/Serializable.cpp index a35bdae1fc..f7f17dd34b 100644 --- a/Fw/Types/Serializable.cpp +++ b/Fw/Types/Serializable.cpp @@ -581,7 +581,23 @@ namespace Fw { this->m_deserLoc = 0; } - SerializeStatus SerializeBufferBase::deserializeSkip(NATIVE_UINT_TYPE numBytesToSkip) + SerializeStatus SerializeBufferBase::serializeSkip(FwSizeType numBytesToSkip) + { + Fw::SerializeStatus status = FW_SERIALIZE_OK; + // compute new deser loc + const FwSizeType newSerLoc = this->m_serLoc + numBytesToSkip; + // check for room + if (newSerLoc <= this->getBuffCapacity()) { + // update deser loc + this->m_serLoc = newSerLoc; + } + else { + status = FW_SERIALIZE_NO_ROOM_LEFT; + } + return status; + } + + SerializeStatus SerializeBufferBase::deserializeSkip(FwSizeType numBytesToSkip) { // check for room if (this->getBuffLength() == this->m_deserLoc) { @@ -594,6 +610,19 @@ namespace Fw { return FW_SERIALIZE_OK; } + SerializeStatus SerializeBufferBase::moveSerToOffset(FwSizeType offset) { + // Reset serialization + this->resetSer(); + // Advance to offset + return this->serializeSkip(offset); + } + SerializeStatus SerializeBufferBase::moveDeserToOffset(FwSizeType offset) { + // Reset deserialization + this->resetDeser(); + // Advance to offset + return this->deserializeSkip(offset); + } + NATIVE_UINT_TYPE SerializeBufferBase::getBuffLength() const { return this->m_serLoc; } diff --git a/Fw/Types/Serializable.hpp b/Fw/Types/Serializable.hpp index 42048edadc..393935ec59 100644 --- a/Fw/Types/Serializable.hpp +++ b/Fw/Types/Serializable.hpp @@ -115,7 +115,11 @@ namespace Fw { void resetSer(); //!< reset to beginning of buffer to reuse for serialization void resetDeser(); //!< reset deserialization to beginning - SerializeStatus deserializeSkip(NATIVE_UINT_TYPE numBytesToSkip); //!< Skips the number of specified bytes for deserialization + SerializeStatus moveSerToOffset(FwSizeType offset); //!< Moves serialization to the specified offset + SerializeStatus moveDeserToOffset(FwSizeType offset); //!< Moves deserialization to the specified offset + + SerializeStatus serializeSkip(FwSizeType numBytesToSkip); //!< Skips the number of specified bytes for serialization + SerializeStatus deserializeSkip(FwSizeType numBytesToSkip); //!< Skips the number of specified bytes for deserialization virtual NATIVE_UINT_TYPE getBuffCapacity() const = 0; //!< returns capacity, not current size, of buffer NATIVE_UINT_TYPE getBuffLength() const; //!< returns current buffer size NATIVE_UINT_TYPE getBuffLeft() const; //!< returns how much deserialization buffer is left diff --git a/Svc/DpCatalog/docs/sdd.md b/Svc/DpCatalog/docs/sdd.md new file mode 100644 index 0000000000..4c2ae74447 --- /dev/null +++ b/Svc/DpCatalog/docs/sdd.md @@ -0,0 +1,44 @@ +\page SvcDpCatalogComponent Svc::DpCatalog Component +# Svc::DpCatalog (Active Component) + +## 1. Introduction + +TODO +## 2. Requirements + +TODO + +## 3. Design + +### 3.1. Component Diagram + +TODO + +### 3.2. Ports + +`DpCatalog` has the following ports: + +TODO + +### 3.3. State + +`DpCatalog` maintains the following state: + +TODO + +### 3.4. Runtime Setup + +TODO + +### 3.5. Port Handlers + +TODO + + +## 4. Ground Interface + +TODO + +## 5. Example Uses + +TODO diff --git a/Svc/DpManager/docs/img/DpManager.png b/Svc/DpManager/docs/img/DpManager.png new file mode 100644 index 0000000000..7a62b1cbb0 Binary files /dev/null and b/Svc/DpManager/docs/img/DpManager.png differ diff --git a/Svc/DpManager/docs/img/top/buffer-get.png b/Svc/DpManager/docs/img/top/buffer-get.png new file mode 100644 index 0000000000..2f06aaa20f Binary files /dev/null and b/Svc/DpManager/docs/img/top/buffer-get.png differ diff --git a/Svc/DpManager/docs/img/top/buffer-get.txt b/Svc/DpManager/docs/img/top/buffer-get.txt new file mode 100644 index 0000000000..cc44630fd6 --- /dev/null +++ b/Svc/DpManager/docs/img/top/buffer-get.txt @@ -0,0 +1,13 @@ +producer +productGetOut +0 +dpManager +productGetIn +0 + +dpManager +bufferGetOut +0 +bufferManager +bufferGetCallee +0 diff --git a/Svc/DpManager/docs/img/top/buffer-request.json b/Svc/DpManager/docs/img/top/buffer-request.json new file mode 100644 index 0000000000..64d034d0b0 --- /dev/null +++ b/Svc/DpManager/docs/img/top/buffer-request.json @@ -0,0 +1,110 @@ +{ + "columns" : [ + [ + { + "instanceName" : "client", + "inputPorts" : [ + { + "name" : "productRecvIn", + "portNumbers" : [ + 0 + ] + } + ], + "outputPorts" : [ + { + "name" : "productRequestOut", + "portNumbers" : [ + 0 + ] + } + ] + } + ], + [ + { + "instanceName" : "dpManager", + "inputPorts" : [ + { + "name" : "productRequestIn", + "portNumbers" : [ + 0 + ] + } + ], + "outputPorts" : [ + { + "name" : "bufferGetOut", + "portNumbers" : [ + 0 + ] + }, + { + "name" : "productResponseOut", + "portNumbers" : [ + 0 + ] + } + ] + } + ], + [ + { + "instanceName" : "bufferManager", + "inputPorts" : [ + { + "name" : "bufferGetCallee", + "portNumbers" : [ + 0 + ] + } + ], + "outputPorts" : [] + } + ] + ], + "connections" : [ + [ + [ + 0, + 0, + 0, + 0 + ], + [ + 1, + 0, + 0, + 0 + ] + ], + [ + [ + 1, + 0, + 0, + 0 + ], + [ + 2, + 0, + 0, + 0 + ] + ], + [ + [ + 1, + 0, + 1, + 0 + ], + [ + 0, + 0, + 0, + 0 + ] + ] + ] +} diff --git a/Svc/DpManager/docs/img/top/buffer-request.png b/Svc/DpManager/docs/img/top/buffer-request.png new file mode 100644 index 0000000000..2d47de76a8 Binary files /dev/null and b/Svc/DpManager/docs/img/top/buffer-request.png differ diff --git a/Svc/DpManager/docs/img/top/buffer-request.txt b/Svc/DpManager/docs/img/top/buffer-request.txt new file mode 100644 index 0000000000..1828eca231 --- /dev/null +++ b/Svc/DpManager/docs/img/top/buffer-request.txt @@ -0,0 +1,20 @@ +producer +productRequestOut +0 +dpManager +productRequestIn +0 + +dpManager +bufferGetOut +0 +bufferManager +bufferGetCallee +0 + +dpManager +productResponseOut +0 +producer +productRecvIn +0 diff --git a/Svc/DpManager/docs/img/top/product-send.json b/Svc/DpManager/docs/img/top/product-send.json new file mode 100644 index 0000000000..f4b9e415e0 --- /dev/null +++ b/Svc/DpManager/docs/img/top/product-send.json @@ -0,0 +1,118 @@ +{ + "columns" : [ + [ + { + "instanceName" : "client", + "inputPorts" : [], + "outputPorts" : [ + { + "name" : "productSendOut", + "portNumbers" : [ + 0 + ] + } + ] + } + ], + [ + { + "instanceName" : "dpManager", + "inputPorts" : [ + { + "name" : "productSendIn", + "portNumbers" : [ + 0 + ] + } + ], + "outputPorts" : [ + { + "name" : "productSendOut", + "portNumbers" : [ + 0 + ] + } + ] + } + ], + [ + { + "instanceName" : "dpWriter", + "inputPorts" : [ + { + "name" : "bufferSendIn", + "portNumbers" : [ + 0 + ] + } + ], + "outputPorts" : [ + { + "name" : "bufferSendOut", + "portNumbers" : [ + 0 + ] + } + ] + } + ], + [ + { + "instanceName" : "bufferManager", + "inputPorts" : [ + { + "name" : "bufferSendIn", + "portNumbers" : [ + 0 + ] + } + ], + "outputPorts" : [] + } + ] + ], + "connections" : [ + [ + [ + 0, + 0, + 0, + 0 + ], + [ + 1, + 0, + 0, + 0 + ] + ], + [ + [ + 1, + 0, + 0, + 0 + ], + [ + 2, + 0, + 0, + 0 + ] + ], + [ + [ + 2, + 0, + 0, + 0 + ], + [ + 3, + 0, + 0, + 0 + ] + ] + ] +} diff --git a/Svc/DpManager/docs/img/top/product-send.png b/Svc/DpManager/docs/img/top/product-send.png new file mode 100644 index 0000000000..86e14921a8 Binary files /dev/null and b/Svc/DpManager/docs/img/top/product-send.png differ diff --git a/Svc/DpManager/docs/img/top/product-send.txt b/Svc/DpManager/docs/img/top/product-send.txt new file mode 100644 index 0000000000..c4bfea78b2 --- /dev/null +++ b/Svc/DpManager/docs/img/top/product-send.txt @@ -0,0 +1,20 @@ +producer +productSendOut +0 +dpManager +productSendIn +0 + +dpManager +productSendOut +0 +dpWriter +bufferSendIn +0 + +dpWriter +bufferSendOut +0 +bufferManager +bufferSendIn +0 diff --git a/Svc/DpManager/docs/sdd.md b/Svc/DpManager/docs/sdd.md new file mode 100644 index 0000000000..81a2e2498c --- /dev/null +++ b/Svc/DpManager/docs/sdd.md @@ -0,0 +1,253 @@ +\page SvcDpManagerComponent Svc::DpManager Component +# Svc::DpManager (Active Component) + +## 1. Introduction + +`Svc::DpManager` is an active component for managing data products. +It does the following: + +1. Receive requests for buffers to hold data products. + + 1. When a client component synchronously requests a data product buffer, + request an [`Fw::Buffer`](../../../Fw/Buffer/docs/sdd.md) + from a buffer manager. + Return the buffer to the client component so the component can fill it. + + 1. When a client component asynchronously requests a data product buffer, + request an [`Fw::Buffer`](../../../Fw/Buffer/docs/sdd.md) + from a buffer manager. + Send the buffer to the client component so the component can fill it. + +1. Receive buffers filled with data products by +client components. +Upon receiving a buffer, send the buffer out on a port. +Another component such as +[`Svc::BufferAccumulator`](../../BufferAccumulator/docs/BufferAccumulator.md) +or [`Svc::DpWriter`](../../DpWriter/docs/sdd.md) +will process the buffer and then send it back to the buffer manager +for deallocation. + +## 2. Requirements + +Requirement | Description | Rationale | Verification Method +----------- | ----------- | ----------| ------------------- +SVC-DPMANAGER-001 | `Svc::DpManager` shall provide an array of ports for synchronously requesting and receiving data product buffers. | This capability supports the `product` `get` port in the auto-generated code for components that define data products. | Unit test +SVC-DPMANAGER-002 | `Svc::DpManager` shall provide arrays of ports for receiving and asynchronously responding to requests for data product buffers. | This capability supports the `product` `request` and `product` `recv` ports in the auto-generated code for components that define data products. | Unit test +SVC-DPMANAGER-003 | `Svc::DpManager` shall receive data product buffers and forward them for further processing. | This requirement provides a pass-through capability for sending data product buffers to downstream components. `Svc::DpManager` receives data product input on a port of type `Fw::DpSend`. This input consists of a container ID and an `Fw::Buffer` _B_. `Svc::DpManager` sends _B_ on a port of type `Fw::BufferSend`. This port type is used by the standard F Prime components for managing and logging data, e.g., `Svc::BufferAccumulator`, `Svc::DpWriter`. | Unit test +SVC-DPMANAGER-004 | `Svc::DpManager` shall provide telemetry that reports the number of successful allocations, the number of failed allocations, and the volume of data handled. | This requirement establishes the telemetry interface for the component. | Unit test + +## 3. Design + +### 3.1. Component Diagram + +The diagram below shows the `DpManager` component. + +
+ +
+ +### 3.2. Ports + +`DpManager` has the following ports: + +| Kind | Name | Port Type | Usage | +|------|------|-----------|-------| +| `async input` | `schedIn` | `Svc.Sched` | Schedule in port | +| `sync input` | `productGetIn` | `[DpManagerNumPorts] Fw.DpGet` | Ports for responding to a data product get from a client component | +| `async input` | `productRequestIn` | `[DpManagerNumPorts] Fw.DpRequest` | Ports for receiving data product buffer requests from a client component | +| `output` | `productResponseOut` | `[DpManagerNumPorts] Fw.DpResponse` | Ports for sending requested data product buffers to a client component | +| `output` | `bufferGetOut` | `[DpManagerNumPorts] Fw.BufferGet` | Ports for getting buffers from a Buffer Manager | +| `async input` | `productSendIn` | `[DpManagerNumPorts] Fw.DpSend` | Ports for receiving filled data product buffers from a client component | +| `output` | `productSendOut` | `[DpManagerNumPorts] Fw.BufferSend` | Ports for sending filled data product buffers to a downstream component | +| `time get` | `timeGetOut` | `Fw.Time` | Time get port | +| `telemetry` | `tlmOut` | `Fw.Tlm` | Telemetry port | +| `event` | `eventOut` | `Fw.Log` | Event port | +| `text event` | `textEventOut` | `Fw.LogText` | Text event port | + +### 3.3. State + +`DpManager` maintains the following state: + +1. `numSuccessfulAllocations (U32)`: The number of successful buffer + allocations. + +1. `numFailedAllocations (U32)`: The number of failed buffer allocations. + +1. `numDataProducts (U32)`: The number of data products handled. + +1. `numBytes (U64)`: The number of bytes handled. + +### 3.4. Compile-Time Setup + +The configuration constant [`DpManagerNumPorts`](../../../config/AcConstants.fpp) +specifies the number of ports for +requesting data product buffers and for sending filled data products. + +### 3.5. Runtime Setup + +No special runtime setup is required. + +### 3.6. Port Handlers + +#### 3.6.1. schedIn + +The handler for this port sends out the state variables as telemetry. + +#### 3.6.2. productGetIn + +This handler receives a port number `portNum`, a container ID `id`, a requested +buffer size `size`, and a mutable reference to a buffer `B`. +It does the following: + +1. Set `status = getBuffer(portNum, id, size, B)`. + +1. Return `status`. + +#### 3.6.3. productRequestIn + +This handler receives a port number `portNum`, a container ID `id` and a +requested buffer size `size`. +It does the following: + +1. Initialize a local variable `B` with an invalid buffer. + +1. Set `status = getBuffer(portNum id, size, B)`. + +1. Send `(id, B, status)` on port `portNum` of `productResponseOut`. + +#### 3.6.4. productSendIn + +This handler receives a port number `portNum`, a data product ID `I` and a +buffer `B`. +It does the following: + +1. Update `numDataProducts` and `numBytes`. + +1. Send `B` on port `portNum` of `productSendOut`. + +### 3.7. Helper Methods + + +#### 3.7.1. getBuffer + +This function receives a port number `portNum`, a container ID `id`, a +requested buffer size `size`, and a mutable reference to a buffer `B`. +It does the following: + +1. Set `status = FAILURE`. + +1. Set `B = bufferGetOut_out(portNum, size)`. + +1. If `B` is valid, then atomically increment `numSuccessfulAllocations` and + set `status = SUCCESS`. + +1. Otherwise atomically increment `numFailedAllocations` and emit a warning event. + +1. Return `status`. + + +## 4. Ground Interface + +### 4.1. Telemetry + +| Name | Type | Description | +|------|------|-------------| +| `NumSuccessfulAllocations` | `U32` | The number of successful buffer allocations | +| `NumFailedAllocations` | `U32` | The number of failed buffer allocations | +| `NumDataProds` | `U32` | Number of data products handled | +| `NumBytes` | `U32` | Number of bytes handled | + +### 4.2. Events + +| Name | Severity | Description | +|------|----------|-------------| +| `BufferAllocationFailed` | `warning high` | Buffer allocation failed | + +## 5. Example Uses + + +### 5.1. Topology Diagrams + +The following topology diagrams show how to connect `Svc::DpManager` +to a client component, a buffer manager, and a data product writer. +The diagrams use the following instances: + +* `bufferManager`: An instance of [`Svc::BufferManager`](../../BufferManager/docs/sdd.md). + +* `dpManager`: An instance of `Svc::DpManager`. + +* `dpWriter`: An instance of [`Svc::DpWriter`](../../DpWriter/docs/sdd.md). + +* `producer`: A client component that produces data products. +`productRequestOut` is the special `product request` port. +`productRecvIn` is the special `product recv` port. + +The connections shown use port zero for requesting, receiving, +and sending data product buffers. +If `DpManagerNumPorts` is greater than one, then you can also use other ports, +e.g., port one or port two. +That way you can use one `DpManager` instance to support multiple sets of +connections. + +#### 5.1.1. Synchronously Getting Data Product Buffers + +
+ +
+ +#### 5.1.2. Asynchronously Requesting Data Product Buffers + +
+ +
+ +#### 5.1.3. Sending Data Products + +
+ +
+ +### 5.2. Sequence Diagrams + +#### 5.2.1. Synchronously Getting a Data Product Buffer + +```mermaid +sequenceDiagram + activate producer + producer->>dpManager: Request buffer [productGetIn] + dpManager->>bufferManager: Request buffer B [bufferGetOut] + bufferManager-->>dpManager: Return B + dpManager->>dpManager: Store B into producer + dpManager-->>producer: Return SUCCESS + deactivate producer +``` + +#### 5.2.2. Asynchronously Requesting a Data Product Buffer + +```mermaid +sequenceDiagram + activate producer + activate dpManager + producer-)dpManager: Request buffer [productRequestIn] + dpManager->>bufferManager: Request buffer B [bufferGetOut] + bufferManager-->>dpManager: Return B + dpManager-)producer: Send B [productResponseOut] + deactivate dpManager + deactivate producer +``` + +#### 5.2.3. Sending a Data Product + +```mermaid +sequenceDiagram + activate producer + activate dpManager + activate dpWriter + producer-)dpManager: Send buffer B [productSendIn] + dpManager-)dpWriter: Send B [productSendOut] + dpWriter->>bufferManager: Deallocate B + bufferManager-->>dpWriter: Return + deactivate dpWriter + deactivate dpManager + deactivate producer +``` diff --git a/Svc/DpWriter/docs/img/DpWriter.png b/Svc/DpWriter/docs/img/DpWriter.png new file mode 100644 index 0000000000..21eb39c7e3 Binary files /dev/null and b/Svc/DpWriter/docs/img/DpWriter.png differ diff --git a/Svc/DpWriter/docs/img/top/product-write.png b/Svc/DpWriter/docs/img/top/product-write.png new file mode 100644 index 0000000000..728e42291a Binary files /dev/null and b/Svc/DpWriter/docs/img/top/product-write.png differ diff --git a/Svc/DpWriter/docs/img/top/product-write.txt b/Svc/DpWriter/docs/img/top/product-write.txt new file mode 100644 index 0000000000..04db804b15 --- /dev/null +++ b/Svc/DpWriter/docs/img/top/product-write.txt @@ -0,0 +1,27 @@ +producer +productSendOut +0 +dpManager +productSendIn +0 + +dpManager +productSendOut +0 +dpWriter +bufferSendIn +0 + +dpWriter +procBufferSendOut +0 +dpProcessor +bufferSendIn +0 + +dpWriter +deallocBufferSendOut +0 +bufferManager +bufferSendIn +0 diff --git a/Svc/DpWriter/docs/sdd.md b/Svc/DpWriter/docs/sdd.md new file mode 100644 index 0000000000..c5bf08809a --- /dev/null +++ b/Svc/DpWriter/docs/sdd.md @@ -0,0 +1,192 @@ +\page SvcDpWriterComponent Svc::DpWriter Component +# Svc::DpWriter (Active Component) + +## 1. Introduction + +`Svc::DpWriter` is an active component for writing data products to disk. +It does the following: + +1. Receive buffers containing filled data product containers. +The buffers typically come from one or more components that produce +data products. +They typically pass through an instance of +[`Svc::DpManager`](../../DpManager/docs/sdd.md), and possibly through +an instance of +[`Svc::BufferAccumulator`](../../BufferAccumulator/docs/BufferAccumulator.md), +before reaching `DpWriter`. + +1. For each buffer _B_ received in step 1: + + 1. Perform any requested processing, such as data compression, on _B_. + + 1. Write _B_ to disk. + +## 2. Requirements + +Requirement | Description | Rationale | Verification Method +----------- | ----------- | ----------| ------------------- +SVC-DPWRITER-001 | `Svc::DpWriter` shall provide a port for receiving `Fw::Buffer` objects pointing to filled data product containers. | The purpose of `DpWriter` is to write the data products to disk. | Unit Test +SVC-DPWRITER-002 | `Svc::DpWriter` shall provide an array of ports for sending `Fw::Buffer` objects for processing. | This requirement supports downstream processing of the data in the buffer. | Unit Test +SVC-DPWRITER-003 | On receiving a data product container _C_, `Svc::DpWriter` shall use the processing type field of the header of _C_ to select zero or more processing ports to invoke, in port order. | The processing type field is a bit mask. A one in bit `2^n` in the bit mask selects port index `n`. | Unit Test +SVC-DPWRITER-004 | On receiving an `Fw::Buffer` _B_, and after performing any requested processing on _B_, `Svc::DpWriter` shall write _B_ to disk. | The purpose of `DpWriter` is to write data products to the disk. | Unit Test +SVC-DPWRITER-005 | `Svc::DpManager` shall provide telemetry that reports the number of data products written and the number of bytes written. | This requirement establishes the telemetry interface for the component. | Unit test + +## 3. Design + +### 3.1. Component Diagram + +The diagram below shows the `DpWriter` component. + +
+ +
+ +### 3.2. Ports + +`DpWriter` has the following ports: + +| Kind | Name | Port Type | Usage | +|------|------|-----------|-------| +| `async input` | `schedIn` | `Svc.Sched` | Schedule in port | +| `async input` | `bufferSendIn` | `Fw.BufferSend` | Port for receiving data products to write to disk | +| `output` | `procBufferSendOut` | `[DpWriterNumProcPorts] Fw.BufferSend` | Port for processing data products | +| `output` | `deallocBufferSendOut` | `Fw.BufferSend` | Port for deallocating data product buffers | +| `time get` | `timeGetOut` | `Fw.Time` | Time get port | +| `telemetry` | `tlmOut` | `Fw.Tlm` | Telemetry port | +| `event` | `eventOut` | `Fw.Log` | Event port | +| `text event` | `textEventOut` | `Fw.LogText` | Text event port | + +### 3.3. State + +`DpWriter` maintains the following state: + +1. `numDataProducts (U32)`: The number of data products written. + +1. `numBytes (U64)`: The number of bytes written. + +### 3.4. Compile-Time Setup + +The configuration constant [`DpWriterNumProcPorts`](../../../config/AcConstants.fpp) +specifies the number of ports for connecting components that perform +processing. + +### 3.5. Runtime Setup + +The `config` function specifies the following constants: + +1. `fileNamePrefix (string)`: The prefix to use for file names. + +1. `fileNameSuffix (string)`: The suffix to use for file names. + +### 3.6. Port Handlers + +#### 3.6.1. schedIn + +This handler sends out the state variables as telemetry. + +#### 3.6.2. bufferSendIn + +This handler receives a mutable reference to a buffer `B`. +It does the following: + +1. Check that `B` is valid and that the first `sizeof(FwPacketDescriptorType)` + bytes of the memory referred to by `B` hold the serialized value + [`Fw_PACKET_DP`](../../../Fw/Com/ComPacket.hpp). + If not, emit a warning event. + +1. If step 1 succeeded, then + + 1. Read the `ProcType` field out of the container header stored in the + memory pointed to by `B`. + If the value is a valid port number `N` for `procBufferSendOut`, then invoke + `procBufferSendOut` at port number `N`, passing in `B`. + This step updates the memory pointed to by `B` in place. + + 1. Write `B` to a file, using the format described in the [**File + Format**](#file_format) section. For the time stamp, use the time + provided by `timeGetOut`. + +1. Send `B` on `deallocBufferSendOut`. + + +## 4. File Format + +### 4.1. Data Format + +Each file stores a serialized data product record, +with the format described in the +[data products documentation](../../../Fw/Dp/docs/sdd.md#serial-format). + +### 4.2. File Name + +The name of each file consists of `fileNamePrefix` followed by an +ID, a time stamp, and `fileNameSuffix`. +The ID consists of an underscore character `_` followed by the container ID. +The time stamp consists of an underscore character `_` followed by a seconds +value, an underscore character, and a microseconds value. + +For example, suppose that the file name prefix is `container_data` and the +file name suffix is `.dat`. +Suppose that container ID is 100, the seconds value is 100000, +and the microseconds value is 1000. +Then the file name is `container_data_100_100000_1000.dat`. + + +## 5. Ground Interface + +### 5.1. Telemetry + +| Name | Type | Description | +|------|------|-------------| +| `NumDataProducts` | `U32` | The number of data products handled | +| `NumBytes` | `U64` | The number of bytes handled | + +### 5.2. Events + +| Name | Severity | Description | +|------|----------|-------------| +| `BufferTooSmall` | `warning high` | Incoming buffer is too small to hold a data product container | +| `InvalidPacketDescriptor` | `warning high` | Incoming buffer had an invalid packet descriptor | + +## 6. Example Uses + + +### 6.1. Topology Diagrams + +The following topology diagram shows how to connect `Svc::DpWriter` +to a `DpManager` component and a processor component. +The diagrams use the following instances: + +* `dpManager`: An instance of [`Svc::DpManager`](../../DpManager/docs/sdd.md). + +* `dpProcessor`: A component that processes data product containers. + +* `dpWriter`: An instance of `Svc::DpWriter`. + +* `producer`: A component that produces data products. + +
+ +
+ +### 6.2. Sequence Diagrams + +The following diagram shows what happens when a buffer is sent to `DpWriter`, +is processed, and is written to disk. + +```mermaid +sequenceDiagram + activate producer + activate dpManager + activate dpWriter + producer-)dpManager: Send buffer + dpManager-)dpWriter: Send buffer [bufferSendIn] + dpWriter->>dpProcessor: Process buffer B [procBufferSendOut] + dpProcessor-->>dpWriter: Return + dpWriter->>dpWriter: Write B to disk + dpWriter->>bufferManager: Deallocate B [deallocBufferSendOut] + bufferManager-->>dpWriter: Return + deactivate dpWriter + deactivate dpManager + deactivate producer +``` diff --git a/config/AcConstants.fpp b/config/AcConstants.fpp index 5315c63dab..fb4b5e6087 100644 --- a/config/AcConstants.fpp +++ b/config/AcConstants.fpp @@ -39,6 +39,12 @@ constant ComQueueBufferPorts = 1 @ Used for maximum number of connected buffer repeater consumers constant BufferRepeaterOutputPorts = 10 +@ Size of port array for DpManager +constant DpManagerNumPorts = 5 + +@ Size of processing port array for DpWriter +constant DpWriterNumProcPorts = 5 + # ---------------------------------------------------------------------- # Hub connections. Connections on all deployments should mirror these settings. # ---------------------------------------------------------------------- diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt index 643fd4f29a..f0a1cfd819 100644 --- a/config/CMakeLists.txt +++ b/config/CMakeLists.txt @@ -4,7 +4,8 @@ # Sets a list of source files for cmake to process as part of autocoding. #### set(SOURCE_FILES - "${CMAKE_CURRENT_LIST_DIR}/FpConfig.fpp" "${CMAKE_CURRENT_LIST_DIR}/AcConstants.fpp" + "${CMAKE_CURRENT_LIST_DIR}/DpCfg.fpp" + "${CMAKE_CURRENT_LIST_DIR}/FpConfig.fpp" ) register_fprime_module(config) diff --git a/config/DpCfg.fpp b/config/DpCfg.fpp new file mode 100644 index 0000000000..0d04662c9a --- /dev/null +++ b/config/DpCfg.fpp @@ -0,0 +1,26 @@ +# ====================================================================== +# FPP file for data products configuration +# ====================================================================== + +module Fw { + + module DpCfg { + + @ The size in bytes of the user-configurable data in the container + @ packet header + constant CONTAINER_USER_DATA_SIZE = 32; + + @ A bit mask for selecting the type of processing to perform on + @ a container before writing it to disk. + enum ProcType: U8 { + @ Processing type 0 + PROC_TYPE_ZERO = 0x01 + @ Processing type 1 + PROC_TYPE_ONE = 0x02 + @ Processing type 2 + PROC_TYPE_TWO = 0x04 + } + + } + +} diff --git a/config/FpConfig.fpp b/config/FpConfig.fpp index 5341ba661b..1769f8c459 100644 --- a/config/FpConfig.fpp +++ b/config/FpConfig.fpp @@ -1,5 +1,7 @@ type FwBuffSizeType type FwChanIdType +type FwDpIdType +type FwDpPriorityType type FwEnumStoreType type FwEventIdType type FwIndexType diff --git a/config/FpConfig.h b/config/FpConfig.h index 0ffa821fb9..36c7173fcb 100644 --- a/config/FpConfig.h +++ b/config/FpConfig.h @@ -68,6 +68,12 @@ typedef U32 FwPrmIdType; typedef U16 FwTlmPacketizeIdType; #define PRI_FwTlmPacketizeIdType PRIu16 +typedef U32 FwDpIdType; +#define PRI_FwDpIdType PRIu32 + +typedef U32 FwDpPriorityType; +#define PRI_FwDpPriorityType PRIu32 + // Boolean values for serialization #ifndef FW_SERIALIZE_TRUE_VALUE #define FW_SERIALIZE_TRUE_VALUE (0xFF) //!< Value encoded during serialization for boolean true diff --git a/docs/Design/data-products.md b/docs/Design/data-products.md new file mode 100644 index 0000000000..dded333ef1 --- /dev/null +++ b/docs/Design/data-products.md @@ -0,0 +1,443 @@ +# Data Products + +## 1. Introduction + +F' provides several features for managing the generation, storage, +and downlink of data products. +In this section, we document those features. + +## 2. Basic Concepts + +First we explain some basic concepts. + +### 2.1. Records, Containers, and Dictionaries + +F' data products are based on **records** and **containers**. +A record is a basic unit of data. +For example, it may be a struct, an array of typed objects of +statically known size, or an array of bytes of statically unknown size. +A container has an identifier and a priority and stores records. +In C++, a container is represented as a class object with member fields that +(1) store header data and (2) store an `Fw::Buffer` object pointing +to the memory that stores the records. + +The set of all containers forms the **data product dictionary**. +To manage the data product dictionary, F Prime uses the same general approach +as for commands, telemetry, events, and parameters: + +1. Each component _C_ defines records and containers. +The container IDs are local to _C_. +Typically they have the values 0, 1, 2, ... . + +2. Each instance _I_ of _C_ contributes one container _I.c_ to the +dictionary for each container _c_ defined in _C_. +The global identifier for _I.c_ is the base identifier of _I_ plus +the local identifier for _c_. +For example, if the base identifier is 0x1000, then the global identifiers +might be 0x1000, 0x1001, 0x1002, ... . + +3. For any topology _T_, the global identifiers _I.c_ for all the instances _T_ +form the data product dictionary for _T_. + +### 2.2. F' Components + +Typically a data product system in an F' application consists of the following +components: + +1. One or more **data product producers**. + These components produce data products and are typically mission-specific. + For example, they may produce science data. + +1. Standard F Prime components for managing data products. + + 1. A **data product manager**. + This component allocates memory for empty containers. + It also forwards filled containers to the data product writer. + See [`Svc::DpManager`](../../Svc/DpManager/docs/sdd.md). + + 1. A **data product writer**. + This component receives filled containers from data product + producers. It writes the contents of the containers to non-volatile + storage. See [`Svc::DpWriter`](../../Svc/DpWriter/docs/sdd.md). + + 1. A **data product catalog**. + This component maintains a database of available data + products. By command, it downlinks and deletes data products. + See TODO. + + 1. A **data product processor**. + This component performs in-memory processing on data + product containers. + See TODO. + +Note that when using data products, you need to develop only the +producer components. The other components are provided by F'. + +## 3. Producer Components + +In this section we provide more detail about producer components. + +### 3.1. Activities + +A producer component typically repeats the following activities, +as often as necessary: + +1. Request a container from a data manager component. + +2. When the container is received, fill the container with +data by serializing records into the container. + +3. When the container is full, send the container to the +data product manager, which forwards it to the data +product writer. + +The FPP model and the autocoded C++ have several features that +support these activities. +We discuss these features in the following sections. + +### 3.2. FPP Modeling + +In this section we summarize the features of the FPP modeling +language used in constructing data product producer components. +Each of these features is fully documented in _The FPP User's Guide_ +and _The FPP Language Specification_. + +#### 3.2.1. Ports + +FPP provides the following special ports for managing data products: + +1. A **product get port** of type [`Fw::DpGet`](../../Fw/Dp/docs/sdd.md). + This is an output port for synchronously requesting + memory from a buffer manager. + The request is served on the thread that invokes the port + and causes a mutex lock to be taken on that thread. + Example syntax: + ``` + product get port productGetOut + ``` + +1. A **product request port** of type [`Fw::DpRequest`](../../Fw/Dp/docs/sdd.md). + This is an output port for asynchronously requesting memory + from a data product manager. + The request is served on the thread of the data product manager. + This approach incurs the overhead of a separate thread, but it + does not require the requesting thread to take a lock. + Example syntax: + ``` + product request port productRequestOut + ``` + +1. A **product receive port** of type [`Fw::DpResponse`](../../Fw/Dp/docs/sdd.md). + This is an input port for receiving an empty container in response + to an asynchronous request. Example syntax: + ``` + async product recv port productRecvIn + ``` + +1. A **product send port** of type [`Fw::DpSend`](../../Fw/Dp/docs/sdd.md). + This is an output port for sending a filled container + to a data product writer. Example syntax: + ``` + product send port productSendOut + ``` + +Each data product producer component must have the following +ports in its component model: + +1. One or both of a `product` `get` port and a `product` `request` port. + +1. A `product` `send` port. + + A component that has a `product` `request` port must also have + a `product` `receive` port. + +#### 3.2.2. Records + +A record is a unit of data. +When defining a producer component, you can specify one or more +records. +A record specification consists of a name, a type specifier, and an optional identifier. +The type specifier may be one of the following: + +1. An FPP type _T_. In this case, the record contains a single value of type + _T_. _T_ may be any FPP type, including a struct or array type. + +1. An FPP type _T_ followed by the keyword `array`. + In this case, the record is an array of values of type _T_ + of statically unknown size. + The size of the array is stored in the record. + +In either case, _T_ may be any FPP type, including a struct or array type. + +Example syntax: +``` +@ A struct with a fixed-size member array +struct FixedSizeData { + data: [1024] F32 +} +@ A record containing fixed-size data +product record FixedSizeDataRecord: FixedSizeData +@ A record containing a variable-size array +product record F32ArrayRecord: F32 array id 0x01 +``` + +#### 3.2.3. Containers + +A container is a data structure that records. +When defining a producer component, you can specify one or more containers. +Each container specified in a component can store +any of the records specified in the component. + +A container specification consists of a name, an optional +identifier, and an optional default priority. +The default priority is the priority to use if no +other priority is specified for the container +during ground operations. +Example syntax: +``` +product container C1 +product container C2 id 0x01 default priority 10 +``` + +### 3.3. Autocoded C++ + +The autocoded C++ base class for a producer component _C_ provides +the following API elements: + +1. Enumerations defining the available container IDs, container +priorities, and record IDs. + +1. A member class _C_ `::DpContainer`. This class is derived from +[`Fw::DpContainer`](../../Fw/Dp/docs/sdd.md) and represents a container +specialized to the data products defined in _C_. +Each instance of _C_ `::DpContainer` is a wrapper for an `Fw::Buffer` _B_, +which points to allocated memory. +The class provides operations for serializing the records +defined in _C_ into the memory pointed to by _B_. +There is one operation _C_ `::DpContainer::serialize_` _R_ +for each record _R_ defined in _C_. +For the serialized format of each record, see the documentation +for [`Fw::DpContainer`](../../Fw/Dp/docs/sdd.md). + +1. If _C_ has a `product` `get` port, a member function `dpGet_` +_c_ for each container defined in _C_. +This function takes a container ID, a data size, and a reference +to a data product container _D_. +It invokes `productGetOut`, which is typically connected +to a data product manager component. +In the nominal case, the invocation returns an `Fw::Buffer` _B_ large enough +to store a data product packet with the requested data size. +The `dpGet` function then uses the ID and _B_ to initialize _D_. +It returns a status value indicating whether the buffer +allocation succeeded. + +1. If _C_ has a `product` `request` port, a member function +`dpRequest_` _c_ for each container defined in _C_. +This function takes a container ID and a data size. +It sends out a request on `productRequestOut`, which is +typically connected to a data product manager component. +The request is for a buffer large enough to store a data +product packet with the requested data size. + +1. If _C_ has a `product` `recv` port, a pure virtual +member function `dpRecv_` _c_ `_handler` for each container _c_ +defined in _C_. +When a fresh container arrives in response to a +`dpRequest` invocation, the autocoded C++ uses the container ID to +select and invoke the appropriate `dpRecv` handler. +The implementation of _C_ must override each handler +to provide the mission-specific behavior for filling +in the corresponding container. +The arguments to `dpRecv_` _c_ `_handler` provide +(1) a reference to the container, which the implementation can fill in; +and (2) a status value indicating whether the container +is valid. An invalid container can result if the buffer +allocation fails. + +1. A member function `dpSend` for sending a filled +data product container. +This function takes a reference to a container _c_ and an +optional time tag. +It does the following: + + 1. If no time tag is provided, then invoke `timeGetOut` + to get the system time and use it to set the time tag. + + 1. Store the time tag into _c_. + + 1. Send _c_ on `productSendOut`. + +### 3.4. Unit Test Support + +In F Prime, each component _C_ comes with auto-generated +classes _C_ `TesterBase` and _C_ `GTestBase` for writing +unit tests against _C_. +_C_ `GTestBase` is derived from _C_ `TesterBase`; it +provides test macros based on the Google Test framework. + +To write unit tests, you construct a class _C_ `Tester`. +Typically _C_ `Tester` is derived from _C_ `GTestBase` and +uses the Google Test framework macros. +If for some reason you can't use the Google Test framework +(e.g., because you are running on a platform that does not support it), +then your _C_ `Tester` class can be derived from _C_ `TesterBase`. + +This section documents the unit test support for producer components. + +#### 3.4.1. The TesterBase Class + +**History data structures:** +The class _C_ `TesterBase` provides the following histories: + +1. If _C_ has a product get port, +then _C_ `TesterBase` has a history called `productGetHistory`. +Each element in the history is of type `DpGet`. +`DpGet` is a struct with fields storing the container ID and the +size emitted on the product get port. + +1. If _C_ has a product request port, then _C_ `TesterBase` has a +corresponding history called `productRequestHistory`. +Each element in the history is of type `DpRequest`. +`DpRequest` is a struct with fields storing the container ID and the +size emitted on the product request port. + +1. _C_ `TesterBase` has a history called `productSendHistory`. +Each element in the history is of type `DpSend`. +`DpSend` is a struct with fields storing the container ID and +a shallow copy of the buffer emitted on the product send port. + +**History functions:** +The class _C_ `TesterBase` provides the following functions +for managing the histories: + +1. If _C_ has a product get port, then _C_ `TesterBase` provides + the following functions: + + 1. `pushProductGetEntry`: This function takes a container ID and + a size. It constructs the corresponding `DpGet` history object + and pushes it on `productGetHistory`. Typically this function is + called by `productGet_handler` (see below). + + 1. `productGet_handler`: This function is called when the tester + component receives data emitted on the product get port of the + component under test. It takes a container ID, a size, and a + mutable reference to a buffer _B_. By default it calls + `pushProductGetEntry` with the ID and size and returns `FAILURE`, + indicating that no memory was allocated and _B_ was not updated. + This function is virtual, so you can override it with your own + behavior. For example, your function could call `pushProductGetEntry`, + allocate a buffer, store the allocated buffer into _B_, and return + `SUCCESS`. + +1. If _C_ has a product request port, then _C_ `TesterBase` provides + the following functions: + + 1. `pushProductRequestEntry`: This function takes a container ID and + a size. It constructs the corresponding `DpRequest` history object + and pushes it on `productRequestHistory`. Typically this function is + called by `productRequest_handler` (see below). + + 1. `productRequest_handler`: This function is called when the tester + component receives data emitted on the product request port of the + component under test. It takes a container ID and a size. By default + it calls `pushProductRequestEntry` with the ID and size. This function + is virtual, so you can override it with your own behavior. + +1. _C_ `TesterBase` provides the following functions: + + 1. `pushProductSendEntry`: This function takes a container ID and a + const reference to a buffer. It constructs the corresponding + `DpSend` history object and pushes it on `productSendHistory`. + Typically this function is called by `productSend_handler` (see below). + + 1. `productSend_handler`: This function is called when the tester + component receives data emitted on the product send port of the + component under test. It takes a container ID and a const reference + to a buffer. By default it calls `pushProductSendEntry` with the + ID and buffer. This function is virtual, so you can override it + with your own behavior. + +#### 3.4.2. The GTestBase Class + +**Testing macros:** +The class _C_ `GTestBase` provides the following macros for +verifying the histories managed by _C_ `TesterBase`. + +1. If _C_ defines data products and has a product get port, then _C_ + `GTestBase` provides the following macros: + + 1. `ASSERT_PRODUCT_GET_SIZE(size)`: This macro checks that `productGetHistory` + has the specified size (number of entries). + + 1. `ASSERT_PRODUCT_GET(index, id, size)`: This macro checks that + `productGetHistory` has the specified container ID and size + at the specified history index. + +1. If _C_ defines data products and has a product request port, + then _C_ `GTestBase` provides the following macros: + + 1. `ASSERT_PRODUCT_REQUEST_SIZE(size)`: This macro checks that + `productRequestHistory` has the specified size (number of entries). + + 1. `ASSERT_PRODUCT_REQUEST(index, id, size)`: This macro checks that + `productRequestHistory` has the specified container ID and size + at the specified history index. + +1. If _C_ defines data products, then _C_ `GTestBase` provides + the following macros: + + 1. `ASSERT_PRODUCT_SEND_SIZE(size)`: This macro checks that + `productSendHistory` has the specified size (number of entries). + + 1. `ASSERT_PRODUCT_SEND(index, id, priority, timeTag, procType, userData, dataSize, buffer)`: + All the arguments of this macro are inputs (read-only) except `buffer`, which is + a by-reference output and must be a variable of type `Fw::Buffer&`. + This macro verifies the entry `entry` stored at the specified + index of `productSendHistory`. It does the following: + + 1. Check that `entry.id` matches the specified ID. + + 1. Deserialize the data product header stored in `entry.buffer`. + + 1. Check that the container ID, priority, time tag, processor type, + user data, and data size stored in the deserialized header + match the specified values. + + 1. Assign `entry.buffer` to `buffer`. After this macro runs, + the deserialization pointer of `buffer` points into the start + of the data payload of `entry.buffer`. You can write additional + code to deserialize and check the data payload. + +**Container IDs:** +The container IDs emitted by the component under test are global +IDs. +Therefore, when constructing specified IDs you must add +the ID base specified in the tester component to the local +ID specified in the component under test. +For example, for container `CONTAINER` in component `Component`, +you would write +```cpp +ID_BASE + Component::ContainerId::CONTAINER +``` +`ID_BASE` is a standard constant defined in each Tester implementation +and provided to the Tester base classes in their constructors. + +## 4. Use Cases + +In this section we discuss several common use cases involving +data products. + +**Requesting and sending data products:** +See the example uses in the documentation for +[`Svc::DpManager`](../../Svc/DpManager/docs/sdd.md#5-example-uses). +The component referred to as `producer` in that document +is a data product producer. + +**Writing data products to non-volatile storage:** +TODO + +**Cataloging and downlinking data products:** +TODO + +**Processing data products:** +TODO diff --git a/requirements.txt b/requirements.txt index 9bfef3b816..1c4046ba46 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,17 +18,17 @@ fprime-fpl-convert-xml==1.0.3 fprime-fpl-extract-xml==1.0.3 fprime-fpl-layout==1.0.3 fprime-fpl-write-pic==1.0.3 -fprime-fpp-check==2.0.2 -fprime-fpp-depend==2.0.2 -fprime-fpp-filenames==2.0.2 -fprime-fpp-format==2.0.2 -fprime-fpp-from-xml==2.0.2 -fprime-fpp-locate-defs==2.0.2 -fprime-fpp-locate-uses==2.0.2 -fprime-fpp-syntax==2.0.2 -fprime-fpp-to-cpp==2.0.2 -fprime-fpp-to-json==2.0.2 -fprime-fpp-to-xml==2.0.2 +fprime-fpp-check==2.1.0a1 +fprime-fpp-depend==2.1.0a1 +fprime-fpp-filenames==2.1.0a1 +fprime-fpp-format==2.1.0a1 +fprime-fpp-from-xml==2.1.0a1 +fprime-fpp-locate-defs==2.1.0a1 +fprime-fpp-locate-uses==2.1.0a1 +fprime-fpp-syntax==2.1.0a1 +fprime-fpp-to-cpp==2.1.0a1 +fprime-fpp-to-json==2.1.0a1 +fprime-fpp-to-xml==2.1.0a1 fprime-gds==3.4.1 fprime-tools==3.4.1 fprime-visual==1.0.2