Data product framework support (#2485)

* Pull in framework changes from data-products

* Pull in framework changes from data-products

* Pull in updates from data-products branch

* Update fpp version

* Fix spelling

* Update spell check

* Add log archiving to FppTest CI

* Update fpp version

* Revise DpContainer

Remove unused code

* Revise data products design

* Revise Fw/Dp

Fix mistake in built-in Python autocoder types

* Revise dp container members

Add _m prefix

* Update fpp version

* Update fpp version

* Update fpp version

* Revise DpContainer and unit tests

Don't fail an assertion on bad serial input

* Pull in changes from data-products branch

* Revise DpContainer

Revise handling of serialize status

---------

Co-authored-by: thomas-bc <thomas.boyerchammard@gmail.com>
This commit is contained in:
Rob Bocchino 2024-02-15 16:06:45 -08:00 committed by GitHub
parent 75b30ad8bb
commit c6f8e7bbba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
47 changed files with 3377 additions and 26 deletions

View File

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

View File

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

View File

@ -178,6 +178,8 @@ class PortHVisitor(AbstractVisitor.AbstractVisitor):
"bool",
"FwBuffSizeType",
"FwChanIdType",
"FwDpIdType",
"FwDpPriorityType",
"FwEnumStoreType",
"FwEventIdType",
"FwIndexType",

View File

@ -18,6 +18,8 @@ types_list = [
port_types_list = [
"FwBuffSizeType",
"FwChanIdType",
"FwDpIdType",
"FwDpPriorityType",
"FwEnumStoreType",
"FwEventIdType",
"FwIndexType",

View File

@ -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
FppTest/dp/CMakeLists.txt Normal file
View File

@ -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()

188
FppTest/dp/DpTest.cpp Normal file
View File

@ -0,0 +1,188 @@
// ======================================================================
// \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);
// Request a buffer for Container 4
this->dpRequest_Container4(CONTAINER_4_DATA_SIZE);
// Request a buffer for Container 5
this->dpRequest_Container5(CONTAINER_5_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);
}
// Get a buffer for Container 4
{
DpContainer container;
Fw::Success status = this->dpGet_Container4(CONTAINER_4_DATA_SIZE, container);
FW_ASSERT(status == Fw::Success::SUCCESS);
// Check the container
this->checkContainer(container, ContainerId::Container4, CONTAINER_4_PACKET_SIZE);
}
// Get a buffer for Container 5
{
DpContainer container;
Fw::Success status = this->dpGet_Container5(CONTAINER_5_DATA_SIZE, container);
FW_ASSERT(status == Fw::Success::SUCCESS);
// Check the container
this->checkContainer(container, ContainerId::Container5, CONTAINER_5_PACKET_SIZE);
}
}
// ----------------------------------------------------------------------
// 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
FppTest/dp/DpTest.fpp Normal file
View File

@ -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
FppTest/dp/DpTest.hpp Normal file
View File

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

View File

@ -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();
}

View File

@ -0,0 +1,331 @@
// ======================================================================
// \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(5);
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_REQUEST(3, ID_BASE + DpTest::ContainerId::Container4, FwSizeType(DpTest::CONTAINER_4_PACKET_SIZE));
ASSERT_PRODUCT_REQUEST(4, ID_BASE + DpTest::ContainerId::Container5, FwSizeType(DpTest::CONTAINER_5_PACKET_SIZE));
ASSERT_PRODUCT_GET_SIZE(5);
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));
ASSERT_PRODUCT_GET(3, ID_BASE + DpTest::ContainerId::Container4, FwSizeType(DpTest::CONTAINER_4_PACKET_SIZE));
ASSERT_PRODUCT_GET(4, ID_BASE + DpTest::ContainerId::Container5, FwSizeType(DpTest::CONTAINER_5_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;
case DpTest::ContainerId::Container4:
FW_ASSERT(size == DpTest::CONTAINER_4_PACKET_SIZE);
buffer = this->container4Buffer;
status = Fw::Success::SUCCESS;
break;
case DpTest::ContainerId::Container5:
FW_ASSERT(size == DpTest::CONTAINER_5_PACKET_SIZE);
buffer = this->container5Buffer;
status = Fw::Success::SUCCESS;
break;
default:
break;
}
return status;
}
} // end namespace FppTest

View File

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

View File

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

View File

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

View File

@ -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)

View File

@ -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
Fw/Dp/CMakeLists.txt Normal file
View File

@ -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
Fw/Dp/Dp.fpp Normal file
View File

@ -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
)
}

174
Fw/Dp/DpContainer.cpp Normal file
View File

@ -0,0 +1,174 @@
// ======================================================================
// \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)
: m_id(id), m_priority(0), m_timeTag(), m_procTypes(0), m_dpState(), m_dataSize(0), m_buffer(), m_dataBuffer() {
// Initialize the user data field
this->initUserDataField();
// Set the packet buffer
// This action also updates the data buffer
this->setBuffer(buffer);
}
DpContainer::DpContainer()
: m_id(0), m_priority(0), m_timeTag(), m_procTypes(0), m_dataSize(0), m_buffer(), m_dataBuffer() {
// Initialize the user data field
this->initUserDataField();
}
// ----------------------------------------------------------------------
// Public member functions
// ----------------------------------------------------------------------
Fw::SerializeStatus DpContainer::deserializeHeader() {
FW_ASSERT(this->m_buffer.isValid());
Fw::SerializeBufferBase& serializeRepr = this->m_buffer.getSerializeRepr();
// Set buffer length
Fw::SerializeStatus status = serializeRepr.setBuffLen(this->m_buffer.getSize());
// Reset deserialization
if (status == Fw::FW_SERIALIZE_OK) {
status = serializeRepr.moveDeserToOffset(Header::PACKET_DESCRIPTOR_OFFSET);
}
// Deserialize the packet type
if (status == Fw::FW_SERIALIZE_OK) {
FwPacketDescriptorType packetDescriptor;
status = serializeRepr.deserialize(packetDescriptor);
if (packetDescriptor != Fw::ComPacket::FW_PACKET_DP) {
status = Fw::FW_SERIALIZE_FORMAT_ERROR;
}
}
// Deserialize the container id
if (status == Fw::FW_SERIALIZE_OK) {
status = serializeRepr.deserialize(this->m_id);
}
// Deserialize the priority
if (status == Fw::FW_SERIALIZE_OK) {
status = serializeRepr.deserialize(this->m_priority);
}
// Deserialize the time tag
if (status == Fw::FW_SERIALIZE_OK) {
status = serializeRepr.deserialize(this->m_timeTag);
}
// Deserialize the processing types
if (status == Fw::FW_SERIALIZE_OK) {
status = serializeRepr.deserialize(this->m_procTypes);
}
// Deserialize the user data
if (status == Fw::FW_SERIALIZE_OK) {
const bool omitLength = true;
const FwSizeType requestedSize = sizeof this->m_userData;
FwSizeType receivedSize = requestedSize;
status = serializeRepr.deserialize(this->m_userData, receivedSize, omitLength);
if (receivedSize != requestedSize) {
status = Fw::FW_DESERIALIZE_SIZE_MISMATCH;
}
}
// Deserialize the data product state
if (status == Fw::FW_SERIALIZE_OK) {
status = serializeRepr.deserialize(this->m_dpState);
}
// Deserialize the data size
if (status == Fw::FW_SERIALIZE_OK) {
status = serializeRepr.deserialize(this->m_dataSize);
}
return status;
}
void DpContainer::serializeHeader() {
FW_ASSERT(this->m_buffer.isValid());
Fw::SerializeBufferBase& serializeRepr = this->m_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, static_cast<FwAssertArgType>(status));
// Serialize the container id
status = serializeRepr.serialize(this->m_id);
FW_ASSERT(status == Fw::FW_SERIALIZE_OK, static_cast<FwAssertArgType>(status));
// Serialize the priority
status = serializeRepr.serialize(this->m_priority);
FW_ASSERT(status == Fw::FW_SERIALIZE_OK, static_cast<FwAssertArgType>(status));
// Serialize the time tag
status = serializeRepr.serialize(this->m_timeTag);
FW_ASSERT(status == Fw::FW_SERIALIZE_OK, static_cast<FwAssertArgType>(status));
// Serialize the processing types
status = serializeRepr.serialize(this->m_procTypes);
FW_ASSERT(status == Fw::FW_SERIALIZE_OK, static_cast<FwAssertArgType>(status));
// Serialize the user data
const bool omitLength = true;
status = serializeRepr.serialize(this->m_userData, sizeof this->m_userData, omitLength);
FW_ASSERT(status == Fw::FW_SERIALIZE_OK, static_cast<FwAssertArgType>(status));
// Serialize the data product state
status = serializeRepr.serialize(this->m_dpState);
FW_ASSERT(status == Fw::FW_SERIALIZE_OK, static_cast<FwAssertArgType>(status));
// Serialize the data size
status = serializeRepr.serialize(this->m_dataSize);
FW_ASSERT(status == Fw::FW_SERIALIZE_OK, static_cast<FwAssertArgType>(status));
// Update the header hash
this->updateHeaderHash();
}
void DpContainer::setBuffer(const Buffer& buffer) {
// Set the buffer
this->m_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->m_dataBuffer.setExtBuffer(dataAddr, dataCapacity);
}
void DpContainer::updateHeaderHash() {
Utils::HashBuffer hashBuffer;
U8* const buffAddr = this->m_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, static_cast<FwAssertArgType>(status));
}
void DpContainer::updateDataHash() {
Utils::HashBuffer hashBuffer;
U8* const buffAddrBase = this->m_buffer.getData();
const U8* const dataAddr = &buffAddrBase[DATA_OFFSET];
const FwSizeType dataSize = this->getDataSize();
const FwSizeType bufferSize = this->m_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, static_cast<FwAssertArgType>(status));
}
// ----------------------------------------------------------------------
// Private member functions
// ----------------------------------------------------------------------
void DpContainer::initUserDataField() {
(void)::memset(this->m_userData, 0, sizeof this->m_userData);
}
} // namespace Fw

225
Fw/Dp/DpContainer.hpp Normal file
View File

@ -0,0 +1,225 @@
// ======================================================================
// \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->m_id; }
//! Get the data size
//! \return The data size
FwSizeType getDataSize() const { return this->m_dataSize; }
//! Get the packet buffer
//! \return The buffer
Fw::Buffer getBuffer() const { return this->m_buffer; }
//! Get the packet size corresponding to the data size
FwSizeType getPacketSize() const { return getPacketSizeForDataSize(this->m_dataSize); }
//! Get the priority
//! \return The priority
FwDpPriorityType getPriority() const { return this->m_priority; }
//! Get the time tag
//! \return The time tag
Fw::Time getTimeTag() const { return this->m_timeTag; }
//! Get the processing types
//! \return The processing types
DpCfg::ProcType::SerialType getProcTypes() const { return this->m_procTypes; }
//! Deserialize the header from the packet buffer
//! Buffer must be valid and large enough to hold a DP container packet
//! \return The serialize status
Fw::SerializeStatus deserializeHeader();
//! Serialize the header into the packet buffer and update the header hash
//! Buffer must be valid and large enough to hold a DP container packet
void serializeHeader();
//! Set the id
void setId(FwDpIdType id //!< The id
) {
this->m_id = id;
}
//! Set the priority
void setPriority(FwDpPriorityType priority //!< The priority
) {
this->m_priority = priority;
}
//! Set the time tag
void setTimeTag(Fw::Time timeTag //!< The time tag
) {
this->m_timeTag = timeTag;
}
//! Set the processing types bit mask
void setProcTypes(DpCfg::ProcType::SerialType procTypes //!< The processing types
) {
this->m_procTypes = procTypes;
}
//! Set the data product state
void setDpState(DpState dpState //!< The data product state
) {
this->m_dpState = dpState;
}
//! Set the data size
void setDataSize(FwSizeType dataSize //!< The data size
) {
this->m_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->m_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 m_userData;
PROTECTED:
// ----------------------------------------------------------------------
// Protected member variables
// ----------------------------------------------------------------------
//! The container id
//! This is a system-global id (component-local id + component base id)
FwDpIdType m_id;
//! The priority
FwDpPriorityType m_priority;
//! The time tag
Time m_timeTag;
//! The processing types
DpCfg::ProcType::SerialType m_procTypes;
//! The data product state
DpState m_dpState;
//! The data size
FwSizeType m_dataSize;
//! The packet buffer
Buffer m_buffer;
//! The data buffer
Fw::ExternalSerializeBuffer m_dataBuffer;
};
} // end namespace Fw
#endif

127
Fw/Dp/docs/sdd.md Normal file
View File

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

147
Fw/Dp/test/ut/TestMain.cpp Normal file
View File

@ -0,0 +1,147 @@
// ----------------------------------------------------------------------
// 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.m_userData);
(void)::memcpy(container.m_userData, userData, sizeof container.m_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);
// Test serialization: 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);
// Test deserialization: Deserialize the header into a new container
DpContainer deserContainer;
deserContainer.setBuffer(container.getBuffer());
const Fw::SerializeStatus serialStatus = deserContainer.deserializeHeader();
ASSERT_EQ(serialStatus, Fw::FW_SERIALIZE_OK);
// Clear out the header in the buffer
FW_ASSERT(buffer.isValid());
::memset(buffer.getData(), 0, DpContainer::Header::SIZE);
// Serialize the header from the new container
deserContainer.serializeHeader();
// Deserialize and check the header
header.deserialize(__FILE__, __LINE__, buffer);
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.m_buffer.getSize(), bufferSize);
// Check the data buffer
U8 *const buffPtr = container.m_buffer.getData();
U8 *const dataPtr = &buffPtr[Fw::DpContainer::DATA_OFFSET];
const FwSizeType dataCapacity = container.m_buffer.getSize() - Fw::DpContainer::MIN_PACKET_SIZE;
ASSERT_EQ(container.m_dataBuffer.getBuffAddr(), dataPtr);
ASSERT_EQ(container.m_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);
}
TEST(Header, BadPacketDescriptor) {
COMMENT("Test header serialization with bad packet descriptor");
// Create a buffer
Fw::Buffer buffer(bufferData, sizeof bufferData);
// Set the packet descriptor to a bad value
Fw::SerializeBufferBase& serialRepr = buffer.getSerializeRepr();
const FwPacketDescriptorType badPacketDescriptor = Fw::ComPacket::FW_PACKET_DP + 1;
Fw::SerializeStatus status = serialRepr.serialize(badPacketDescriptor);
ASSERT_EQ(status, Fw::FW_SERIALIZE_OK);
// Use the buffer to create a container
DpContainer container;
container.setBuffer(buffer);
// Deserialize the header
const Fw::SerializeStatus serialStatus = container.deserializeHeader();
// Check the error
ASSERT_EQ(serialStatus, Fw::FW_SERIALIZE_FORMAT_ERROR);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
STest::Random::seed();
return RUN_ALL_TESTS();
}

View File

@ -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() : m_id(0), m_priority(0), m_timeTag(), m_procTypes(0), m_dpState(), m_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->m_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->m_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->m_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->m_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->m_userData;
const bool omitLength = true;
status = serializeRepr.deserialize(this->m_userData, size, omitLength);
DP_CONTAINER_HEADER_ASSERT_EQ(status, FW_SERIALIZE_OK);
DP_CONTAINER_HEADER_ASSERT_EQ(size, sizeof this->m_userData);
// Deserialize the data product state
DpContainerHeader::moveDeserToOffset(file, line, buffer, DpContainer::Header::DP_STATE_OFFSET);
status = serializeRepr.deserialize(this->m_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->m_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->m_dataSize, computedHashBuffer);
DpContainer container(this->m_id, buffer);
container.setDataSize(this->m_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->m_id, id);
// Check the priority
DP_CONTAINER_HEADER_ASSERT_EQ(this->m_priority, priority);
// Check the time tag
DP_CONTAINER_HEADER_ASSERT_EQ(this->m_timeTag, timeTag);
// Check the deserialized processing types
DP_CONTAINER_HEADER_ASSERT_EQ(this->m_procTypes, procTypes);
// Check the user data
for (FwSizeType i = 0; i < DpCfg::CONTAINER_USER_DATA_SIZE; ++i) {
DP_CONTAINER_HEADER_ASSERT_EQ(this->m_userData[i], userData[i]);
}
// Check the deserialized data product state
DP_CONTAINER_HEADER_ASSERT_EQ(this->m_dpState, dpState);
// Check the data size
DP_CONTAINER_HEADER_ASSERT_EQ(this->m_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 m_id;
//! The priority
FwDpPriorityType m_priority;
//! The time tag
Time m_timeTag;
//! The processing types
DpCfg::ProcType::SerialType m_procTypes;
//! The user data
U8 m_userData[DpCfg::CONTAINER_USER_DATA_SIZE];
//! The data product state
DpState m_dpState;
//! The data size
FwSizeType m_dataSize;
};
} // namespace TestUtil
} // end namespace Fw
#endif

View File

@ -581,7 +581,23 @@ namespace Fw {
this->m_deserLoc = 0;
}
SerializeStatus SerializeBufferBase::deserializeSkip(NATIVE_UINT_TYPE numBytesToSkip)
SerializeStatus SerializeBufferBase::serializeSkip(FwSizeType numBytesToSkip)
{
Fw::SerializeStatus status = FW_SERIALIZE_OK;
// compute new deser loc
const FwSizeType newSerLoc = this->m_serLoc + numBytesToSkip;
// check for room
if (newSerLoc <= this->getBuffCapacity()) {
// update deser loc
this->m_serLoc = newSerLoc;
}
else {
status = FW_SERIALIZE_NO_ROOM_LEFT;
}
return status;
}
SerializeStatus SerializeBufferBase::deserializeSkip(FwSizeType numBytesToSkip)
{
// check for room
if (this->getBuffLength() == this->m_deserLoc) {
@ -594,6 +610,19 @@ namespace Fw {
return FW_SERIALIZE_OK;
}
SerializeStatus SerializeBufferBase::moveSerToOffset(FwSizeType offset) {
// Reset serialization
this->resetSer();
// Advance to offset
return this->serializeSkip(offset);
}
SerializeStatus SerializeBufferBase::moveDeserToOffset(FwSizeType offset) {
// Reset deserialization
this->resetDeser();
// Advance to offset
return this->deserializeSkip(offset);
}
NATIVE_UINT_TYPE SerializeBufferBase::getBuffLength() const {
return this->m_serLoc;
}

View File

@ -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
Svc/DpCatalog/docs/sdd.md Normal file
View File

@ -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 not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

View File

@ -0,0 +1,13 @@
producer
productGetOut
0
dpManager
productGetIn
0
dpManager
bufferGetOut
0
bufferManager
bufferGetCallee
0

View File

@ -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 not shown.

After

Width:  |  Height:  |  Size: 219 KiB

View File

@ -0,0 +1,20 @@
producer
productRequestOut
0
dpManager
productRequestIn
0
dpManager
bufferGetOut
0
bufferManager
bufferGetCallee
0
dpManager
productResponseOut
0
producer
productRecvIn
0

View File

@ -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 not shown.

After

Width:  |  Height:  |  Size: 188 KiB

View File

@ -0,0 +1,20 @@
producer
productSendOut
0
dpManager
productSendIn
0
dpManager
productSendOut
0
dpWriter
bufferSendIn
0
dpWriter
bufferSendOut
0
bufferManager
bufferSendIn
0

253
Svc/DpManager/docs/sdd.md Normal file
View File

@ -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 not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

View File

@ -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
Svc/DpWriter/docs/sdd.md Normal file
View File

@ -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
```

View File

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

View File

@ -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
config/DpCfg.fpp Normal file
View File

@ -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
}
}
}

View File

@ -1,5 +1,7 @@
type FwBuffSizeType
type FwChanIdType
type FwDpIdType
type FwDpPriorityType
type FwEnumStoreType
type FwEventIdType
type FwIndexType

View File

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

View File

@ -0,0 +1,446 @@
# Data Products
## 1. Introduction
F' provides several features for managing the generation, storage,
and downlink of data products.
In this section, we document those features.
## 2. Basic Concepts
First we explain some basic concepts.
### 2.1. Records, Containers, and Dictionaries
F' data products are based on **records** and **containers**.
A record is a basic unit of data.
For example, it may be a struct, an array of typed objects of
statically known size, or an array of bytes of statically unknown size.
A container has an identifier and a priority and stores records.
In C++, a container is represented as a class object with member fields that
(1) store header data and (2) store an `Fw::Buffer` object pointing
to the memory that stores the records.
The set of all containers forms the **data product dictionary**.
To manage the data product dictionary, F Prime uses the same general approach
as for commands, telemetry, events, and parameters:
1. Each component _C_ defines records and containers.
The container IDs are local to _C_.
Typically they have the values 0, 1, 2, ... .
2. Each instance _I_ of _C_ contributes one container _I.c_ to the
dictionary for each container _c_ defined in _C_.
The global identifier for _I.c_ is the base identifier of _I_ plus
the local identifier for _c_.
For example, if the base identifier is 0x1000, then the global identifiers
might be 0x1000, 0x1001, 0x1002, ... .
3. For any topology _T_, the global identifiers _I.c_ for all the instances _T_
form the data product dictionary for _T_.
### 2.2. F' Components
Typically a data product system in an F' application consists of the following
components:
1. One or more **data product producers**.
These components produce data products and are typically mission-specific.
For example, they may produce science data.
1. Standard F Prime components for managing data products.
1. A **data product manager**.
This component allocates memory for empty containers.
It also forwards filled containers to the data product writer.
See [`Svc::DpManager`](../../Svc/DpManager/docs/sdd.md).
1. A **data product writer**.
This component receives filled containers from data product
producers. It writes the contents of the containers to non-volatile
storage. See [`Svc::DpWriter`](../../Svc/DpWriter/docs/sdd.md).
1. A **data product catalog**.
This component maintains a database of available data
products. By command, it downlinks and deletes data products.
See TODO.
1. A **data product processor**.
This component performs in-memory processing on data
product containers.
See TODO.
Note that when using data products, you need to develop only the
producer components. The other components are provided by F'.
## 3. Producer Components
In this section we provide more detail about producer components.
### 3.1. Activities
A producer component typically repeats the following activities,
as often as necessary:
1. Request a container from a data manager component.
2. When the container is received, fill the container with
data by serializing records into the container.
3. When the container is full, send the container to the
data product manager, which forwards it to the data
product writer.
The FPP model and the autocoded C++ have several features that
support these activities.
We discuss these features in the following sections.
### 3.2. FPP Modeling
In this section we summarize the features of the FPP modeling
language used in constructing data product producer components.
Each of these features is fully documented in _The FPP User's Guide_
and _The FPP Language Specification_.
#### 3.2.1. Ports
FPP provides the following special ports for managing data products:
1. A **product get port** of type [`Fw::DpGet`](../../Fw/Dp/docs/sdd.md).
This is an output port for synchronously requesting
memory from a buffer manager.
The request is served on the thread that invokes the port
and causes a mutex lock to be taken on that thread.
Example syntax:
```
product get port productGetOut
```
1. A **product request port** of type [`Fw::DpRequest`](../../Fw/Dp/docs/sdd.md).
This is an output port for asynchronously requesting memory
from a data product manager.
The request is served on the thread of the data product manager.
This approach incurs the overhead of a separate thread, but it
does not require the requesting thread to take a lock.
Example syntax:
```
product request port productRequestOut
```
1. A **product receive port** of type [`Fw::DpResponse`](../../Fw/Dp/docs/sdd.md).
This is an input port for receiving an empty container in response
to an asynchronous request. Example syntax:
```
async product recv port productRecvIn
```
1. A **product send port** of type [`Fw::DpSend`](../../Fw/Dp/docs/sdd.md).
This is an output port for sending a filled container
to a data product writer. Example syntax:
```
product send port productSendOut
```
Each data product producer component must have the following
ports in its component model:
1. One or both of a `product` `get` port and a `product` `request` port.
1. A `product` `send` port.
A component that has a `product` `request` port must also have
a `product` `receive` port.
#### 3.2.2. Records
A record is a unit of data.
When defining a producer component, you can specify one or more
records.
A record specification consists of a name, a type specifier, and an optional identifier.
The type specifier may be one of the following:
1. An FPP type _T_. In this case, the record contains a single value of type
_T_. _T_ may be any FPP type, including a struct or array type.
1. An FPP type _T_ followed by the keyword `array`.
In this case, the record is an array of values of type _T_
of statically unknown size.
The size of the array is stored in the record.
In either case, _T_ may be any FPP type, including a struct or array type.
Example syntax:
```
@ A struct with a fixed-size member array
struct FixedSizeData {
data: [1024] F32
}
@ A record containing fixed-size data
product record FixedSizeDataRecord: FixedSizeData id 0x00
@ A record containing a variable-size array
product record F32ArrayRecord: F32 array id 0x01
```
#### 3.2.3. Containers
A container is a data structure that stores records.
When defining a producer component, you can specify one or more containers.
Each container specified in a component can store
any of the records specified in the component.
A container specification consists of a name, an optional
identifier, and an optional default priority.
The default priority is the priority to use if no
other priority is specified for the container
during operations.
Example syntax:
```
product container C1
product container C2 id 0x01 default priority 10
```
### 3.3. Autocoded C++
The autocoded C++ base class for a producer component _C_ provides
the following API elements:
1. Enumerations defining the available container IDs, container
priorities, and record IDs.
1. A member class _C_ `::DpContainer`. This class is derived from
[`Fw::DpContainer`](../../Fw/Dp/docs/sdd.md) and represents a container
specialized to the data products defined in _C_.
Each instance of _C_ `::DpContainer` is a wrapper for an `Fw::Buffer` _B_,
which points to allocated memory.
The class provides operations for serializing the records
defined in _C_ into the memory pointed to by _B_.
There is one operation _C_ `::DpContainer::serialize_` _R_
for each record _R_ defined in _C_.
For the serialized format of each record, see the documentation
for [`Fw::DpContainer`](../../Fw/Dp/docs/sdd.md).
1. If _C_ has a `product` `get` port, a member function `dpGet_`
_c_ for each container defined in _C_.
This function takes a container ID, a data size, and a reference
to a data product container _D_.
It invokes `productGetOut`, which is typically connected
to a data product manager component.
In the nominal case, the invocation returns an `Fw::Buffer` _B_ large enough
to store a data product packet with the requested data size.
The `dpGet` function then uses the ID and _B_ to initialize _D_.
It returns a status value indicating whether the buffer
allocation succeeded.
1. If _C_ has a `product` `request` port, a member function
`dpRequest_` _c_ for each container defined in _C_.
This function takes a container ID and a data size.
It sends out a request on `productRequestOut`, which is
typically connected to a data product manager component.
The request is for a buffer large enough to store a data
product packet with the requested data size.
1. If _C_ has a `product` `recv` port, a pure virtual
member function `dpRecv_` _c_ `_handler` for each container _c_
defined in _C_.
When a fresh container arrives in response to a
`dpRequest` invocation, the autocoded C++ uses the container ID to
select and invoke the appropriate `dpRecv` handler.
The implementation of _C_ must override each handler
to provide the mission-specific behavior for filling
in the corresponding container.
The arguments to `dpRecv_` _c_ `_handler` provide
(1) a reference to the container, which the implementation can fill in;
and (2) a status value indicating whether the container
is valid. An invalid container can result if the buffer
allocation fails.
1. A member function `dpSend` for sending a filled
data product container.
This function takes a reference to a container _c_ and an
optional time tag.
It does the following:
1. If no time tag is provided, then invoke `timeGetOut`
to get the system time and use it to set the time tag.
1. Store the time tag into _c_.
1. Send _c_ on `productSendOut`.
### 3.4. Unit Test Support
In F Prime, each component _C_ comes with auto-generated
classes _C_ `TesterBase` and _C_ `GTestBase` for writing
unit tests against _C_.
_C_ `GTestBase` is derived from _C_ `TesterBase`; it
provides test macros based on the Google Test framework.
To write unit tests, you construct a class _C_ `Tester`.
Typically _C_ `Tester` is derived from _C_ `GTestBase` and
uses the Google Test framework macros.
If for some reason you can't use the Google Test framework
(e.g., because you are running on a platform that does not support it),
then your _C_ `Tester` class can be derived from _C_ `TesterBase`.
This section documents the unit test support for producer components.
#### 3.4.1. The TesterBase Class
**History data structures:**
The class _C_ `TesterBase` provides the following histories:
1. If _C_ has a product get port,
then _C_ `TesterBase` has a history called `productGetHistory`.
Each element in the history is of type `DpGet`.
`DpGet` is a struct with fields storing the container ID and the
size emitted on the product get port.
1. If _C_ has a product request port, then _C_ `TesterBase` has a
corresponding history called `productRequestHistory`.
Each element in the history is of type `DpRequest`.
`DpRequest` is a struct with fields storing the container ID and the
size emitted on the product request port.
1. _C_ `TesterBase` has a history called `productSendHistory`.
Each element in the history is of type `DpSend`.
`DpSend` is a struct with fields storing the container ID and
a shallow copy of the buffer emitted on the product send port.
**History functions:**
The class _C_ `TesterBase` provides the following functions
for managing the histories:
1. If _C_ has a product get port, then _C_ `TesterBase` provides
the following functions:
1. `pushProductGetEntry`: This function takes a container ID and
a size. It constructs the corresponding `DpGet` history object
and pushes it on `productGetHistory`. Typically this function is
called by `productGet_handler` (see below).
1. `productGet_handler`: This function is called when the tester
component receives data emitted on the product get port of the
component under test. It takes a container ID, a size, and a
mutable reference to a buffer _B_. By default it calls
`pushProductGetEntry` with the ID and size and returns `FAILURE`,
indicating that no memory was allocated and _B_ was not updated.
This function is virtual, so you can override it with your own
behavior. For example, your function could call `pushProductGetEntry`,
allocate a buffer, store the allocated buffer into _B_, and return
`SUCCESS`.
1. If _C_ has a product request port, then _C_ `TesterBase` provides
the following functions:
1. `pushProductRequestEntry`: This function takes a container ID and
a size. It constructs the corresponding `DpRequest` history object
and pushes it on `productRequestHistory`. Typically this function is
called by `productRequest_handler` (see below).
1. `productRequest_handler`: This function is called when the tester
component receives data emitted on the product request port of the
component under test. It takes a container ID and a size. By default
it calls `pushProductRequestEntry` with the ID and size. This function
is virtual, so you can override it with your own behavior.
1. _C_ `TesterBase` provides the following functions:
1. `pushProductSendEntry`: This function takes a container ID and a
const reference to a buffer. It constructs the corresponding
`DpSend` history object and pushes it on `productSendHistory`.
Typically this function is called by `productSend_handler` (see below).
1. `productSend_handler`: This function is called when the tester
component receives data emitted on the product send port of the
component under test. It takes a container ID and a const reference
to a buffer. By default it calls `pushProductSendEntry` with the
ID and buffer. This function is virtual, so you can override it
with your own behavior.
#### 3.4.2. The GTestBase Class
**Testing macros:**
The class _C_ `GTestBase` provides the following macros for
verifying the histories managed by _C_ `TesterBase`.
1. If _C_ defines data products and has a product get port, then _C_
`GTestBase` provides the following macros:
1. `ASSERT_PRODUCT_GET_SIZE(size)`: This macro checks that `productGetHistory`
has the specified size (number of entries).
1. `ASSERT_PRODUCT_GET(index, id, size)`: This macro checks that
`productGetHistory` has the specified container ID and size
at the specified history index.
1. If _C_ defines data products and has a product request port,
then _C_ `GTestBase` provides the following macros:
1. `ASSERT_PRODUCT_REQUEST_SIZE(size)`: This macro checks that
`productRequestHistory` has the specified size (number of entries).
1. `ASSERT_PRODUCT_REQUEST(index, id, size)`: This macro checks that
`productRequestHistory` has the specified container ID and size
at the specified history index.
1. If _C_ defines data products, then _C_ `GTestBase` provides
the following macros:
1. `ASSERT_PRODUCT_SEND_SIZE(size)`: This macro checks that
`productSendHistory` has the specified size (number of entries).
1. `ASSERT_PRODUCT_SEND(index, id, priority, timeTag, procType, userData, dataSize, buffer)`:
All the arguments of this macro are inputs (read-only) except `buffer`, which is
a by-reference output and must be a variable of type `Fw::Buffer&`.
This macro verifies the entry `entry` stored at the specified
index of `productSendHistory`. It does the following:
1. Check that `entry.id` matches the specified ID.
1. Deserialize the data product header stored in `entry.buffer`.
1. Check that the container ID, priority, time tag, processor type,
user data, and data size stored in the deserialized header
match the specified values.
1. Assign `entry.buffer` to `buffer`. After this macro runs,
the deserialization pointer of `buffer` points into the start
of the data payload of `entry.buffer`. You can write additional
code to deserialize and check the data payload.
**Container IDs:**
The container IDs emitted by the component under test are global
IDs.
Therefore, when constructing specified IDs you must add
the ID base specified in the tester component to the local
ID specified in the component under test.
For example, for container `CONTAINER` in component `Component`,
you would write
```cpp
ID_BASE + Component::ContainerId::CONTAINER
```
`ID_BASE` is a standard constant defined in each Tester implementation
and provided to the Tester base classes in their constructors.
## 4. Use Cases
In this section we discuss several common use cases involving
data products.
**Requesting and sending data products:**
See the example uses in the documentation for
[`Svc::DpManager`](../../Svc/DpManager/docs/sdd.md#5-example-uses).
The component referred to as `producer` in that document
is a data product producer.
**Writing data products to non-volatile storage:**
See the example uses in the documentation for
[`Svc::DpWriter`](../../Svc/DpWriter/docs/sdd.md#6-example-uses).
The component referred to as `producer` in that document
is a data product producer.
**Cataloging and downlinking data products:**
TODO
**Processing data products:**
TODO

View File

@ -18,17 +18,17 @@ fprime-fpl-convert-xml==1.0.3
fprime-fpl-extract-xml==1.0.3
fprime-fpl-layout==1.0.3
fprime-fpl-write-pic==1.0.3
fprime-fpp-check==2.0.2
fprime-fpp-depend==2.0.2
fprime-fpp-filenames==2.0.2
fprime-fpp-format==2.0.2
fprime-fpp-from-xml==2.0.2
fprime-fpp-locate-defs==2.0.2
fprime-fpp-locate-uses==2.0.2
fprime-fpp-syntax==2.0.2
fprime-fpp-to-cpp==2.0.2
fprime-fpp-to-json==2.0.2
fprime-fpp-to-xml==2.0.2
fprime-fpp-check==2.1.0a3
fprime-fpp-depend==2.1.0a3
fprime-fpp-filenames==2.1.0a3
fprime-fpp-format==2.1.0a3
fprime-fpp-from-xml==2.1.0a3
fprime-fpp-locate-defs==2.1.0a3
fprime-fpp-locate-uses==2.1.0a3
fprime-fpp-syntax==2.1.0a3
fprime-fpp-to-cpp==2.1.0a3
fprime-fpp-to-json==2.1.0a3
fprime-fpp-to-xml==2.1.0a3
fprime-gds==3.4.1
fprime-tools==3.4.1
fprime-visual==1.0.2