rde_handler: Check multiPartReceiveResp over-read

It seems the handler had vulnerabilities to buffer over-reads found by
the fuzzer. Fix them and add unit test coverage.

Tested: Unit test

Signed-off-by: Brandon Kim <brandonkim@google.com>
Change-Id: Ic217a7a9be39c0873d10f89fe68b559e5d3cfadb
diff --git a/include/rde/rde_handler.hpp b/include/rde/rde_handler.hpp
index 019b43a..b31e26c 100644
--- a/include/rde/rde_handler.hpp
+++ b/include/rde/rde_handler.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
 #include "external_storer_interface.hpp"
+// For local unit testing, remove "libbej/" prefix below
 #include "libbej/bej_decoder_json.hpp"
 #include "rde_dictionary_manager.hpp"
 
diff --git a/src/rde/rde_handler.cpp b/src/rde/rde_handler.cpp
index 49641c8..6f6acfb 100644
--- a/src/rde/rde_handler.cpp
+++ b/src/rde/rde_handler.cpp
@@ -20,7 +20,7 @@
 RdeCommandHandler::RdeCommandHandler(
     std::unique_ptr<ExternalStorerInterface> exStorer) :
     flagState(RdeDictTransferFlagState::RdeStateIdle),
-    exStorer(std::move(exStorer))
+    exStorer(std::move(exStorer)), prevDictResourceId(0), crc(0xFFFFFFFF)
 {
     // Initialize CRC table.
     calcCrcTable();
@@ -122,9 +122,25 @@
 RdeDecodeStatus RdeCommandHandler::multiPartReceiveResp(
     std::span<const uint8_t> rdeCommand)
 {
+    if (rdeCommand.size() < sizeof(MultipartReceiveResHeader))
+    {
+        stdplus::print(
+            stderr, "RDE command is smaller than the expected header size.\n");
+        return RdeDecodeStatus::RdeInvalidCommand;
+    }
+
     const MultipartReceiveResHeader* header =
         reinterpret_cast<const MultipartReceiveResHeader*>(rdeCommand.data());
 
+    if (rdeCommand.size() <
+        sizeof(MultipartReceiveResHeader) + header->dataLengthBytes)
+    {
+        stdplus::print(
+            stderr,
+            "RDE command size is smaller than header + declared payload size.\n");
+        return RdeDecodeStatus::RdeInvalidCommand;
+    }
+
     // This is a hack to get the resource ID for the dictionary data. Even
     // though nextDataTransferHandle field is supposed to be used for something
     // else, BIOS is using it to specify the resource ID corresponding to the
diff --git a/test/rde_handler_test.cpp b/test/rde_handler_test.cpp
index ef0574d..9240102 100644
--- a/test/rde_handler_test.cpp
+++ b/test/rde_handler_test.cpp
@@ -14,8 +14,537 @@
 namespace rde
 {
 
+using ::testing::_;
+using ::testing::NiceMock;
 using ::testing::Return;
 
+// Mock for ExternalStorerInterface
+class MockExternalStorerInterface : public ExternalStorerInterface
+{
+  public:
+    MOCK_METHOD(bool, publishJson, (std::string_view jsonStr), (override));
+};
+
+// Test fixture for RdeCommandHandler
+class RdeCommandHandlerTest : public ::testing::Test
+{
+  protected:
+    std::unique_ptr<MockExternalStorerInterface> mockExStorerInstance;
+    // Note: RdeCommandHandler takes ownership of the raw pointer.
+    // We pass mockExStorerInstance.get() and it's moved into the handler.
+    // For safety in test setup, we'll create the handler with a moved
+    // unique_ptr.
+    std::unique_ptr<RdeCommandHandler> handler;
+
+    void SetUp() override
+    {
+        auto exStorer =
+            std::make_unique<NiceMock<MockExternalStorerInterface>>();
+        // Keep a raw pointer for EXPECT_CALL, but handler owns the unique_ptr
+        mockExStorer = exStorer.get();
+        handler = std::make_unique<RdeCommandHandler>(std::move(exStorer));
+    }
+
+    // Helper to create RdeOperationInitReqHeader and its command data
+    std::vector<uint8_t> createOpInitReqCmd(
+        bool containsPayload, uint8_t opType, uint8_t sendDataTransferHandle,
+        uint32_t resourceID, uint8_t opLocatorLength,
+        uint16_t requestPayloadLength,
+        const std::vector<uint8_t>& payloadData = {})
+    {
+        RdeOperationInitReqHeader header{};
+        header.containsRequestPayload = containsPayload;
+        header.operationType = opType;
+        header.sendDataTransferHandle = sendDataTransferHandle;
+        header.resourceID = resourceID;
+        header.operationLocatorLength = opLocatorLength;
+        header.requestPayloadLength = requestPayloadLength;
+
+        std::vector<uint8_t> command(sizeof(header));
+        memcpy(command.data(), &header, sizeof(header));
+        command.insert(command.end(), payloadData.begin(), payloadData.end());
+        return command;
+    }
+
+    // Helper to create MultipartReceiveResHeader and its command data
+    std::vector<uint8_t> createMultiPartRespCmd(
+        uint8_t transferFlag, uint32_t nextDataTransferHandleAsResourceId,
+        uint16_t dataLength, const std::vector<uint8_t>& payloadData,
+        const std::optional<uint32_t>& checksum = std::nullopt)
+    {
+        MultipartReceiveResHeader header{};
+        header.transferFlag = transferFlag;
+        header.nextDataTransferHandle = nextDataTransferHandleAsResourceId;
+        header.dataLengthBytes = dataLength;
+
+        std::vector<uint8_t> command(sizeof(header));
+        memcpy(command.data(), &header, sizeof(header));
+        command.insert(command.end(), payloadData.begin(), payloadData.end());
+
+        if (checksum)
+        {
+            uint32_t csVal = *checksum;
+            command.push_back(static_cast<uint8_t>(csVal & 0xFF));
+            command.push_back(static_cast<uint8_t>((csVal >> 8) & 0xFF));
+            command.push_back(static_cast<uint8_t>((csVal >> 16) & 0xFF));
+            command.push_back(static_cast<uint8_t>((csVal >> 24) & 0xFF));
+        }
+
+        return command;
+    }
+
+    // To be used by EXPECT_CALL
+    MockExternalStorerInterface* mockExStorer;
+};
+
+TEST_F(RdeCommandHandlerTest, DecodeRdeCommand_InvalidType)
+{
+    std::vector<uint8_t> cmdData = {0x01, 0x02};
+    auto status =
+        handler->decodeRdeCommand(cmdData, static_cast<RdeCommandType>(0xFF));
+    EXPECT_EQ(status, RdeDecodeStatus::RdeInvalidCommand);
+}
+
+TEST_F(RdeCommandHandlerTest, GetDictionaryCount_Initial)
+{
+    EXPECT_EQ(handler->getDictionaryCount(), 0);
+}
+
+TEST_F(RdeCommandHandlerTest, OperationInitRequest_NoPayload)
+{
+    auto cmd = createOpInitReqCmd(
+        false, // containsRequestPayload
+        static_cast<uint8_t>(RdeOperationInitType::RdeOpInitOperationUpdate), 0,
+        1, 0, 0);
+    auto status =
+        handler->decodeRdeCommand(cmd, RdeCommandType::RdeOperationInitRequest);
+    EXPECT_EQ(status, RdeDecodeStatus::RdeOk);
+}
+
+TEST_F(RdeCommandHandlerTest, OperationInitRequest_UnsupportedOperationType)
+{
+    auto cmd = createOpInitReqCmd(true, 0xFE, 0, 1, 0, 5, {1, 2, 3, 4, 5});
+    auto status =
+        handler->decodeRdeCommand(cmd, RdeCommandType::RdeOperationInitRequest);
+    EXPECT_EQ(status, RdeDecodeStatus::RdeUnsupportedOperation);
+}
+
+TEST_F(RdeCommandHandlerTest, OperationInitRequest_PayloadOverflowNotSupported)
+{
+    auto cmd = createOpInitReqCmd(
+        true,
+        static_cast<uint8_t>(RdeOperationInitType::RdeOpInitOperationUpdate), 1,
+        1, 0, 5, {1, 2, 3, 4, 5}); // sendDataTransferHandle != 0
+    auto status =
+        handler->decodeRdeCommand(cmd, RdeCommandType::RdeOperationInitRequest);
+    EXPECT_EQ(status, RdeDecodeStatus::RdePayloadOverflow);
+}
+
+TEST_F(RdeCommandHandlerTest, OperationInitRequest_SchemaDictionaryNotFound)
+{
+    std::vector<uint8_t> locatorAndPayload = {0x00}; // Minimal locator
+    auto cmd = createOpInitReqCmd(
+        true,
+        static_cast<uint8_t>(RdeOperationInitType::RdeOpInitOperationUpdate), 0,
+        123, 1, 0, locatorAndPayload); // resourceID 123, opLocatorLength=1,
+                                       // payloadLength=0
+    auto status =
+        handler->decodeRdeCommand(cmd, RdeCommandType::RdeOperationInitRequest);
+    EXPECT_EQ(status, RdeDecodeStatus::RdeNoDictionary);
+}
+
+TEST_F(RdeCommandHandlerTest, OperationInitRequest_AnnotationDictionaryNotFound)
+{
+    uint32_t schemaResourceId = 1;
+    std::vector<uint8_t> schemaDictData = {'s', 'c', 'h', 'e', 'm', 'a'};
+    uint32_t schemaChecksum = 0xb88e4152; // CRC32("schema")
+    auto cmdSchema = createMultiPartRespCmd(
+        static_cast<uint8_t>(
+            RdeMultiReceiveTransferFlag::RdeMRecFlagStartAndEnd),
+        schemaResourceId, schemaDictData.size(), schemaDictData,
+        schemaChecksum);
+    ASSERT_EQ(handler->decodeRdeCommand(
+                  cmdSchema, RdeCommandType::RdeMultiPartReceiveResponse),
+              RdeDecodeStatus::RdeStopFlagReceived);
+
+    std::vector<uint8_t> locatorAndPayload = {0x00};
+    auto cmdOpInit = createOpInitReqCmd(
+        true,
+        static_cast<uint8_t>(RdeOperationInitType::RdeOpInitOperationUpdate), 0,
+        schemaResourceId, 1, 0, locatorAndPayload);
+    auto status = handler->decodeRdeCommand(
+        cmdOpInit, RdeCommandType::RdeOperationInitRequest);
+    EXPECT_EQ(status, RdeDecodeStatus::RdeNoDictionary);
+}
+
+TEST_F(RdeCommandHandlerTest, OperationInitRequest_BejDecodingError)
+{
+    // Add dummy schema dictionary
+    uint32_t schemaResourceId = 1;
+    std::vector<uint8_t> schemaDictData = {'s', 'c', 'h', 'e', 'm', 'a'};
+    uint32_t schemaChecksum = 0xb88e4152; // CRC32("schema")
+    auto cmdSchema = createMultiPartRespCmd(
+        static_cast<uint8_t>(
+            RdeMultiReceiveTransferFlag::RdeMRecFlagStartAndEnd),
+        schemaResourceId, schemaDictData.size(), schemaDictData,
+        schemaChecksum);
+    ASSERT_EQ(handler->decodeRdeCommand(
+                  cmdSchema, RdeCommandType::RdeMultiPartReceiveResponse),
+              RdeDecodeStatus::RdeStopFlagReceived);
+
+    // Add dummy annotation dictionary
+    uint32_t annotationResourceId =
+        0; // DictionaryManager::annotationResourceId
+    std::vector<uint8_t> annotationDictData = {'a', 'n', 'n', 'o'};
+    uint32_t annotationChecksum = 0xc6e493b0; // CRC32("anno")
+    auto cmdAnnotation = createMultiPartRespCmd(
+        static_cast<uint8_t>(
+            RdeMultiReceiveTransferFlag::RdeMRecFlagStartAndEnd),
+        annotationResourceId, annotationDictData.size(), annotationDictData,
+        annotationChecksum);
+    ASSERT_EQ(handler->decodeRdeCommand(
+                  cmdAnnotation, RdeCommandType::RdeMultiPartReceiveResponse),
+              RdeDecodeStatus::RdeStopFlagReceived);
+
+    std::vector<uint8_t> locator = {0x00};
+    std::vector<uint8_t> bejPayload = {0x01, 0x02}; // Dummy BEJ payload
+    std::vector<uint8_t> opInitFullPayload = locator;
+    opInitFullPayload.insert(opInitFullPayload.end(), bejPayload.begin(),
+                             bejPayload.end());
+
+    auto cmdOpInit = createOpInitReqCmd(
+        true,
+        static_cast<uint8_t>(RdeOperationInitType::RdeOpInitOperationUpdate), 0,
+        schemaResourceId, locator.size(), bejPayload.size(), opInitFullPayload);
+
+    // Expect BEJ decoding to fail with invalid dictionaries
+    EXPECT_CALL(*mockExStorer, publishJson(_)).Times(0);
+    auto status = handler->decodeRdeCommand(
+        cmdOpInit, RdeCommandType::RdeOperationInitRequest);
+    EXPECT_EQ(status, RdeDecodeStatus::RdeBejDecodingError);
+}
+
+TEST_F(RdeCommandHandlerTest, OperationInitRequest_ExternalStorerPublishFails)
+{
+    // This test requires BejDecoder to succeed. Since we can't easily mock
+    // BejDecoder or provide universally valid simple BEJ dicts/payloads that
+    // guarantee success for the internal BejDecoder, this specific path is hard
+    // to test in isolation. We would need a known schema, annotation, and
+    // payload that successfully decodes.
+    GTEST_SKIP()
+        << "Skipping due to complexity of ensuring BEJ decode success without mock or valid complex BEJ data.";
+}
+
+TEST_F(RdeCommandHandlerTest, MultiPartReceiveResp_CmdTooSmallForHeader)
+{
+    std::vector<uint8_t> cmdData = {0x01};
+    auto status = handler->decodeRdeCommand(
+        cmdData, RdeCommandType::RdeMultiPartReceiveResponse);
+    EXPECT_EQ(status, RdeDecodeStatus::RdeInvalidCommand);
+}
+
+TEST_F(RdeCommandHandlerTest,
+       MultiPartReceiveResp_CmdTooSmallForDeclaredPayload)
+{
+    MultipartReceiveResHeader header{};
+    header.transferFlag =
+        static_cast<uint8_t>(RdeMultiReceiveTransferFlag::RdeMRecFlagStart);
+    header.nextDataTransferHandle = 1;
+    header.dataLengthBytes = 10; // Expects 10 bytes
+
+    std::vector<uint8_t> cmdData(sizeof(header));
+    memcpy(cmdData.data(), &header, sizeof(header));
+    cmdData.push_back(0xAA); // Only 1 byte of payload provided
+
+    auto status = handler->decodeRdeCommand(
+        cmdData, RdeCommandType::RdeMultiPartReceiveResponse);
+    EXPECT_EQ(status, RdeDecodeStatus::RdeInvalidCommand);
+}
+
+TEST_F(RdeCommandHandlerTest, MultiPartReceiveResp_InvalidTransferFlag)
+{
+    std::vector<uint8_t> payload = {'d', 'a', 't', 'a'};
+    auto cmd = createMultiPartRespCmd(0xFF, 1, payload.size(),
+                                      payload); // Invalid flag
+    auto status = handler->decodeRdeCommand(
+        cmd, RdeCommandType::RdeMultiPartReceiveResponse);
+    EXPECT_EQ(status, RdeDecodeStatus::RdeInvalidCommand);
+}
+
+TEST_F(RdeCommandHandlerTest, MultiPartReceiveResp_FlagStart)
+{
+    uint32_t resourceId = 1;
+    std::vector<uint8_t> dataPayload = {'s', 't', 'a', 'r', 't'};
+    auto cmd = createMultiPartRespCmd(
+        static_cast<uint8_t>(RdeMultiReceiveTransferFlag::RdeMRecFlagStart),
+        resourceId, dataPayload.size(), dataPayload);
+    auto status = handler->decodeRdeCommand(
+        cmd, RdeCommandType::RdeMultiPartReceiveResponse);
+    EXPECT_EQ(status, RdeDecodeStatus::RdeOk);
+    EXPECT_EQ(handler->getDictionaryCount(), 0); // Not yet complete
+}
+
+TEST_F(RdeCommandHandlerTest, MultiPartReceiveResp_FlagMiddle_InvalidOrder)
+{
+    uint32_t resourceId = 1;
+    std::vector<uint8_t> dataPayload = {'m', 'i', 'd', 'd', 'l', 'e'};
+    auto cmd = createMultiPartRespCmd(
+        static_cast<uint8_t>(RdeMultiReceiveTransferFlag::RdeMRecFlagMiddle),
+        resourceId, dataPayload.size(), dataPayload);
+    auto status = handler->decodeRdeCommand(
+        cmd, RdeCommandType::RdeMultiPartReceiveResponse);
+    EXPECT_EQ(status, RdeDecodeStatus::RdeInvalidPktOrder);
+}
+
+TEST_F(RdeCommandHandlerTest,
+       MultiPartReceiveResp_FlagMiddle_AfterStart_SameResource)
+{
+    uint32_t resourceId = 1;
+    std::vector<uint8_t> startPayload = {'s', 't', 'a', 'r', 't'};
+    auto cmdStart = createMultiPartRespCmd(
+        static_cast<uint8_t>(RdeMultiReceiveTransferFlag::RdeMRecFlagStart),
+        resourceId, startPayload.size(), startPayload);
+    ASSERT_EQ(handler->decodeRdeCommand(
+                  cmdStart, RdeCommandType::RdeMultiPartReceiveResponse),
+              RdeDecodeStatus::RdeOk);
+
+    std::vector<uint8_t> middlePayload = {'m', 'i', 'd', 'd', 'l', 'e'};
+    auto cmdMiddle = createMultiPartRespCmd(
+        static_cast<uint8_t>(RdeMultiReceiveTransferFlag::RdeMRecFlagMiddle),
+        resourceId, middlePayload.size(), middlePayload);
+    auto status = handler->decodeRdeCommand(
+        cmdMiddle, RdeCommandType::RdeMultiPartReceiveResponse);
+    EXPECT_EQ(status, RdeDecodeStatus::RdeOk);
+    EXPECT_EQ(handler->getDictionaryCount(), 0);
+}
+
+TEST_F(RdeCommandHandlerTest,
+       MultiPartReceiveResp_FlagMiddle_AfterStart_NewResource)
+{
+    // Tests current behavior: if Middle flag comes for a new resource,
+    // previous resource is marked complete, new one is started. CRC continues.
+    uint32_t resourceId1 = 1;
+    std::vector<uint8_t> startPayload1 = {'r', '1', 's'};
+    auto cmdStart1 = createMultiPartRespCmd(
+        static_cast<uint8_t>(RdeMultiReceiveTransferFlag::RdeMRecFlagStart),
+        resourceId1, startPayload1.size(), startPayload1);
+    ASSERT_EQ(handler->decodeRdeCommand(
+                  cmdStart1, RdeCommandType::RdeMultiPartReceiveResponse),
+              RdeDecodeStatus::RdeOk);
+
+    uint32_t resourceId2 = 2;
+    std::vector<uint8_t> middlePayload2 = {'r', '2', 'm'};
+    auto cmdMiddle2 = createMultiPartRespCmd(
+        static_cast<uint8_t>(RdeMultiReceiveTransferFlag::RdeMRecFlagMiddle),
+        resourceId2, middlePayload2.size(), middlePayload2);
+    auto status = handler->decodeRdeCommand(
+        cmdMiddle2, RdeCommandType::RdeMultiPartReceiveResponse);
+    EXPECT_EQ(status, RdeDecodeStatus::RdeOk);
+    EXPECT_EQ(handler->getDictionaryCount(), 1); // Resource 1 completed
+}
+
+TEST_F(RdeCommandHandlerTest, MultiPartReceiveResp_FlagEnd_InvalidOrder)
+{
+    uint32_t resourceId = 1;
+    std::vector<uint8_t> dataPayload = {'e', 'n', 'd'};
+    uint32_t checksum = 0xfc33b1; // CRC32("end")
+    auto cmd = createMultiPartRespCmd(
+        static_cast<uint8_t>(RdeMultiReceiveTransferFlag::RdeMRecFlagEnd),
+        resourceId, dataPayload.size(), dataPayload, checksum);
+    auto status = handler->decodeRdeCommand(
+        cmd, RdeCommandType::RdeMultiPartReceiveResponse);
+    EXPECT_EQ(status, RdeDecodeStatus::RdeInvalidPktOrder);
+}
+
+TEST_F(RdeCommandHandlerTest,
+       MultiPartReceiveResp_FlagEnd_AfterStart_SameResource_ValidChecksum)
+{
+    uint32_t resourceId = 1;
+    std::vector<uint8_t> startPayload = {'s', 't', 'a', 'r', 't'};
+    auto cmdStart = createMultiPartRespCmd(
+        static_cast<uint8_t>(RdeMultiReceiveTransferFlag::RdeMRecFlagStart),
+        resourceId, startPayload.size(), startPayload);
+    ASSERT_EQ(handler->decodeRdeCommand(
+                  cmdStart, RdeCommandType::RdeMultiPartReceiveResponse),
+              RdeDecodeStatus::RdeOk);
+
+    std::vector<uint8_t> endPayload = {'e', 'n', 'd'};
+    uint32_t checksum = 0x4800f1a; // CRC32("startend")
+    auto cmdEnd = createMultiPartRespCmd(
+        static_cast<uint8_t>(RdeMultiReceiveTransferFlag::RdeMRecFlagEnd),
+        resourceId, endPayload.size(), endPayload, checksum);
+    auto status = handler->decodeRdeCommand(
+        cmdEnd, RdeCommandType::RdeMultiPartReceiveResponse);
+    EXPECT_EQ(status, RdeDecodeStatus::RdeStopFlagReceived);
+    EXPECT_EQ(handler->getDictionaryCount(), 1);
+}
+
+TEST_F(RdeCommandHandlerTest,
+       MultiPartReceiveResp_FlagEnd_AfterStart_SameResource_InvalidChecksum)
+{
+    uint32_t resourceId = 1;
+    std::vector<uint8_t> startPayload = {'s', 't', 'a', 'r', 't'};
+    auto cmdStart = createMultiPartRespCmd(
+        static_cast<uint8_t>(RdeMultiReceiveTransferFlag::RdeMRecFlagStart),
+        resourceId, startPayload.size(), startPayload);
+    ASSERT_EQ(handler->decodeRdeCommand(
+                  cmdStart, RdeCommandType::RdeMultiPartReceiveResponse),
+              RdeDecodeStatus::RdeOk);
+
+    std::vector<uint8_t> endPayload = {'e', 'n', 'd'};
+    uint32_t invalidChecksum = 0x12345678; // Invalid checksum
+    auto cmdEnd = createMultiPartRespCmd(
+        static_cast<uint8_t>(RdeMultiReceiveTransferFlag::RdeMRecFlagEnd),
+        resourceId, endPayload.size(), endPayload, invalidChecksum);
+    auto status = handler->decodeRdeCommand(
+        cmdEnd, RdeCommandType::RdeMultiPartReceiveResponse);
+    EXPECT_EQ(status, RdeDecodeStatus::RdeInvalidChecksum);
+    EXPECT_EQ(handler->getDictionaryCount(), 0); // Dictionaries invalidated
+}
+
+TEST_F(RdeCommandHandlerTest,
+       MultiPartReceiveResp_FlagEnd_AfterStart_NewResource_UsesPrevCrcState)
+{
+    // This test verifies that if an End flag for a new resource follows a Start
+    // flag for a different resource, the CRC calculation for the new resource's
+    // data incorrectly continues from the previous resource's CRC state.
+    uint32_t resourceId1 = 1;
+    std::vector<uint8_t> startPayload1 = {'r', '1', 's'}; // CRC for "r1s"
+    auto cmdStart1 = createMultiPartRespCmd(
+        static_cast<uint8_t>(RdeMultiReceiveTransferFlag::RdeMRecFlagStart),
+        resourceId1, startPayload1.size(), startPayload1);
+    ASSERT_EQ(handler->decodeRdeCommand(
+                  cmdStart1, RdeCommandType::RdeMultiPartReceiveResponse),
+              RdeDecodeStatus::RdeOk);
+
+    uint32_t resourceId2 = 2;
+    std::vector<uint8_t> endPayload2 = {'r', '2', 'e'};
+    // Checksum for "r2e" ALONE is 0x789ca48a.
+    // If CRC continued from "r1s", this checksum will be wrong.
+    uint32_t checksumForR2eAlone = 0x789ca48a;
+    auto cmdEnd2 = createMultiPartRespCmd(
+        static_cast<uint8_t>(RdeMultiReceiveTransferFlag::RdeMRecFlagEnd),
+        resourceId2, endPayload2.size(), endPayload2, checksumForR2eAlone);
+
+    auto status = handler->decodeRdeCommand(
+        cmdEnd2, RdeCommandType::RdeMultiPartReceiveResponse);
+    // Expect InvalidChecksum because internal CRC is for "r1s" + "r2e"
+    EXPECT_EQ(status, RdeDecodeStatus::RdeInvalidChecksum);
+    EXPECT_EQ(handler->getDictionaryCount(), 0); // Dictionaries invalidated
+}
+
+TEST_F(RdeCommandHandlerTest,
+       MultiPartReceiveResp_FlagStartAndEnd_ValidChecksum)
+{
+    uint32_t resourceId = 1;
+    std::vector<uint8_t> dataPayload = {'c', 'o', 'm', 'p', 'l', 'e', 't', 'e'};
+    uint32_t checksum = 0x4267d023; // CRC32("complete")
+    auto cmd = createMultiPartRespCmd(
+        static_cast<uint8_t>(
+            RdeMultiReceiveTransferFlag::RdeMRecFlagStartAndEnd),
+        resourceId, dataPayload.size(), dataPayload, checksum);
+    auto status = handler->decodeRdeCommand(
+        cmd, RdeCommandType::RdeMultiPartReceiveResponse);
+    EXPECT_EQ(status, RdeDecodeStatus::RdeStopFlagReceived);
+    EXPECT_EQ(handler->getDictionaryCount(), 1);
+}
+
+TEST_F(RdeCommandHandlerTest,
+       MultiPartReceiveResp_FlagStartAndEnd_InvalidChecksum)
+{
+    uint32_t resourceId = 1;
+    std::vector<uint8_t> dataPayload = {'c', 'o', 'm', 'p', 'l', 'e', 't', 'e'};
+    uint32_t invalidChecksum = 0x12345678; // Invalid checksum
+    auto cmd = createMultiPartRespCmd(
+        static_cast<uint8_t>(
+            RdeMultiReceiveTransferFlag::RdeMRecFlagStartAndEnd),
+        resourceId, dataPayload.size(), dataPayload, invalidChecksum);
+    auto status = handler->decodeRdeCommand(
+        cmd, RdeCommandType::RdeMultiPartReceiveResponse);
+    EXPECT_EQ(status, RdeDecodeStatus::RdeInvalidChecksum);
+    EXPECT_EQ(handler->getDictionaryCount(), 0);
+}
+
+TEST_F(RdeCommandHandlerTest,
+       MultiPartReceiveResp_Sequence_StartMiddleEnd_Valid)
+{
+    uint32_t resourceId = 42;
+
+    std::vector<uint8_t> startPayload = {'p', 'a', 'r', 't', '1'};
+    auto cmdStart = createMultiPartRespCmd(
+        static_cast<uint8_t>(RdeMultiReceiveTransferFlag::RdeMRecFlagStart),
+        resourceId, startPayload.size(), startPayload);
+    ASSERT_EQ(handler->decodeRdeCommand(
+                  cmdStart, RdeCommandType::RdeMultiPartReceiveResponse),
+              RdeDecodeStatus::RdeOk);
+
+    std::vector<uint8_t> middlePayload = {'p', 'a', 'r', 't', '2'};
+    auto cmdMiddle = createMultiPartRespCmd(
+        static_cast<uint8_t>(RdeMultiReceiveTransferFlag::RdeMRecFlagMiddle),
+        resourceId, middlePayload.size(), middlePayload);
+    ASSERT_EQ(handler->decodeRdeCommand(
+                  cmdMiddle, RdeCommandType::RdeMultiPartReceiveResponse),
+              RdeDecodeStatus::RdeOk);
+
+    std::vector<uint8_t> endPayload = {'p', 'a', 'r', 't', '3'};
+    uint32_t checksum = 0xf5295f3; // CRC32("part1part2part3")
+    auto cmdEnd = createMultiPartRespCmd(
+        static_cast<uint8_t>(RdeMultiReceiveTransferFlag::RdeMRecFlagEnd),
+        resourceId, endPayload.size(), endPayload, checksum);
+    auto status = handler->decodeRdeCommand(
+        cmdEnd, RdeCommandType::RdeMultiPartReceiveResponse);
+    EXPECT_EQ(status, RdeDecodeStatus::RdeStopFlagReceived);
+    EXPECT_EQ(handler->getDictionaryCount(), 1);
+}
+
+TEST_F(RdeCommandHandlerTest,
+       MultiPartReceiveResp_MultipleDictionaries_ValidSequence)
+{
+    // Dictionary 1: StartAndEnd
+    uint32_t resourceId1 = 1;
+    std::vector<uint8_t> payload1 = {'d', 'i', 'c', 't', '1'};
+    uint32_t checksum1 = 0xbca257a8; // CRC32("dict1")
+    auto cmd1 = createMultiPartRespCmd(
+        static_cast<uint8_t>(
+            RdeMultiReceiveTransferFlag::RdeMRecFlagStartAndEnd),
+        resourceId1, payload1.size(), payload1, checksum1);
+    ASSERT_EQ(handler->decodeRdeCommand(
+                  cmd1, RdeCommandType::RdeMultiPartReceiveResponse),
+              RdeDecodeStatus::RdeStopFlagReceived);
+    ASSERT_EQ(handler->getDictionaryCount(), 1);
+
+    // Dictionary 2: Start, Middle, End
+    uint32_t resourceId2 = 2;
+    std::vector<uint8_t> startPayload2 = {'d', '2', '_'};
+    auto cmdStart2 = createMultiPartRespCmd(
+        static_cast<uint8_t>(RdeMultiReceiveTransferFlag::RdeMRecFlagStart),
+        resourceId2, startPayload2.size(), startPayload2);
+    ASSERT_EQ(handler->decodeRdeCommand(
+                  cmdStart2, RdeCommandType::RdeMultiPartReceiveResponse),
+              RdeDecodeStatus::RdeOk);
+    ASSERT_EQ(handler->getDictionaryCount(),
+              1); // Dict1 still valid, Dict2 not yet
+
+    std::vector<uint8_t> middlePayload2 = {'m', 'i', 'd'};
+    auto cmdMiddle2 = createMultiPartRespCmd(
+        static_cast<uint8_t>(RdeMultiReceiveTransferFlag::RdeMRecFlagMiddle),
+        resourceId2, middlePayload2.size(), middlePayload2);
+    ASSERT_EQ(handler->decodeRdeCommand(
+                  cmdMiddle2, RdeCommandType::RdeMultiPartReceiveResponse),
+              RdeDecodeStatus::RdeOk);
+
+    std::vector<uint8_t> endPayload2 = {'e', 'n', 'd'};
+    uint32_t checksum2 = 0x9e428a17; // CRC32("d2_midend")
+    auto cmdEnd2 = createMultiPartRespCmd(
+        static_cast<uint8_t>(RdeMultiReceiveTransferFlag::RdeMRecFlagEnd),
+        resourceId2, endPayload2.size(), endPayload2, checksum2);
+    ASSERT_EQ(handler->decodeRdeCommand(
+                  cmdEnd2, RdeCommandType::RdeMultiPartReceiveResponse),
+              RdeDecodeStatus::RdeStopFlagReceived);
+    ASSERT_EQ(handler->getDictionaryCount(),
+              2); // Both dictionaries should now be valid
+}
+
 /**
  * @brief Dummy values for annotation dictionary. We do not need the annotation
  * dictionary. So this contains a dictionary with some dummy values. But the RDE