Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Data product framework support #2485

Merged
merged 20 commits into from
Feb 16, 2024
Merged
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions .github/workflows/fpp-tests.yml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -178,6 +178,8 @@ def _get_args_sum_string(self, obj):
"bool",
"FwBuffSizeType",
"FwChanIdType",
"FwDpIdType",
"FwDpPriorityType",
"FwEnumStoreType",
"FwEventIdType",
"FwIndexType",
2 changes: 2 additions & 0 deletions Autocoders/Python/src/fprime_ac/utils/TypesList.py
Original file line number Diff line number Diff line change
@@ -18,6 +18,8 @@
port_types_list = [
"FwBuffSizeType",
"FwChanIdType",
"FwDpIdType",
"FwDpPriorityType",
"FwEnumStoreType",
"FwEventIdType",
"FwIndexType",
15 changes: 7 additions & 8 deletions FppTest/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions FppTest/dp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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()
168 changes: 168 additions & 0 deletions FppTest/dp/DpTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// ======================================================================
// \title DpTest.cpp
// \author bocchino
// \brief cpp file for DpTest component implementation class
// ======================================================================

#include <cstdio>

#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
82 changes: 82 additions & 0 deletions FppTest/dp/DpTest.fpp
Original file line number Diff line number Diff line change
@@ -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

}

}
154 changes: 154 additions & 0 deletions FppTest/dp/DpTest.hpp
Original file line number Diff line number Diff line change
@@ -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 <array>

#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<U8, 256>;
using U32ArrayRecordData = std::array<U32, 100>;
using DataArrayRecordData = std::array<DpTest_Data, 300>;

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
81 changes: 81 additions & 0 deletions FppTest/dp/test/ut/TestMain.cpp
Original file line number Diff line number Diff line change
@@ -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();
}
317 changes: 317 additions & 0 deletions FppTest/dp/test/ut/Tester.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
// ======================================================================
// \title DpTest.hpp
// \author bocchino
// \brief cpp file for DpTest test harness implementation class
// ======================================================================

#include <cstdio>
#include <cstring>

#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<U8>(STest::Pick::any());
}
for (U32& elt : this->u32ArrayRecordData) {
elt = static_cast<U8>(STest::Pick::any());
}
for (DpTest_Data& elt : this->dataArrayRecordData) {
elt.set(static_cast<U16>(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
171 changes: 171 additions & 0 deletions FppTest/dp/test/ut/Tester.hpp
Original file line number Diff line number Diff line change
@@ -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
41 changes: 41 additions & 0 deletions FppTest/dp/test/ut/TesterHelpers.cpp
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions Fw/Buffer/Buffer.fpp
Original file line number Diff line number Diff line change
@@ -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

9 changes: 5 additions & 4 deletions Fw/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
1 change: 1 addition & 0 deletions Fw/Com/ComPacket.hpp
Original file line number Diff line number Diff line change
@@ -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;
13 changes: 13 additions & 0 deletions Fw/Dp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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()
62 changes: 62 additions & 0 deletions Fw/Dp/Dp.fpp
Original file line number Diff line number Diff line change
@@ -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
)

}
118 changes: 118 additions & 0 deletions Fw/Dp/DpContainer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// ======================================================================
// \title DpContainer.cpp
// \author bocchino
// \brief cpp file for DpContainer
// ======================================================================

#include <cstring>

#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);

Check warning

Code scanning / CodeQL

Unchecked function argument

This use of parameter buffer has not been checked.
}

DpContainer::DpContainer() : id(0), priority(0), procTypes(0), dataSize(0), buffer(), dataBuffer() {
// Initialize the user data field
this->initUserDataField();
}

// ----------------------------------------------------------------------
// Public member functions
// ----------------------------------------------------------------------

void DpContainer::serializeHeader() {

Check notice

Code scanning / CodeQL

Long function without assertion

All functions of more than 10 lines should have at least one assertion.
Fw::SerializeBufferBase& serializeRepr = this->buffer.getSerializeRepr();
// Reset serialization
serializeRepr.resetSer();
// Serialize the packet type
Fw::SerializeStatus status =
serializeRepr.serialize(static_cast<FwPacketDescriptorType>(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() {

Check notice

Code scanning / CodeQL

Long function without assertion

All functions of more than 10 lines should have at least one assertion.
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
219 changes: 219 additions & 0 deletions Fw/Dp/DpContainer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
// ======================================================================
// \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; }

//! 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
127 changes: 127 additions & 0 deletions Fw/Dp/docs/sdd.md
Original file line number Diff line number Diff line change
@@ -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.fpp`](../../../config/DpCfg.fpp):

| 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_USER_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.

<a name="serial-format"></a>
### 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 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.
116 changes: 116 additions & 0 deletions Fw/Dp/test/ut/TestMain.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// ----------------------------------------------------------------------
// TestMain.cpp
// ----------------------------------------------------------------------

#include <cstring>
#include <limits>

#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<FwDpPriorityType>::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<U8>(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<DpState::T>(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<U8>(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<FwDpIdType>::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<FwDpIdType>::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();
}
200 changes: 200 additions & 0 deletions Fw/Dp/test/util/DpContainerHeader.hpp
Original file line number Diff line number Diff line change
@@ -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
31 changes: 30 additions & 1 deletion Fw/Types/Serializable.cpp
Original file line number Diff line number Diff line change
@@ -581,7 +581,23 @@ namespace Fw {
this->m_deserLoc = 0;
}

SerializeStatus SerializeBufferBase::deserializeSkip(NATIVE_UINT_TYPE numBytesToSkip)
SerializeStatus SerializeBufferBase::serializeSkip(FwSizeType numBytesToSkip)

Check notice

Code scanning / CodeQL

Long function without assertion

All functions of more than 10 lines should have at least one assertion.
{
Fw::SerializeStatus status = FW_SERIALIZE_OK;
// compute new deser loc
const FwSizeType newSerLoc = this->m_serLoc + numBytesToSkip;

Check warning

Code scanning / CodeQL

Unchecked function argument

This use of parameter numBytesToSkip has not been checked.
// 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);

Check warning

Code scanning / CodeQL

Unchecked function argument

This use of parameter offset has not been checked.
}
SerializeStatus SerializeBufferBase::moveDeserToOffset(FwSizeType offset) {
// Reset deserialization
this->resetDeser();
// Advance to offset
return this->deserializeSkip(offset);

Check warning

Code scanning / CodeQL

Unchecked function argument

This use of parameter offset has not been checked.
}

NATIVE_UINT_TYPE SerializeBufferBase::getBuffLength() const {
return this->m_serLoc;
}
6 changes: 5 additions & 1 deletion Fw/Types/Serializable.hpp
Original file line number Diff line number Diff line change
@@ -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
44 changes: 44 additions & 0 deletions Svc/DpCatalog/docs/sdd.md
Original file line number Diff line number Diff line change
@@ -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

<a name="ground_interface"></a>
## 4. Ground Interface

TODO

## 5. Example Uses

TODO
Binary file added Svc/DpManager/docs/img/DpManager.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Svc/DpManager/docs/img/top/buffer-get.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions Svc/DpManager/docs/img/top/buffer-get.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
producer
productGetOut
0
dpManager
productGetIn
0

dpManager
bufferGetOut
0
bufferManager
bufferGetCallee
0
110 changes: 110 additions & 0 deletions Svc/DpManager/docs/img/top/buffer-request.json
Original file line number Diff line number Diff line change
@@ -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
]
]
]
}
Binary file added Svc/DpManager/docs/img/top/buffer-request.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions Svc/DpManager/docs/img/top/buffer-request.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
producer
productRequestOut
0
dpManager
productRequestIn
0

dpManager
bufferGetOut
0
bufferManager
bufferGetCallee
0

dpManager
productResponseOut
0
producer
productRecvIn
0
118 changes: 118 additions & 0 deletions Svc/DpManager/docs/img/top/product-send.json
Original file line number Diff line number Diff line change
@@ -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
]
]
]
}
Binary file added Svc/DpManager/docs/img/top/product-send.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions Svc/DpManager/docs/img/top/product-send.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
producer
productSendOut
0
dpManager
productSendIn
0

dpManager
productSendOut
0
dpWriter
bufferSendIn
0

dpWriter
bufferSendOut
0
bufferManager
bufferSendIn
0
253 changes: 253 additions & 0 deletions Svc/DpManager/docs/sdd.md
Original file line number Diff line number Diff line change
@@ -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.

<div>
<img src="img/DpManager.png" width=700/>
</div>

### 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

<a name="getBuffer"></a>
#### 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`.

<a name="ground_interface"></a>
## 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

<a name="top-diagrams"></a>
### 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

<div>
<img src="img/top/buffer-get.png" width=800/>
</div>

#### 5.1.2. Asynchronously Requesting Data Product Buffers

<div>
<img src="img/top/buffer-request.png" width=800/>
</div>

#### 5.1.3. Sending Data Products

<div>
<img src="img/top/product-send.png" width=1000/>
</div>

### 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
```
Binary file added Svc/DpWriter/docs/img/DpWriter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Svc/DpWriter/docs/img/top/product-write.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions Svc/DpWriter/docs/img/top/product-write.txt
Original file line number Diff line number Diff line change
@@ -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
192 changes: 192 additions & 0 deletions Svc/DpWriter/docs/sdd.md
Original file line number Diff line number Diff line change
@@ -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.

<div>
<img src="img/DpWriter.png" width=700/>
</div>

### 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`.

<a name="file_format"></a>
## 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`.

<a name="ground_interface"></a>
## 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

<a name="top-diagrams"></a>
### 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.

<div>
<img src="img/top/product-write.png" width=800/>
</div>

### 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
```
6 changes: 6 additions & 0 deletions config/AcConstants.fpp
Original file line number Diff line number Diff line change
@@ -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.
# ----------------------------------------------------------------------
3 changes: 2 additions & 1 deletion config/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
26 changes: 26 additions & 0 deletions config/DpCfg.fpp
Original file line number Diff line number Diff line change
@@ -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
}

}

}
2 changes: 2 additions & 0 deletions config/FpConfig.fpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
type FwBuffSizeType
type FwChanIdType
type FwDpIdType
type FwDpPriorityType
type FwEnumStoreType
type FwEventIdType
type FwIndexType
6 changes: 6 additions & 0 deletions config/FpConfig.h
Original file line number Diff line number Diff line change
@@ -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
Loading