Add a class to handle RDE commands

This class is used to process RDE packets received from BIOS-BMC
circular buffer. RdeCommandHandler will manage dictionary data,
decode RDE BEJ encoded payloads and push them to the ExternalStorer.

Tested:
Tested this with unit tests.

Signed-off-by: Kasun Athukorala <kasunath@google.com>
Change-Id: Ic66e4e4e2afa523906835713d36015457f324fcc
diff --git a/include/rde/rde_handler.hpp b/include/rde/rde_handler.hpp
new file mode 100644
index 0000000..65e9361
--- /dev/null
+++ b/include/rde/rde_handler.hpp
@@ -0,0 +1,219 @@
+#pragma once
+
+#include "bej_decoder_json.hpp"
+#include "external_storer_interface.hpp"
+#include "rde_dictionary_manager.hpp"
+
+#include <cstdint>
+#include <span>
+#include <string_view>
+
+namespace bios_bmc_smm_error_logger
+{
+namespace rde
+{
+
+/**
+ * @brief Supported RDE commands.
+ *
+ * The values used are the same as what BIOS uses.
+ */
+enum class RdeCommandType : char
+{
+    // Used for RDE BEJ dictionary transfer.
+    RdeMultiPartReceiveResponse = 1,
+    // Used for RDE BEJ encoded data.
+    RdeOperationInitRequest = 2,
+};
+
+/**
+ * @brief Used to keep track of RdeMultiPartReceiveResponse START flag
+ * reception.
+ */
+enum class RdeDictTransferFlagState
+{
+    RdeStateIdle,
+    RdeStateStartRecvd,
+};
+
+/**
+ * @brief Status of RDE command processing.
+ */
+enum class RdeDecodeStatus
+{
+    RdeOk,
+    RdeInvalidCommand,
+    RdeUnsupportedOperation,
+    RdeNoDictionary,
+    RdePayloadOverflow,
+    RdeBejDecodingError,
+    RdeInvalidPktOrder,
+    RdeDictionaryError,
+    RdeFileCreationFailed,
+    RdeExternalStorerError,
+    // This implies that the stop flag has been received.
+    RdeInvalidChecksum,
+    // This implies that the checksum is correct.
+    RdeStopFlagReceived,
+};
+
+/**
+ * @brief Handles RDE messages from the BIOS - BMC circular buffer and updates
+ * ExternalStorer.
+ */
+class RdeCommandHandler
+{
+  public:
+    /**
+     * @brief Constructor for RdeExternalStorer.
+     *
+     * @param[in] exStorer - valid ExternalStorerInterface. This class will take
+     * the ownership of this object.
+     */
+    explicit RdeCommandHandler(
+        std::unique_ptr<ExternalStorerInterface> exStorer);
+
+    /**
+     * @brief Decode a RDE command.
+     *
+     * @param[in] rdeCommand - RDE command.
+     * @param[in] type - RDE command type.
+     * @return RdeDecodeStatus code.
+     */
+    RdeDecodeStatus decodeRdeCommand(std::span<const uint8_t> rdeCommand,
+                                     RdeCommandType type);
+
+    /**
+     * @brief Get the number of complete dictionaries received.
+     *
+     * @return number of complete dictionaries.
+     */
+    uint32_t getDictionaryCount();
+
+  private:
+    /**
+     * @brief This keeps track of whether we received the dictionary start flag
+     * or not.
+     */
+    RdeDictTransferFlagState flagState;
+
+    std::unique_ptr<ExternalStorerInterface> exStorer;
+
+    /**
+     * @brief We are using the prevDictResourceId to detect a new dictionary.
+     *
+     * BIOS-BMC buffer uses RdeMultiPartReceiveResponse START flag to indicate
+     * the first dictionary data chunk. BMC will not receive this flag at start
+     * of every new dictionary but only for the first data chunk. Therefore
+     * difference between resource ID is used to identify a new dictionary
+     * start. prevDictResourceId keeps track of the resource ID of the last
+     * dictionary data chunk.
+     */
+    uint32_t prevDictResourceId;
+
+    DictionaryManager dictionaryManager;
+    libbej::BejDecoderJson decoder;
+
+    uint32_t crc;
+    std::array<uint32_t, UINT8_MAX + 1> crcTable;
+
+    /**
+     * @brief Handles OperationInit request messages.
+     *
+     * @param[in] rdeCommand - RDE command.
+     * @return RdeDecodeStatus
+     */
+    RdeDecodeStatus operationInitRequest(std::span<const uint8_t> rdeCommand);
+
+    /**
+     * @brief Handles MultiPartReceive response messages.
+     *
+     * @param[in] rdeCommand - RDE command.
+     * @return RdeDecodeStatus
+     */
+    RdeDecodeStatus multiPartReceiveResp(std::span<const uint8_t> rdeCommand);
+
+    /**
+     * @brief Initializes the CRC table.
+     */
+    void calcCrcTable();
+
+    /**
+     * @brief Update the existing CRC using the provided byte stream.
+     *
+     * According to the RDE BEJ specification: "32-bit CRC for the entire block
+     * of data (all parts concatenated together, excluding this checksum)".
+     * Therefore when calculating the CRC whole RDEMultipartReceive Response
+     * data packet is considered, not just the dictionary data contained within
+     * it.
+     *
+     * @param[in] stream - a byte stream.
+     */
+    void updateCrc(std::span<const uint8_t> stream);
+
+    /**
+     * @brief Get the final checksum value.
+     *
+     * @return uint32_t - final checksum value.
+     */
+    uint32_t finalChecksum();
+
+    /**
+     * @brief Process received CRC field from a multi receive response command.
+     * END or START_AND_END flag should be set in the command.
+     *
+     * @param multiReceiveRespCmd - payload with a checksum field populated.
+     * @return RdeDecodeStatus
+     */
+    RdeDecodeStatus handleCrc(std::span<const uint8_t> multiReceiveRespCmd);
+
+    /**
+     * @brief Handle dictionary data with flag Start.
+     *
+     * @param[in] header -  RDE header portion of the RDE command.
+     * @param[in] data - data portion of the RDE command.
+     * @param[in] resourceId - PDR resource ID of the dictionary.
+     */
+    void handleFlagStart(const MultipartReceiveResHeader* header,
+                         const uint8_t* data, uint32_t resourceId);
+
+    /**
+     * @brief Handle dictionary data with flag Middle.
+     *
+     * @param[in] header -  RDE header portion of the RDE command.
+     * @param[in] data - data portion of the RDE command.
+     * @param[in] resourceId - PDR resource ID of the dictionary.
+     * @return RdeDecodeStatus
+     */
+    RdeDecodeStatus handleFlagMiddle(const MultipartReceiveResHeader* header,
+                                     const uint8_t* data, uint32_t resourceId);
+    /**
+     * @brief Handle dictionary data with flag End.
+     *
+     * @param[in] rdeCommand - RDE command.
+     * @param[in] header -  RDE header portion of the RDE command.
+     * @param[in] data - data portion of the RDE command.
+     * @param[in] resourceId - PDR resource ID of the dictionary.
+     * @return RdeDecodeStatus
+     */
+    RdeDecodeStatus handleFlagEnd(std::span<const uint8_t> rdeCommand,
+                                  const MultipartReceiveResHeader* header,
+                                  const uint8_t* data, uint32_t resourceId);
+
+    /**
+     * @brief Handle dictionary data with flag StartAndEnd.
+     *
+     * @param[in] rdeCommand - RDE command.
+     * @param[in] header -  RDE header portion of the RDE command.
+     * @param[in] data - data portion of the RDE command.
+     * @param[in] resourceId - PDR resource ID of the dictionary.
+     * @return RdeDecodeStatus
+     */
+    RdeDecodeStatus
+        handleFlagStartAndEnd(std::span<const uint8_t> rdeCommand,
+                              const MultipartReceiveResHeader* header,
+                              const uint8_t* data, uint32_t resourceId);
+};
+
+} // namespace rde
+} // namespace bios_bmc_smm_error_logger
diff --git a/src/rde/meson.build b/src/rde/meson.build
index 482e9ff..c6831e2 100644
--- a/src/rde/meson.build
+++ b/src/rde/meson.build
@@ -1,9 +1,17 @@
+libbej_dep = dependency('libbej')
+
+rde_pre = declare_dependency(
+  include_directories: [rde_inc],
+  dependencies: [libbej_dep])
+
 rde_lib = static_library(
   'rde',
   'rde_dictionary_manager.cpp',
   'external_storer_file.cpp',
-  include_directories : rde_inc,
-  implicit_include_directories: false)
+  'rde_handler.cpp',
+  implicit_include_directories: false,
+  dependencies: rde_pre)
 
 rde_dep = declare_dependency(
-  link_with: rde_lib)
+  link_with: rde_lib,
+  dependencies: rde_pre)
diff --git a/src/rde/rde_handler.cpp b/src/rde/rde_handler.cpp
new file mode 100644
index 0000000..e5100e2
--- /dev/null
+++ b/src/rde/rde_handler.cpp
@@ -0,0 +1,334 @@
+#include "rde/rde_handler.hpp"
+
+#include <fmt/format.h>
+
+#include <iostream>
+
+namespace bios_bmc_smm_error_logger
+{
+namespace rde
+{
+
+/**
+ * @brief CRC-32 divisor.
+ *
+ * This is equivalent to the one used by IEEE802.3.
+ */
+constexpr uint32_t crcDevisor = 0xedb88320;
+
+RdeCommandHandler::RdeCommandHandler(
+    std::unique_ptr<ExternalStorerInterface> exStorer) :
+    flagState(RdeDictTransferFlagState::RdeStateIdle),
+    exStorer(std::move(exStorer))
+{
+    // Initialize CRC table.
+    calcCrcTable();
+}
+
+RdeDecodeStatus
+    RdeCommandHandler::decodeRdeCommand(std::span<const uint8_t> rdeCommand,
+                                        RdeCommandType type)
+{
+    if (type == RdeCommandType::RdeMultiPartReceiveResponse)
+    {
+        return multiPartReceiveResp(rdeCommand);
+    }
+    if (type == RdeCommandType::RdeOperationInitRequest)
+    {
+        return operationInitRequest(rdeCommand);
+    }
+
+    fmt::print(stderr, "Invalid command type\n");
+    return RdeDecodeStatus::RdeInvalidCommand;
+}
+
+uint32_t RdeCommandHandler::getDictionaryCount()
+{
+    return dictionaryManager.getDictionaryCount();
+}
+
+RdeDecodeStatus
+    RdeCommandHandler::operationInitRequest(std::span<const uint8_t> rdeCommand)
+{
+    const RdeOperationInitReqHeader* header =
+        reinterpret_cast<const RdeOperationInitReqHeader*>(rdeCommand.data());
+    // Check if there is a payload. If not, we are not doing anything.
+    if (!header->containsRequestPayload)
+    {
+        return RdeDecodeStatus::RdeOk;
+    }
+
+    if (header->operationType != rdeOpInitOperationUpdate)
+    {
+        fmt::print(stderr, "Operation not supported\n");
+        return RdeDecodeStatus::RdeUnsupportedOperation;
+    }
+
+    // OperationInit payload overflows are not suported.
+    if (header->sendDataTransferHandle != 0)
+    {
+        fmt::print(stderr, "Payload should fit in within the request\n");
+        return RdeDecodeStatus::RdePayloadOverflow;
+    }
+
+    auto schemaDictOrErr = dictionaryManager.getDictionary(header->resourceID);
+    if (!schemaDictOrErr)
+    {
+        fmt::print(stderr, "Schema Dictionary not found for resourceId: {}\n",
+                   header->resourceID);
+        return RdeDecodeStatus::RdeNoDictionary;
+    }
+
+    auto annotationDictOrErr = dictionaryManager.getAnnotationDictionary();
+    if (!annotationDictOrErr)
+    {
+        fmt::print(stderr, "Annotation dictionary not found\n");
+        return RdeDecodeStatus::RdeNoDictionary;
+    }
+
+    BejDictionaries dictionaries = {
+        .schemaDictionary = (*schemaDictOrErr).data(),
+        .annotationDictionary = (*annotationDictOrErr).data(),
+        // We do not use the error dictionary.
+        .errorDictionary = nullptr,
+    };
+
+    // Soon after header, we have bejLocator field. Then we have the encoded
+    // data.
+    const uint8_t* encodedPldmBlock = rdeCommand.data() +
+                                      sizeof(RdeOperationInitReqHeader) +
+                                      header->operationLocatorLength;
+
+    // Decoded the data.
+    if (decoder.decode(dictionaries, std::span(encodedPldmBlock,
+                                               header->requestPayloadLength)) !=
+        0)
+    {
+        fmt::print(stderr, "BEJ decoding failed.\n");
+        return RdeDecodeStatus::RdeBejDecodingError;
+    }
+
+    // Post the output.
+    if (!exStorer->publishJson(decoder.getOutput()))
+    {
+        fmt::print(stderr, "Failed to write to ExternalStorer.\n");
+        return RdeDecodeStatus::RdeExternalStorerError;
+    }
+    return RdeDecodeStatus::RdeOk;
+}
+
+RdeDecodeStatus
+    RdeCommandHandler::multiPartReceiveResp(std::span<const uint8_t> rdeCommand)
+{
+    const MultipartReceiveResHeader* header =
+        reinterpret_cast<const MultipartReceiveResHeader*>(rdeCommand.data());
+
+    // 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
+    // dictionary data.
+    uint32_t resourceId = header->nextDataTransferHandle;
+
+    // data points to the payload of the MultipartReceive.
+    const uint8_t* data = rdeCommand.data() + sizeof(MultipartReceiveResHeader);
+    RdeDecodeStatus ret = RdeDecodeStatus::RdeOk;
+
+    switch (header->transferFlag)
+    {
+        case rdeMRecFlagStart:
+            handleFlagStart(header, data, resourceId);
+            break;
+        case rdeMRecFlagMiddle:
+            ret = handleFlagMiddle(header, data, resourceId);
+            break;
+        case rdeMRecFlagEnd:
+            ret = handleFlagEnd(rdeCommand, header, data, resourceId);
+            break;
+        case rdeMRecFlagStartAndEnd:
+            ret = handleFlagStartAndEnd(rdeCommand, header, data, resourceId);
+            break;
+        default:
+            fmt::print(stderr, "Invalid transfer flag: {}\n",
+                       header->transferFlag);
+            ret = RdeDecodeStatus::RdeInvalidCommand;
+    }
+
+    // If there is a failure, this assignment is not useful. So we can do it
+    // even if there is a failure.
+    prevDictResourceId = resourceId;
+    return ret;
+}
+
+void RdeCommandHandler::calcCrcTable()
+{
+    for (uint32_t i = 0; i < UINT8_MAX + 1; ++i)
+    {
+        uint32_t rem = i;
+        for (uint8_t k = 0; k < 8; ++k)
+        {
+            rem = (rem & 1) ? (rem >> 1) ^ crcDevisor : rem >> 1;
+        }
+        crcTable[i] = rem;
+    }
+}
+
+void RdeCommandHandler::updateCrc(std::span<const uint8_t> stream)
+{
+    for (uint32_t i = 0; i < stream.size_bytes(); ++i)
+    {
+        crc = crcTable[(crc ^ stream[i]) & 0xff] ^ (crc >> 8);
+    }
+}
+
+uint32_t RdeCommandHandler::finalChecksum()
+{
+    return (crc ^ 0xFFFFFFFF);
+}
+
+RdeDecodeStatus
+    RdeCommandHandler::handleCrc(std::span<const uint8_t> multiReceiveRespCmd)
+{
+    const MultipartReceiveResHeader* header =
+        reinterpret_cast<const MultipartReceiveResHeader*>(
+            multiReceiveRespCmd.data());
+    const uint8_t* checksumPtr = multiReceiveRespCmd.data() +
+                                 sizeof(MultipartReceiveResHeader) +
+                                 header->dataLengthBytes;
+    uint32_t checksum = checksumPtr[0] | (checksumPtr[1] << 8) |
+                        (checksumPtr[2] << 16) | (checksumPtr[3] << 24);
+
+    if (finalChecksum() != checksum)
+    {
+        fmt::print(stderr, "Checksum failed. Ex: {} Calculated: {}\n", checksum,
+                   finalChecksum());
+        dictionaryManager.invalidateDictionaries();
+        return RdeDecodeStatus::RdeInvalidChecksum;
+    }
+    return RdeDecodeStatus::RdeOk;
+}
+
+void RdeCommandHandler::handleFlagStart(const MultipartReceiveResHeader* header,
+                                        const uint8_t* data,
+                                        uint32_t resourceId)
+{
+    // This is a beginning of a dictionary. Reset CRC.
+    crc = 0xFFFFFFFF;
+    std::span dataS(data, header->dataLengthBytes);
+    dictionaryManager.startDictionaryEntry(resourceId, dataS);
+    // Start checksum calculation only for the data portion.
+    updateCrc(dataS);
+    flagState = RdeDictTransferFlagState::RdeStateStartRecvd;
+}
+
+RdeDecodeStatus
+    RdeCommandHandler::handleFlagMiddle(const MultipartReceiveResHeader* header,
+                                        const uint8_t* data,
+                                        uint32_t resourceId)
+{
+    if (flagState != RdeDictTransferFlagState::RdeStateStartRecvd)
+    {
+        fmt::print(
+            stderr,
+            "Invalid dictionary packet order. Need start before middle.\n");
+        return RdeDecodeStatus::RdeInvalidPktOrder;
+    }
+
+    std::span dataS(data, header->dataLengthBytes);
+    if (prevDictResourceId != resourceId)
+    {
+        // Start of a new dictionary. Mark previous dictionary as
+        // complete.
+        dictionaryManager.markDataComplete(prevDictResourceId);
+        dictionaryManager.startDictionaryEntry(resourceId, dataS);
+    }
+    else
+    {
+        // Not a new dictionary. Add the received data to the existing
+        // dictionary.
+        if (!dictionaryManager.addDictionaryData(resourceId, dataS))
+        {
+            fmt::print(stderr,
+                       "Failed to add dictionary data: ResourceId: {}\n",
+                       resourceId);
+            return RdeDecodeStatus::RdeDictionaryError;
+        }
+    }
+    // Continue checksum calculation only for the data portion.
+    updateCrc(dataS);
+    return RdeDecodeStatus::RdeOk;
+}
+
+RdeDecodeStatus
+    RdeCommandHandler::handleFlagEnd(std::span<const uint8_t> rdeCommand,
+                                     const MultipartReceiveResHeader* header,
+                                     const uint8_t* data, uint32_t resourceId)
+{
+    if (flagState != RdeDictTransferFlagState::RdeStateStartRecvd)
+    {
+        fmt::print(
+            stderr,
+            "Invalid dictionary packet order. Need start before middle.\n");
+        return RdeDecodeStatus::RdeInvalidPktOrder;
+    }
+    flagState = RdeDictTransferFlagState::RdeStateIdle;
+
+    std::span dataS(data, header->dataLengthBytes);
+    if (prevDictResourceId != resourceId)
+    {
+        // Start of a new dictionary. Mark previous dictionary as
+        // complete.
+        dictionaryManager.markDataComplete(prevDictResourceId);
+        dictionaryManager.startDictionaryEntry(resourceId, dataS);
+    }
+    else
+    {
+        if (!dictionaryManager.addDictionaryData(resourceId, dataS))
+        {
+            fmt::print(stderr,
+                       "Failed to add dictionary data: ResourceId: {}\n",
+                       resourceId);
+            return RdeDecodeStatus::RdeDictionaryError;
+        }
+    }
+    dictionaryManager.markDataComplete(resourceId);
+
+    // Continue checksum calculation only for the data portion. At the end of
+    // data, we will have the DataIntegrityChecksum field. So omit that when
+    // calculating checksum.
+    updateCrc(dataS);
+    auto ret = handleCrc(rdeCommand);
+    if (ret != RdeDecodeStatus::RdeOk)
+    {
+        return ret;
+    }
+    return RdeDecodeStatus::RdeStopFlagReceived;
+}
+
+RdeDecodeStatus RdeCommandHandler::handleFlagStartAndEnd(
+    std::span<const uint8_t> rdeCommand,
+    const MultipartReceiveResHeader* header, const uint8_t* data,
+    uint32_t resourceId)
+{
+    // This is a beginning of a dictionary. Reset CRC.
+    crc = 0xFFFFFFFF;
+    // This is a beginning and end of a dictionary.
+    dictionaryManager.startDictionaryEntry(
+        resourceId, std::span(data, header->dataLengthBytes));
+    dictionaryManager.markDataComplete(resourceId);
+    flagState = RdeDictTransferFlagState::RdeStateIdle;
+
+    // Do checksum calculation only for the data portion. At the end of data, we
+    // will have the DataIntegrityChecksum field. So omit that when calculating
+    // checksum.
+    updateCrc(std::span(data, header->dataLengthBytes));
+
+    auto ret = handleCrc(rdeCommand);
+    if (ret != RdeDecodeStatus::RdeOk)
+    {
+        return ret;
+    }
+    return RdeDecodeStatus::RdeStopFlagReceived;
+}
+
+} // namespace rde
+} // namespace bios_bmc_smm_error_logger
diff --git a/subprojects/libbej.wrap b/subprojects/libbej.wrap
new file mode 100644
index 0000000..4c03659
--- /dev/null
+++ b/subprojects/libbej.wrap
@@ -0,0 +1,6 @@
+[wrap-git]
+url = https://github.com/openbmc/libbej
+revision = HEAD
+
+[provide]
+libbej = libbej
diff --git a/test/meson.build b/test/meson.build
index 01a92a2..6528b92 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -27,6 +27,7 @@
   'rde_dictionary_manager',
   'buffer',
   'external_storer_file',
+  'rde_handler',
 ]
 foreach t : gtests
   test(t, executable(t.underscorify(), t + '_test.cpp',
diff --git a/test/rde_handler_test.cpp b/test/rde_handler_test.cpp
new file mode 100644
index 0000000..ef0574d
--- /dev/null
+++ b/test/rde_handler_test.cpp
@@ -0,0 +1,320 @@
+#include "nlohmann/json.hpp"
+#include "rde/external_storer_interface.hpp"
+#include "rde/rde_handler.hpp"
+
+#include <memory>
+#include <span>
+
+#include <gmock/gmock-matchers.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace bios_bmc_smm_error_logger
+{
+namespace rde
+{
+
+using ::testing::Return;
+
+/**
+ * @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
+ * header is correct.
+ */
+constexpr std::array<uint8_t, 38> mRcvDummyAnnotation{
+    {0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
+     0x0,  0x0,  0xc,  0x0,  0x0,  0xf0, 0xf0, 0xf1, 0x17, 0x1,
+     0x0,  0x0,  0x0,  0x0,  0x0,  0x16, 0x0,  0x5,  0x0,  0xc,
+     0x84, 0x0,  0x14, 0x0,  0x30, 0xa8, 0xc3, 0x3c}};
+
+constexpr std::array<uint8_t, 38> mRcvDummyInvalidChecksum{
+    {0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
+     0x0,  0x0,  0xc,  0x0,  0x0,  0xf0, 0xf0, 0xf1, 0x17, 0x1,
+     0x0,  0x0,  0x0,  0x0,  0x0,  0x16, 0x0,  0x5,  0x0,  0xc,
+     0x84, 0x0,  0x14, 0x0,  0x17, 0x86, 0x00, 0x00}};
+
+/**
+ * @brief MultipartReceive command with START_AND_END flag set.
+ */
+constexpr std::array<uint8_t, 293> mRcvInput0StartAndEnd{
+    {0x00, 0x03, 0x02, 0x00, 0x00, 0x00, 0x17, 0x01, 0x00, 0x00, 0x0,  0x0,
+     0xc,  0x0,  0x0,  0xf0, 0xf0, 0xf1, 0x17, 0x1,  0x0,  0x0,  0x0,  0x0,
+     0x0,  0x16, 0x0,  0x5,  0x0,  0xc,  0x84, 0x0,  0x14, 0x0,  0x0,  0x48,
+     0x0,  0x1,  0x0,  0x13, 0x90, 0x0,  0x56, 0x1,  0x0,  0x0,  0x0,  0x0,
+     0x0,  0x3,  0xa3, 0x0,  0x74, 0x2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x16,
+     0xa6, 0x0,  0x34, 0x3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x16, 0xbc, 0x0,
+     0x64, 0x4,  0x0,  0x0,  0x0,  0x0,  0x0,  0x13, 0xd2, 0x0,  0x0,  0x0,
+     0x0,  0x52, 0x0,  0x2,  0x0,  0x0,  0x0,  0x0,  0x74, 0x0,  0x0,  0x0,
+     0x0,  0x0,  0x0,  0xf,  0xe5, 0x0,  0x46, 0x1,  0x0,  0x66, 0x0,  0x3,
+     0x0,  0xb,  0xf4, 0x0,  0x50, 0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x9,
+     0xff, 0x0,  0x50, 0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7,  0x8,  0x1,
+     0x50, 0x2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7,  0xf,  0x1,  0x44, 0x75,
+     0x6d, 0x6d, 0x79, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x0,  0x43, 0x68,
+     0x69, 0x6c, 0x64, 0x41, 0x72, 0x72, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x70,
+     0x65, 0x72, 0x74, 0x79, 0x0,  0x49, 0x64, 0x0,  0x53, 0x61, 0x6d, 0x70,
+     0x6c, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x50, 0x72, 0x6f,
+     0x70, 0x65, 0x72, 0x74, 0x79, 0x0,  0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65,
+     0x49, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x70, 0x65,
+     0x72, 0x74, 0x79, 0x0,  0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65,
+     0x61, 0x6c, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x0,  0x41,
+     0x6e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x42, 0x6f, 0x6f, 0x6c, 0x65, 0x61,
+     0x6e, 0x0,  0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
+     0x0,  0x4c, 0x69, 0x6e, 0x6b, 0x44, 0x6f, 0x77, 0x6e, 0x0,  0x4c, 0x69,
+     0x6e, 0x6b, 0x55, 0x70, 0x0,  0x4e, 0x6f, 0x4c, 0x69, 0x6e, 0x6b, 0x0,
+     0x0,  0x8c, 0x87, 0xed, 0x74}};
+
+/**
+ * @brief MultipartReceive command with START flag set.
+ */
+constexpr std::array<uint8_t, 166> mRcvInput1Start{
+    {0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x0,  0x0,
+     0xc,  0x0,  0x0,  0xf0, 0xf0, 0xf1, 0x17, 0x1,  0x0,  0x0,  0x0,  0x0,
+     0x0,  0x16, 0x0,  0x5,  0x0,  0xc,  0x84, 0x0,  0x14, 0x0,  0x0,  0x48,
+     0x0,  0x1,  0x0,  0x13, 0x90, 0x0,  0x56, 0x1,  0x0,  0x0,  0x0,  0x0,
+     0x0,  0x3,  0xa3, 0x0,  0x74, 0x2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x16,
+     0xa6, 0x0,  0x34, 0x3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x16, 0xbc, 0x0,
+     0x64, 0x4,  0x0,  0x0,  0x0,  0x0,  0x0,  0x13, 0xd2, 0x0,  0x0,  0x0,
+     0x0,  0x52, 0x0,  0x2,  0x0,  0x0,  0x0,  0x0,  0x74, 0x0,  0x0,  0x0,
+     0x0,  0x0,  0x0,  0xf,  0xe5, 0x0,  0x46, 0x1,  0x0,  0x66, 0x0,  0x3,
+     0x0,  0xb,  0xf4, 0x0,  0x50, 0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x9,
+     0xff, 0x0,  0x50, 0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7,  0x8,  0x1,
+     0x50, 0x2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7,  0xf,  0x1,  0x44, 0x75,
+     0x6d, 0x6d, 0x79, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x0,  0x43, 0x68,
+     0x69, 0x6c, 0x64, 0x41, 0x72, 0x72, 0x61, 0x79, 0x50, 0x72}};
+
+/**
+ * @brief MultipartReceive command with END flag set.
+ */
+constexpr std::array<uint8_t, 137> mRcvInput1End{
+    {0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x6f, 0x70,
+     0x65, 0x72, 0x74, 0x79, 0x0,  0x49, 0x64, 0x0,  0x53, 0x61, 0x6d, 0x70,
+     0x6c, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x50, 0x72, 0x6f,
+     0x70, 0x65, 0x72, 0x74, 0x79, 0x0,  0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65,
+     0x49, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x70, 0x65,
+     0x72, 0x74, 0x79, 0x0,  0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65,
+     0x61, 0x6c, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x0,  0x41,
+     0x6e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x42, 0x6f, 0x6f, 0x6c, 0x65, 0x61,
+     0x6e, 0x0,  0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
+     0x0,  0x4c, 0x69, 0x6e, 0x6b, 0x44, 0x6f, 0x77, 0x6e, 0x0,  0x4c, 0x69,
+     0x6e, 0x6b, 0x55, 0x70, 0x0,  0x4e, 0x6f, 0x4c, 0x69, 0x6e, 0x6b, 0x0,
+     0x0,  0x8c, 0x87, 0xed, 0x74}};
+
+/**
+ * @brief MultipartReceive command with START flag set.
+ */
+constexpr std::array<uint8_t, 106> mRcvInput2Start{
+    {0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x60, 0x0,  0x00, 0x00, 0x0,  0x0,
+     0xc,  0x0,  0x0,  0xf0, 0xf0, 0xf1, 0x17, 0x1,  0x0,  0x0,  0x0,  0x0,
+     0x0,  0x16, 0x0,  0x5,  0x0,  0xc,  0x84, 0x0,  0x14, 0x0,  0x0,  0x48,
+     0x0,  0x1,  0x0,  0x13, 0x90, 0x0,  0x56, 0x1,  0x0,  0x0,  0x0,  0x0,
+     0x0,  0x3,  0xa3, 0x0,  0x74, 0x2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x16,
+     0xa6, 0x0,  0x34, 0x3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x16, 0xbc, 0x0,
+     0x64, 0x4,  0x0,  0x0,  0x0,  0x0,  0x0,  0x13, 0xd2, 0x0,  0x0,  0x0,
+     0x0,  0x52, 0x0,  0x2,  0x0,  0x0,  0x0,  0x0,  0x74, 0x0,  0x0,  0x0,
+     0x0,  0x0,  0x0,  0xf,  0xe5, 0x0,  0x46, 0x1,  0x0,  0x66}};
+
+/**
+ * @brief MultipartReceive command with MIDDLE flag set.
+ */
+constexpr std::array<uint8_t, 106> mRcvInput2Mid{
+    {0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x60, 0x0,  0x00, 0x00, 0x0,  0x3,
+     0x0,  0xb,  0xf4, 0x0,  0x50, 0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x9,
+     0xff, 0x0,  0x50, 0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7,  0x8,  0x1,
+     0x50, 0x2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7,  0xf,  0x1,  0x44, 0x75,
+     0x6d, 0x6d, 0x79, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x0,  0x43, 0x68,
+     0x69, 0x6c, 0x64, 0x41, 0x72, 0x72, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x70,
+     0x65, 0x72, 0x74, 0x79, 0x0,  0x49, 0x64, 0x0,  0x53, 0x61, 0x6d, 0x70,
+     0x6c, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x50, 0x72, 0x6f,
+     0x70, 0x65, 0x72, 0x74, 0x79, 0x0,  0x53, 0x61, 0x6d, 0x70}};
+
+/**
+ * @brief MultipartReceive command with END flag set.
+ */
+constexpr std::array<uint8_t, 101> mRcvInput2End{
+    {0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x57, 0x0,  0x00, 0x00, 0x6c, 0x65,
+     0x49, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x70, 0x65,
+     0x72, 0x74, 0x79, 0x0,  0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65,
+     0x61, 0x6c, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x0,  0x41,
+     0x6e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x42, 0x6f, 0x6f, 0x6c, 0x65, 0x61,
+     0x6e, 0x0,  0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
+     0x0,  0x4c, 0x69, 0x6e, 0x6b, 0x44, 0x6f, 0x77, 0x6e, 0x0,  0x4c, 0x69,
+     0x6e, 0x6b, 0x55, 0x70, 0x0,  0x4e, 0x6f, 0x4c, 0x69, 0x6e, 0x6b, 0x0,
+     0x0,  0x8c, 0x87, 0xed, 0x74}};
+
+/**
+ * @brief RDEOperationInit command with encoded json/dummysimple.json as the
+ * payload.
+ */
+constexpr std::array<uint8_t, 113> mInitOp{
+    {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00,
+     0x00, 0x60, 0x00, 0x00, 0x00, 0x0,  0xf0, 0xf0, 0xf1, 0x0,  0x0,  0x0,
+     0x1,  0x0,  0x0,  0x1,  0x54, 0x1,  0x5,  0x1,  0x2,  0x50, 0x1,  0x9,
+     0x44, 0x75, 0x6d, 0x6d, 0x79, 0x20, 0x49, 0x44, 0x0,  0x1,  0x6,  0x20,
+     0x1,  0x0,  0x1,  0x8,  0x60, 0x1,  0xb,  0x1,  0x2,  0x38, 0xea, 0x1,
+     0x0,  0x2,  0xa3, 0x23, 0x1,  0x0,  0x1,  0x4,  0x70, 0x1,  0x1,  0x0,
+     0x1,  0x0,  0x10, 0x1,  0x24, 0x1,  0x2,  0x1,  0x0,  0x0,  0x1,  0xf,
+     0x1,  0x2,  0x1,  0x0,  0x70, 0x1,  0x1,  0x1,  0x1,  0x2,  0x40, 0x1,
+     0x2,  0x1,  0x2,  0x1,  0x2,  0x0,  0x1,  0x9,  0x1,  0x1,  0x1,  0x2,
+     0x40, 0x1,  0x2,  0x1,  0x2}};
+
+class MockExternalStorer : public ExternalStorerInterface
+{
+  public:
+    MOCK_METHOD(bool, publishJson, (std::string_view jsonStr), (override));
+};
+
+class RdeHandlerTest : public ::testing::Test
+{
+  public:
+    RdeHandlerTest() : mockExStorer(std::make_unique<MockExternalStorer>())
+    {
+        mockExStorerPtr = dynamic_cast<MockExternalStorer*>(mockExStorer.get());
+        rdeH = std::make_unique<RdeCommandHandler>(std::move(mockExStorer));
+    }
+
+  protected:
+    std::unique_ptr<ExternalStorerInterface> mockExStorer;
+    std::unique_ptr<RdeCommandHandler> rdeH;
+    MockExternalStorer* mockExStorerPtr;
+    const std::string exJson =
+        R"({"Id":"Dummy ID","SampleIntegerProperty":null,"SampleRealProperty":-5576.9123,"SampleEnabledProperty":false,"ChildArrayProperty":[{"AnotherBoolean":true,"LinkStatus":"NoLink"},{"LinkStatus":"NoLink"}]})";
+};
+
+TEST_F(RdeHandlerTest, DictionaryStartAndEndTest)
+{
+    // Send a payload with START_AND_END flag.
+    EXPECT_THAT(
+        rdeH->decodeRdeCommand(std::span(mRcvInput0StartAndEnd),
+                               RdeCommandType::RdeMultiPartReceiveResponse),
+        RdeDecodeStatus::RdeStopFlagReceived);
+    EXPECT_THAT(rdeH->getDictionaryCount(), 1);
+    // Send annotation dictionary.
+    EXPECT_THAT(
+        rdeH->decodeRdeCommand(std::span(mRcvDummyAnnotation),
+                               RdeCommandType::RdeMultiPartReceiveResponse),
+        RdeDecodeStatus::RdeStopFlagReceived);
+    EXPECT_THAT(rdeH->getDictionaryCount(), 2);
+
+    // Send the encoded payload.
+    EXPECT_CALL(*mockExStorerPtr, publishJson(exJson)).WillOnce(Return(true));
+    EXPECT_THAT(rdeH->decodeRdeCommand(std::span(mInitOp),
+                                       RdeCommandType::RdeOperationInitRequest),
+                RdeDecodeStatus::RdeOk);
+}
+
+TEST_F(RdeHandlerTest, DictionaryStartThenEndTest)
+{
+    // Send a payload with START flag.
+    EXPECT_THAT(
+        rdeH->decodeRdeCommand(std::span(mRcvInput1Start),
+                               RdeCommandType::RdeMultiPartReceiveResponse),
+        RdeDecodeStatus::RdeOk);
+    // We didn't send END. So dictionary count should be 0.
+    EXPECT_THAT(rdeH->getDictionaryCount(), 0);
+    // Send a payload with END flag.
+    EXPECT_THAT(
+        rdeH->decodeRdeCommand(std::span(mRcvInput1End),
+                               RdeCommandType::RdeMultiPartReceiveResponse),
+        RdeDecodeStatus::RdeStopFlagReceived);
+    EXPECT_THAT(rdeH->getDictionaryCount(), 1);
+    // Send annotation dictionary.
+    EXPECT_THAT(
+        rdeH->decodeRdeCommand(std::span(mRcvDummyAnnotation),
+                               RdeCommandType::RdeMultiPartReceiveResponse),
+        RdeDecodeStatus::RdeStopFlagReceived);
+    EXPECT_THAT(rdeH->getDictionaryCount(), 2);
+
+    // Send the encoded payload.
+    EXPECT_CALL(*mockExStorerPtr, publishJson(exJson)).WillOnce(Return(true));
+    EXPECT_THAT(rdeH->decodeRdeCommand(std::span(mInitOp),
+                                       RdeCommandType::RdeOperationInitRequest),
+                RdeDecodeStatus::RdeOk);
+
+    // Sending the START again for same resource ID should decrease the
+    // dictionary count.
+    EXPECT_THAT(
+        rdeH->decodeRdeCommand(std::span(mRcvInput1Start),
+                               RdeCommandType::RdeMultiPartReceiveResponse),
+        RdeDecodeStatus::RdeOk);
+    EXPECT_THAT(rdeH->getDictionaryCount(), 1);
+}
+
+TEST_F(RdeHandlerTest, DictionaryStartMidEndTest)
+{
+    // Send a payload with START flag.
+    EXPECT_THAT(
+        rdeH->decodeRdeCommand(std::span(mRcvInput2Start),
+                               RdeCommandType::RdeMultiPartReceiveResponse),
+        RdeDecodeStatus::RdeOk);
+    // We didn't send END. So dictionary count should be 0.
+    EXPECT_THAT(rdeH->getDictionaryCount(), 0);
+    // Send a payload with MIDDLE flag.
+    EXPECT_THAT(
+        rdeH->decodeRdeCommand(std::span(mRcvInput2Mid),
+                               RdeCommandType::RdeMultiPartReceiveResponse),
+        RdeDecodeStatus::RdeOk);
+    // We didn't send END. So dictionary count should be 0.
+    EXPECT_THAT(rdeH->getDictionaryCount(), 0);
+    // Send a payload with END flag.
+    EXPECT_THAT(
+        rdeH->decodeRdeCommand(std::span(mRcvInput2End),
+                               RdeCommandType::RdeMultiPartReceiveResponse),
+        RdeDecodeStatus::RdeStopFlagReceived);
+    EXPECT_THAT(rdeH->getDictionaryCount(), 1);
+
+    // Send annotation dictionary.
+    EXPECT_THAT(
+        rdeH->decodeRdeCommand(std::span(mRcvDummyAnnotation),
+                               RdeCommandType::RdeMultiPartReceiveResponse),
+        RdeDecodeStatus::RdeStopFlagReceived);
+    EXPECT_THAT(rdeH->getDictionaryCount(), 2);
+
+    // Send the encoded payload.
+    EXPECT_CALL(*mockExStorerPtr, publishJson(exJson)).WillOnce(Return(true));
+    EXPECT_THAT(rdeH->decodeRdeCommand(std::span(mInitOp),
+                                       RdeCommandType::RdeOperationInitRequest),
+                RdeDecodeStatus::RdeOk);
+}
+
+TEST_F(RdeHandlerTest, InvalidDictionaryFlowTest)
+{
+    // Send a payload with MIDDLE flag before START and it should fail.
+    EXPECT_THAT(
+        rdeH->decodeRdeCommand(std::span(mRcvInput2Mid),
+                               RdeCommandType::RdeMultiPartReceiveResponse),
+        RdeDecodeStatus::RdeInvalidPktOrder);
+    // Send a payload with END flag before START and it should fail.
+    EXPECT_THAT(
+        rdeH->decodeRdeCommand(std::span(mRcvInput2End),
+                               RdeCommandType::RdeMultiPartReceiveResponse),
+        RdeDecodeStatus::RdeInvalidPktOrder);
+}
+
+TEST_F(RdeHandlerTest, MissingDictionaryTest)
+{
+    // Try decoding without any dictionaries.
+    EXPECT_THAT(rdeH->decodeRdeCommand(std::span(mInitOp),
+                                       RdeCommandType::RdeOperationInitRequest),
+                RdeDecodeStatus::RdeNoDictionary);
+
+    // Try decoding just with annotation dictionary.
+    EXPECT_THAT(
+        rdeH->decodeRdeCommand(std::span(mRcvDummyAnnotation),
+                               RdeCommandType::RdeMultiPartReceiveResponse),
+        RdeDecodeStatus::RdeStopFlagReceived);
+    EXPECT_THAT(rdeH->decodeRdeCommand(std::span(mInitOp),
+                                       RdeCommandType::RdeOperationInitRequest),
+                RdeDecodeStatus::RdeNoDictionary);
+}
+
+TEST_F(RdeHandlerTest, InvalidDictionaryChecksumTest)
+{
+    // Send a dictionary with an invalid checksum.
+    EXPECT_THAT(
+        rdeH->decodeRdeCommand(std::span(mRcvDummyInvalidChecksum),
+                               RdeCommandType::RdeMultiPartReceiveResponse),
+        RdeDecodeStatus::RdeInvalidChecksum);
+}
+
+} // namespace rde
+} // namespace bios_bmc_smm_error_logger