Repository: nasa/fprime (language: python)

Issue:
DpContainer does not reset serialization on assignment.
| | |
|:---|:---|
|**_F´ Version_**| [v3.4.3](https://github.com/nasa/fprime/releases/tag/v3.4.3) |
|**_Affected Component_**|  n/a |
---
## Problem Description

When a new buffer is assigned to a DpContainer, the serialization pointer is not reset.

## Context / Environment

Execute `fprime-util version-check` and share the output.

```
Operating System: Linux
CPU Architecture: x86_64
Platform: Linux-5.15.133.1-microsoft-standard-WSL2-x86_64-with-glibc2.35
Python version: 3.10.12
CMake version: 3.30.2
Pip version: 22.0.2
Pip packages:
    fprime-tools==3.4.4
    fprime-gds==3.4.4a3
    fprime-fpp-*==2.2.0a1
```

## How to Reproduce

1. Fill a container with data products and write
2. Ask DpManager for another buffer
3. Underlying DpContainer does not reset serialization
4. Adding records picks up at old serialization point

I manually added a reset of the serialization to DpContainer:

```c++
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 with
    // zero-size data
    const FwSizeType bufferSize = buffer.getSize();
    FW_ASSERT(bufferSize >= MIN_PACKET_SIZE, static_cast<FwAssertArgType>(bufferSize),
              static_cast<FwAssertArgType>(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
    const FwSizeType minBufferSize = DATA_OFFSET + dataCapacity;
    FW_ASSERT(bufferSize >= minBufferSize, static_cast<FwAssertArgType>(bufferSize),
              static_cast<FwAssertArgType>(minBufferSize));
    U8* const dataAddr = &buffAddr[DATA_OFFSET];
    this->m_dataBuffer.setExtBuffer(dataAddr, static_cast<Fw::Serializable::SizeType>(dataCapacity));
    // reset the serialization
    this->m_dataBuffer.resetSer();
}
```

However, when adding new records, eventually the container asserts:

```
EVENT: (8450) (2:1728529763,447710) ACTIVITY_LO: (SG1) DpComplete : Writing 15 DP records 780 bytes total
Assert: "/home/tcanham/source/fprime/Fw/Dp/DpContainer.cpp:201" 1463 907
FATAL 16898 handled.
Exiting with abort signal and core dump file.
Aborted
```

## Expected Behavior

The  container should be completely reset.



Discussion / hints:

Fix patch:
diff --git a/FppTest/dp/DpTest.cpp b/FppTest/dp/DpTest.cpp
index 56d0e7baa5..b1a74e0dd4 100644
--- a/FppTest/dp/DpTest.cpp
+++ b/FppTest/dp/DpTest.cpp
@@ -102,6 +102,7 @@ void DpTest::schedIn_handler(const NATIVE_INT_TYPE portNum, U32 context) {
 void DpTest ::dpRecv_Container1_handler(DpContainer& container, Fw::Success::T status) {
     // Test container assignment
     this->m_container = container;
+    this->checkContainerEmpty(this->m_container);
     if (status == Fw::Success::SUCCESS) {
         auto serializeStatus = Fw::FW_SERIALIZE_OK;
         for (FwSizeType i = 0; i < CONTAINER_1_DATA_SIZE; ++i) {
@@ -121,6 +122,7 @@ void DpTest ::dpRecv_Container1_handler(DpContainer& container, Fw::Success::T s
 void DpTest ::dpRecv_Container2_handler(DpContainer& container, Fw::Success::T status) {
     // Test container assignment
     this->m_container = container;
+    this->checkContainerEmpty(this->m_container);
     if (status == Fw::Success::SUCCESS) {
         const DpTest_Data dataRecord(this->dataRecordData);
         auto serializeStatus = Fw::FW_SERIALIZE_OK;
@@ -141,6 +143,7 @@ void DpTest ::dpRecv_Container2_handler(DpContainer& container, Fw::Success::T s
 void DpTest ::dpRecv_Container3_handler(DpContainer& container, Fw::Success::T status) {
     // Test container assignment
     this->m_container = container;
+    this->checkContainerEmpty(this->m_container);
     if (status == Fw::Success::SUCCESS) {
         auto serializeStatus = Fw::FW_SERIALIZE_OK;
         for (FwSizeType i = 0; i < CONTAINER_3_DATA_SIZE; ++i) {
@@ -161,6 +164,7 @@ void DpTest ::dpRecv_Container3_handler(DpContainer& container, Fw::Success::T s
 void DpTest ::dpRecv_Container4_handler(DpContainer& container, Fw::Success::T status) {
     // Test container assignment
     this->m_container = container;
+    this->checkContainerEmpty(this->m_container);
     if (status == Fw::Success::SUCCESS) {
         auto serializeStatus = Fw::FW_SERIALIZE_OK;
         for (FwSizeType i = 0; i < CONTAINER_4_DATA_SIZE; ++i) {
@@ -181,6 +185,7 @@ void DpTest ::dpRecv_Container4_handler(DpContainer& container, Fw::Success::T s
 void DpTest ::dpRecv_Container5_handler(DpContainer& container, Fw::Success::T status) {
     // Test container assignment
     this->m_container = container;
+    this->checkContainerEmpty(this->m_container);
     if (status == Fw::Success::SUCCESS) {
         auto serializeStatus = Fw::FW_SERIALIZE_OK;
         for (FwSizeType i = 0; i < CONTAINER_5_DATA_SIZE; ++i) {
@@ -201,6 +206,7 @@ void DpTest ::dpRecv_Container5_handler(DpContainer& container, Fw::Success::T s
 void DpTest ::dpRecv_Container6_handler(DpContainer& container, Fw::Success::T status) {
     // Test container assignment
     this->m_container = container;
+    this->checkContainerEmpty(this->m_container);
     if (status == Fw::Success::SUCCESS) {
         auto serializeStatus = Fw::FW_SERIALIZE_OK;
         for (FwSizeType i = 0; i < CONTAINER_6_DATA_SIZE; ++i) {
@@ -220,6 +226,7 @@ void DpTest ::dpRecv_Container6_handler(DpContainer& container, Fw::Success::T s
 void DpTest ::dpRecv_Container7_handler(DpContainer& container, Fw::Success::T status) {
     // Test container assignment
     this->m_container = container;
+    this->checkContainerEmpty(this->m_container);
     if (status == Fw::Success::SUCCESS) {
         auto serializeStatus = Fw::FW_SERIALIZE_OK;
         for (FwSizeType i = 0; i < CONTAINER_7_DATA_SIZE; ++i) {
@@ -241,10 +248,21 @@ void DpTest ::dpRecv_Container7_handler(DpContainer& container, Fw::Success::T s
 // Private helper functions
 // ----------------------------------------------------------------------
 
+void DpTest::checkContainerEmpty(const DpContainer& container) const {
+    const FwSizeType dataSize = container.getDataSize();
+    FW_ASSERT(dataSize == 0, static_cast<FwAssertArgType>(dataSize));
+    const Fw::SerializeBufferBase& buffer = container.m_dataBuffer;
+    const FwSizeType buffLength = buffer.getBuffLength();
+    FW_ASSERT(buffLength == 0, static_cast<FwAssertArgType>(buffLength));
+    const FwSizeType buffLeft = buffer.getBuffLeft();
+    FW_ASSERT(buffLeft == 0, static_cast<FwAssertArgType>(buffLeft));
+}
+
 void DpTest::checkContainer(const DpContainer& container,
                             FwDpIdType localId,
                             FwSizeType size,
                             FwDpPriorityType priority) const {
+    this->checkContainerEmpty(container);
     FW_ASSERT(container.getBaseId() == this->getIdBase(), container.getBaseId(), this->getIdBase());
     FW_ASSERT(container.getId() == container.getBaseId() + localId, container.getId(), container.getBaseId(),
               ContainerId::Container1);
diff --git a/FppTest/dp/DpTest.hpp b/FppTest/dp/DpTest.hpp
index 1adab4e75b..05c6b83c22 100644
--- a/FppTest/dp/DpTest.hpp
+++ b/FppTest/dp/DpTest.hpp
@@ -15,7 +15,6 @@
 namespace FppTest {
 
 class DpTest : public DpTestComponentBase {
-
     // Friend class for testing
     friend class Tester;
 
@@ -139,6 +138,10 @@ class DpTest : public DpTestComponentBase {
     // Private helper functions
     // ----------------------------------------------------------------------
 
+    //! Check that a container is empty
+    void checkContainerEmpty(const DpContainer& container  //!< The container
+    ) const;
+
     //! Check a container for validity
     void checkContainer(const DpContainer& container,  //!< The container
                         FwDpIdType localId,            //!< The expected local id
diff --git a/Fw/Dp/DpContainer.cpp b/Fw/Dp/DpContainer.cpp
index 9eac97f16b..be3a6c6f77 100644
--- a/Fw/Dp/DpContainer.cpp
+++ b/Fw/Dp/DpContainer.cpp
@@ -139,7 +139,11 @@ void DpContainer::setBuffer(const Buffer& buffer) {
     FW_ASSERT(bufferSize >= minBufferSize, static_cast<FwAssertArgType>(bufferSize),
               static_cast<FwAssertArgType>(minBufferSize));
     U8* const dataAddr = &buffAddr[DATA_OFFSET];
+    // Set the buffer
+    // This action also clears the serialization state in the buffer
     this->m_dataBuffer.setExtBuffer(dataAddr, static_cast<Fw::Serializable::SizeType>(dataCapacity));
+    // Reset the data size
+    this->m_dataSize = 0;
 }
 
 Utils::HashBuffer DpContainer::getHeaderHash() const {
diff --git a/Fw/Dp/DpContainer.hpp b/Fw/Dp/DpContainer.hpp
index a9d9e3030c..b0363dbfe6 100644
--- a/Fw/Dp/DpContainer.hpp
+++ b/Fw/Dp/DpContainer.hpp
@@ -173,6 +173,8 @@ class DpContainer {
     //! Invalidate the packet buffer
     void invalidateBuffer() {
         this->m_buffer = Fw::Buffer();
+        this->m_dataBuffer.clear();
+        this->m_dataSize = 0;
     }
 
     //! Get the stored header hash
diff --git a/Fw/Types/CMakeLists.txt b/Fw/Types/CMakeLists.txt
index 0d1fc3c273..6c3c54334f 100644
--- a/Fw/Types/CMakeLists.txt
+++ b/Fw/Types/CMakeLists.txt
@@ -25,6 +25,7 @@ set(MOD_DEPS
 register_fprime_module()
 ### UTs ###
 set(UT_SOURCE_FILES
+  "${CMAKE_CURRENT_LIST_DIR}/test/ut/ExternalSerializeBufferTest.cpp"
   "${CMAKE_CURRENT_LIST_DIR}/test/ut/TypesTest.cpp"
 )
 set(UT_MOD_DEPS
diff --git a/Fw/Types/Serializable.cpp b/Fw/Types/Serializable.cpp
index dfba70881f..afe43934ed 100644
--- a/Fw/Types/Serializable.cpp
+++ b/Fw/Types/Serializable.cpp
@@ -46,10 +46,8 @@ void SerializeBufferBase::copyFrom(const SerializeBufferBase& src) {
     FW_ASSERT(src.getBuffAddr());
     FW_ASSERT(this->getBuffAddr());
     // destination has to be same or bigger
-    FW_ASSERT(
-        src.getBuffLength() <= this->getBuffCapacity(),
-        static_cast<FwAssertArgType>(src.getBuffLength()),
-        static_cast<FwAssertArgType>(this->getBuffLength()));
+    FW_ASSERT(src.getBuffLength() <= this->getBuffCapacity(), static_cast<FwAssertArgType>(src.getBuffLength()),
+              static_cast<FwAssertArgType>(this->getBuffLength()));
     (void)memcpy(this->getBuffAddr(), src.getBuffAddr(), this->m_serLoc);
 }
 
@@ -676,6 +674,8 @@ SerializeStatus SerializeBufferBase::setBuffLen(Serializable::SizeType length) {
 }
 
 Serializable::SizeType SerializeBufferBase::getBuffLeft() const {
+    FW_ASSERT(this->m_serLoc >= this->m_deserLoc, static_cast<FwAssertArgType>(this->m_serLoc),
+              static_cast<FwAssertArgType>(this->m_deserLoc));
     return this->m_serLoc - this->m_deserLoc;
 }
 
@@ -765,11 +765,14 @@ ExternalSerializeBuffer::ExternalSerializeBuffer() {
 
 void ExternalSerializeBuffer::setExtBuffer(U8* buffPtr, Serializable::SizeType size) {
     FW_ASSERT(buffPtr != nullptr);
+    this->clear();
     this->m_buff = buffPtr;
     this->m_buffSize = size;
 }
 
 void ExternalSerializeBuffer::clear() {
+    this->resetSer();
+    this->resetDeser();
     this->m_buff = nullptr;
     this->m_buffSize = 0;
 }
diff --git a/Fw/Types/Serializable.hpp b/Fw/Types/Serializable.hpp
index f9ee4cd8f0..61513d5531 100644
--- a/Fw/Types/Serializable.hpp
+++ b/Fw/Types/Serializable.hpp
@@ -197,7 +197,6 @@ class SerializeBufferBase {
     // Copy constructor can be used only by the implementation
     SerializeBufferBase(const SerializeBufferBase& src);  //!< constructor with buffer as source
 
-
     void copyFrom(const SerializeBufferBase& src);  //!< copy data from source buffer
     Serializable::SizeType m_serLoc;                //!< current offset in buffer of serialized data
     Serializable::SizeType m_deserLoc;              //!< current offset for deserialization
@@ -208,10 +207,12 @@ class SerializeBufferBase {
 //! External serialize buffer with no copy semantics
 class ExternalSerializeBuffer : public SerializeBufferBase {
   public:
-    ExternalSerializeBuffer(U8* buffPtr, Serializable::SizeType size);     //!< construct with external buffer
-    ExternalSerializeBuffer();                                             //!< default constructor
-    ~ExternalSerializeBuffer() {}                                          //!< destructor
-    void setExtBuffer(U8* buffPtr, Serializable::SizeType size);           //!< Set the external buffer
+    ExternalSerializeBuffer(U8* buffPtr, Serializable::SizeType size);  //!< construct with external buffer
+    ExternalSerializeBuffer();                                          //!< default constructor
+    ~ExternalSerializeBuffer() {}                                       //!< destructor
+    //! Set the external buffer
+    //! This action also resets the serialization and deserialization pointers
+    void setExtBuffer(U8* buffPtr, Serializable::SizeType size);
     void clear();                                                          //!< clear external buffer
     ExternalSerializeBuffer(const ExternalSerializeBuffer& src) = delete;  //!< deleted copy constructor
 
@@ -262,8 +263,7 @@ class ExternalSerializeBufferWithMemberCopy final : public ExternalSerializeBuff
     ExternalSerializeBufferWithMemberCopy& operator=(const ExternalSerializeBufferWithMemberCopy& src) {
         // Ward against self-assignment
         if (this != &src) {
-            this->m_buff = src.m_buff;
-            this->m_buffSize = src.m_buffSize;
+            this->setExtBuffer(src.m_buff, src.m_buffSize);
         }
         return *this;
     }


Test patch:
diff --git a/FppTest/dp/test/ut/TestMain.cpp b/FppTest/dp/test/ut/TestMain.cpp
index 63f0dc9b46..2ab5ab49b1 100644
--- a/FppTest/dp/test/ut/TestMain.cpp
+++ b/FppTest/dp/test/ut/TestMain.cpp
@@ -8,94 +8,127 @@
 
 using namespace FppTest;
 
+// Iterate the tests to ensure that everything works after the first time
+static constexpr FwIndexType NUM_ITERS = 2;
+
 TEST(schedIn, OK) {
     COMMENT("schedIn OK");
     Tester tester;
-    tester.schedIn_OK();
+    for (FwIndexType i = 0; i < NUM_ITERS; i++) {
+        tester.schedIn_OK();
+    }
 }
 
 TEST(productRecvIn, Container1_SUCCESS) {
     COMMENT("Receive Container1 SUCCESS");
     Tester tester;
-    tester.productRecvIn_Container1_SUCCESS();
+    for (FwIndexType i = 0; i < NUM_ITERS; i++) {
+        tester.productRecvIn_Container1_SUCCESS();
+    }
 }
 
 TEST(productRecvIn, Container1_FAILURE) {
     COMMENT("Receive Container1 FAILURE");
     Tester tester;
-    tester.productRecvIn_Container1_FAILURE();
+    for (FwIndexType i = 0; i < NUM_ITERS; i++) {
+        tester.productRecvIn_Container1_FAILURE();
+    }
 }
 
 TEST(productRecvIn, Container2_SUCCESS) {
     COMMENT("Receive Container2 SUCCESS");
     Tester tester;
-    tester.productRecvIn_Container2_SUCCESS();
+    for (FwIndexType i = 0; i < NUM_ITERS; i++) {
+        tester.productRecvIn_Container2_SUCCESS();
+    }
 }
 
 TEST(productRecvIn, Container2_FAILURE) {
     COMMENT("Receive Container2 FAILURE");
     Tester tester;
-    tester.productRecvIn_Container2_FAILURE();
+    for (FwIndexType i = 0; i < NUM_ITERS; i++) {
+        tester.productRecvIn_Container2_FAILURE();
+    }
 }
 
 TEST(productRecvIn, Container3_SUCCESS) {
     COMMENT("Receive Container3 SUCCESS");
     Tester tester;
-    tester.productRecvIn_Container3_SUCCESS();
+    for (FwIndexType i = 0; i < NUM_ITERS; i++) {
+        tester.productRecvIn_Container3_SUCCESS();
+    }
 }
 
 TEST(productRecvIn, Container3_FAILURE) {
     COMMENT("Receive Container3 FAILURE");
     Tester tester;
-    tester.productRecvIn_Container3_FAILURE();
+    for (FwIndexType i = 0; i < NUM_ITERS; i++) {
+        tester.productRecvIn_Container3_FAILURE();
+    }
 }
 
 TEST(productRecvIn, Container4_SUCCESS) {
     COMMENT("Receive Container4 SUCCESS");
     Tester tester;
-    tester.productRecvIn_Container4_SUCCESS();
+    for (FwIndexType i = 0; i < NUM_ITERS; i++) {
+        tester.productRecvIn_Container4_SUCCESS();
+    }
 }
 
 TEST(productRecvIn, Container4_FAILURE) {
     COMMENT("Receive Container4 FAILURE");
     Tester tester;
-    tester.productRecvIn_Container4_FAILURE();
+    for (FwIndexType i = 0; i < NUM_ITERS; i++) {
+        tester.productRecvIn_Container4_FAILURE();
+    }
 }
 
 TEST(productRecvIn, Container5_SUCCESS) {
     COMMENT("Receive Container5 SUCCESS");
     Tester tester;
-    tester.productRecvIn_Container5_SUCCESS();
+    for (FwIndexType i = 0; i < NUM_ITERS; i++) {
+        tester.productRecvIn_Container5_SUCCESS();
+    }
 }
 
 TEST(productRecvIn, Container5_FAILURE) {
     COMMENT("Receive Container5 FAILURE");
     Tester tester;
-    tester.productRecvIn_Container5_FAILURE();
+    for (FwIndexType i = 0; i < NUM_ITERS; i++) {
+        tester.productRecvIn_Container5_FAILURE();
+    }
 }
 
 TEST(productRecvIn, Container6_SUCCESS) {
     COMMENT("Receive Container6 SUCCESS");
     Tester tester;
-    tester.productRecvIn_Container6_SUCCESS();
+    for (FwIndexType i = 0; i < NUM_ITERS; i++) {
+        tester.productRecvIn_Container6_SUCCESS();
+    }
 }
 
 TEST(productRecvIn, Container6_FAILURE) {
     COMMENT("Receive Container6 FAILURE");
     Tester tester;
-    tester.productRecvIn_Container6_FAILURE();
+    for (FwIndexType i = 0; i < NUM_ITERS; i++) {
+        tester.productRecvIn_Container6_FAILURE();
+    }
 }
 
 TEST(productRecvIn, Container7_SUCCESS) {
     COMMENT("Receive Container7 SUCCESS");
     Tester tester;
-    tester.productRecvIn_Container7_SUCCESS();
+    for (FwIndexType i = 0; i < NUM_ITERS; i++) {
+        tester.productRecvIn_Container7_SUCCESS();
+    }
 }
 
 TEST(productRecvIn, Container7_FAILURE) {
     COMMENT("Receive Container7 FAILURE");
     Tester tester;
-    tester.productRecvIn_Container7_FAILURE();
+    for (FwIndexType i = 0; i < NUM_ITERS; i++) {
+        tester.productRecvIn_Container7_FAILURE();
+    }
 }
 
 int main(int argc, char** argv) {
diff --git a/FppTest/dp/test/ut/Tester.cpp b/FppTest/dp/test/ut/Tester.cpp
index e12d70bb99..08d8f067de 100644
--- a/FppTest/dp/test/ut/Tester.cpp
+++ b/FppTest/dp/test/ut/Tester.cpp
@@ -63,6 +63,7 @@ Tester::~Tester() {}
 // ----------------------------------------------------------------------
 
 void Tester::schedIn_OK() {
+    this->clearHistory();
     this->invoke_to_schedIn(0, 0);
     this->component.doDispatch();
     ASSERT_PRODUCT_REQUEST_SIZE(6);
@@ -83,6 +84,8 @@ void Tester::schedIn_OK() {
 void Tester::productRecvIn_Container1_SUCCESS() {
     Fw::Buffer buffer;
     FwSizeType expectedNumElts;
+    // Clear the history
+    this->clearHistory();
     // Check the record size
     constexpr FwSizeType recordSize = DpTestComponentBase::SIZE_OF_U32Record_RECORD;
     ASSERT_EQ(recordSize, sizeof(FwDpIdType) + sizeof(U32));
@@ -107,12 +110,15 @@ void Tester::productRecvIn_Container1_SUCCESS() {
 }
 
 void Tester::productRecvIn_Container1_FAILURE() {
+    this->clearHistory();
     productRecvIn_CheckFailure(DpTest::ContainerId::Container1, this->container1Buffer);
 }
 
 void Tester::productRecvIn_Container2_SUCCESS() {
     Fw::Buffer buffer;
     FwSizeType expectedNumElts;
+    // Clear the history
+    this->clearHistory();
     // Check the record size
     constexpr FwSizeType recordSize = DpTestComponentBase::SIZE_OF_DataRecord_RECORD;
     ASSERT_EQ(recordSize, sizeof(FwDpIdType) + DpTest_Data::SERIALIZED_SIZE);
@@ -137,12 +143,15 @@ void Tester::productRecvIn_Container2_SUCCESS() {
 }
 
 void Tester::productRecvIn_Container2_FAILURE() {
+    this->clearHistory();
     productRecvIn_CheckFailure(DpTest::ContainerId::Container2, this->container2Buffer);
 }
 
 void Tester::productRecvIn_Container3_SUCCESS() {
     Fw::Buffer buffer;
     FwSizeType expectedNumElts;
+    // Clear the history
+    this->clearHistory();
     // Compute the data element size
     const FwSizeType arraySize = this->u8ArrayRecordData.size();
     const FwSizeType dataEltSize = sizeof(FwSizeStoreType) + arraySize;
@@ -178,12 +187,15 @@ void Tester::productRecvIn_Container3_SUCCESS() {
 }
 
 void Tester::productRecvIn_Container3_FAILURE() {
+    this->clearHistory();
     productRecvIn_CheckFailure(DpTest::ContainerId::Container3, this->container3Buffer);
 }
 
 void Tester::productRecvIn_Container4_SUCCESS() {
     Fw::Buffer buffer;
     FwSizeType expectedNumElts;
+    // Clear the history
+    this->clearHistory();
     // Compute the data element size
     const FwSizeType arraySize = this->u32ArrayRecordData.size();
     const FwSizeType dataEltSize = sizeof(FwSizeStoreType) + arraySize * sizeof(U32);
@@ -220,11 +232,14 @@ void Tester::productRecvIn_Container4_SUCCESS() {
 
 void Tester::productRecvIn_Container4_FAILURE() {
     productRecvIn_CheckFailure(DpTest::ContainerId::Container4, this->container4Buffer);
+    this->clearHistory();
 }
 
 void Tester::productRecvIn_Container5_SUCCESS() {
     Fw::Buffer buffer;
     FwSizeType expectedNumElts;
+    // Clear the history
+    this->clearHistory();
     // Compute the data element size
     const FwSizeType arraySize = this->dataArrayRecordData.size();
     const FwSizeType dataEltSize = sizeof(FwSizeStoreType) + arraySize * DpTest_Data::SERIALIZED_SIZE;
@@ -260,12 +275,15 @@ void Tester::productRecvIn_Container5_SUCCESS() {
 }
 
 void Tester::productRecvIn_Container5_FAILURE() {
+    this->clearHistory();
     productRecvIn_CheckFailure(DpTest::ContainerId::Container5, this->container5Buffer);
 }
 
 void Tester::productRecvIn_Container6_SUCCESS() {
     Fw::Buffer buffer;
     FwSizeType expectedNumElts;
+    // Clear the history
+    this->clearHistory();
     // Check the record size
     constexpr FwSizeType recordSize = DpTestComponentBase::SIZE_OF_StringRecord_RECORD;
     ASSERT_EQ(recordSize, sizeof(FwDpIdType) + Fw::StringBase::STATIC_SERIALIZED_SIZE(DpTest_stringSize));
@@ -293,12 +311,15 @@ void Tester::productRecvIn_Container6_SUCCESS() {
 }
 
 void Tester::productRecvIn_Container6_FAILURE() {
+    this->clearHistory();
     productRecvIn_CheckFailure(DpTest::ContainerId::Container6, this->container6Buffer);
 }
 
 void Tester::productRecvIn_Container7_SUCCESS() {
     Fw::Buffer buffer;
     FwSizeType expectedNumElts;
+    // Clear the history
+    this->clearHistory();
     // Check the record size
     const FwSizeType arraySize = DpTest::STRING_ARRAY_RECORD_ARRAY_SIZE;
     const FwSizeType recordSize = DpTestComponentBase::SIZE_OF_StringArrayRecord_RECORD(arraySize);
@@ -337,6 +358,7 @@ void Tester::productRecvIn_Container7_SUCCESS() {
 }
 
 void Tester::productRecvIn_Container7_FAILURE() {
+    this->clearHistory();
     productRecvIn_CheckFailure(DpTest::ContainerId::Container7, this->container7Buffer);
 }
 
diff --git a/Fw/Dp/test/ut/TestMain.cpp b/Fw/Dp/test/ut/TestMain.cpp
index 90a7d8b058..5c04d2d356 100644
--- a/Fw/Dp/test/ut/TestMain.cpp
+++ b/Fw/Dp/test/ut/TestMain.cpp
@@ -155,6 +155,8 @@ TEST(Header, BufferSet) {
     container.invalidateBuffer();
     // Check that the buffer is invalid
     ASSERT_EQ(container.getBuffer(), Fw::Buffer());
+    // Check that the data size is zero
+    ASSERT_EQ(container.getDataSize(), 0);
 }
 
 TEST(Header, BadPacketDescriptor) {
diff --git a/Fw/Types/test/ut/ExternalSerializeBufferTest.cpp b/Fw/Types/test/ut/ExternalSerializeBufferTest.cpp
new file mode 100644
index 0000000000..01d472cd17
--- /dev/null
+++ b/Fw/Types/test/ut/ExternalSerializeBufferTest.cpp
@@ -0,0 +1,100 @@
+#include <FpConfig.hpp>
+#include <gtest/gtest.h>
+
+#include "Fw/Types/Serializable.hpp"
+
+namespace ExternalSerializeBufferTest {
+
+  using SizeType = Fw::Serializable::SizeType;
+
+  constexpr SizeType BUFFER_SIZE = 10;
+
+  U8 buffer[BUFFER_SIZE];
+
+  void serializeOK(Fw::ExternalSerializeBuffer& esb) {
+    const SizeType buffCapacity = esb.getBuffCapacity();
+    ASSERT_EQ(esb.getBuffLength(), 0);
+    for (SizeType i = 0; i < buffCapacity; i++) {
+      const U8 value = static_cast<U8>(i);
+      const Fw::SerializeStatus status = esb.serialize(value);
+      ASSERT_EQ(status, Fw::FW_SERIALIZE_OK);
+    }
+    ASSERT_EQ(esb.getBuffLength(), buffCapacity);
+  }
+
+  void serializeFail(Fw::ExternalSerializeBuffer& esb) {
+    ASSERT_EQ(esb.getBuffLength(), esb.getBuffCapacity());
+    const Fw::SerializeStatus status = esb.serialize(static_cast<U8>(0));
+    ASSERT_EQ(status, Fw::FW_SERIALIZE_NO_ROOM_LEFT);
+  }
+
+  void deserializeOK(Fw::ExternalSerializeBuffer& esb) {
+    const SizeType buffCapacity = esb.getBuffCapacity();
+    ASSERT_EQ(esb.getBuffLeft(), buffCapacity);
+    for (SizeType i = 0; i < buffCapacity; i++) {
+      U8 value = 0;
+      const Fw::SerializeStatus status = esb.deserialize(value);
+      ASSERT_EQ(status, Fw::FW_SERIALIZE_OK);
+      ASSERT_EQ(value, static_cast<U8>(i));
+    }
+    ASSERT_EQ(esb.getBuffLeft(), 0);
+  }
+
+  void deserializeFail(Fw::ExternalSerializeBuffer& esb) {
+    U8 value = 0;
+    ASSERT_EQ(esb.getBuffLeft(), 0);
+    const Fw::SerializeStatus status = esb.deserialize(value);
+    ASSERT_EQ(status, Fw::FW_DESERIALIZE_BUFFER_EMPTY);
+  }
+
+  TEST(ExternalSerializeBuffer, Basic) {
+    Fw::ExternalSerializeBuffer esb(buffer, BUFFER_SIZE);
+    // Serialization should succeed
+    serializeOK(esb);
+    // Serialization should fail
+    serializeFail(esb);
+    // Deserialization should succeed
+    deserializeOK(esb);
+    // Deserialization should fail
+    deserializeFail(esb);
+  }
+
+  TEST(ExternalSerializeBuffer, Clear) {
+    Fw::ExternalSerializeBuffer esb(buffer, BUFFER_SIZE);
+    // Serialization should succeed
+    serializeOK(esb);
+    // Clear the buffer
+    esb.clear();
+    // Serialization should fail
+    serializeFail(esb);
+    // Deserialization should fail
+    deserializeFail(esb);
+  }
+
+  TEST(ExternalSerializeBuffer, SetExtBuffer) {
+    Fw::ExternalSerializeBuffer esb(buffer, BUFFER_SIZE);
+    // Serialization should succeed
+    serializeOK(esb);
+    // Set the buffer
+    // This should also clear the serialization state
+    esb.setExtBuffer(buffer, BUFFER_SIZE);
+    // Serialization should succeed
+    serializeOK(esb);
+    // Deserialization should succeed
+    deserializeOK(esb);
+  }
+
+  TEST(ExternalSerializeBufferWithMemberCopy, Assign) {
+    Fw::ExternalSerializeBufferWithMemberCopy esb1(buffer, BUFFER_SIZE);
+    Fw::ExternalSerializeBufferWithMemberCopy esb2(buffer, BUFFER_SIZE);
+    // Serialization should succeed
+    serializeOK(esb1);
+    // Assign esb2 to esb1
+    esb1 = esb2;
+    // Deserialization should fail
+    deserializeFail(esb1);
+    // Serialization should succeed
+    serializeOK(esb1);
+  }
+
+}