Add a class to handle RDE BEJ dictionary data

This class is used to store RDE BEJ dictionary data transmitted
through bios-bmc circular buffer interface.

Signed-off-by: Kasun Athukorala <kasunath@google.com>
Change-Id: Idf7726a9f4647885ede615229d507f233ffb13c5
diff --git a/include/rde/rde_dictionary_manager.hpp b/include/rde/rde_dictionary_manager.hpp
new file mode 100644
index 0000000..099ac24
--- /dev/null
+++ b/include/rde/rde_dictionary_manager.hpp
@@ -0,0 +1,123 @@
+#pragma once
+
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <span>
+#include <unordered_map>
+#include <vector>
+
+namespace bios_bmc_smm_error_logger
+{
+namespace rde
+{
+
+/**
+ * @brief Resource ID for the annotation dictionary. The other entity
+ * communicating with the BMC (Eg: BIOS) should use the same resource ID for the
+ * annotation dictionary.
+ */
+constexpr uint32_t annotationResourceId = 0;
+
+/**
+ * @brief Holds an RDE BEJ dictionary entry.
+ */
+struct DictionaryEntry
+{
+    DictionaryEntry(bool valid, const std::span<const uint8_t> data) :
+        valid(valid), data(data.begin(), data.end())
+    {}
+    // True indicates that the dictionary data is ready to be used.
+    bool valid;
+    std::vector<uint8_t> data;
+};
+
+/**
+ * @brief Manages RDE BEJ dictionaries.
+ */
+class DictionaryManager
+{
+  public:
+    DictionaryManager();
+
+    /**
+     * @brief Starts a dictionary entry with the provided data.
+     *
+     * @param[in] resourceId - PDR resource id corresponding to the dictionary.
+     * @param[in] data - dictionary data.
+     */
+    void startDictionaryEntry(uint32_t resourceId,
+                              const std::span<const uint8_t> data);
+
+    /**
+     * @brief Set the dictionary valid status. Until this is called, dictionary
+     * data is considered to be incomplete for use.
+     *
+     * @param[in] resourceId - PDR resource id corresponding to the dictionary.
+     * @return true if successful.
+     */
+    bool markDataComplete(uint32_t resourceId);
+
+    /**
+     * @brief Add more dictionary data for an existing entry. Adding data to a
+     * completed dictionary will mark the dictionary as incomplete.
+     *
+     * @param[in] resourceId - PDR resource id corresponding to the dictionary.
+     * @param[in] data - dictionary data.
+     * @return true if successful.
+     */
+    bool addDictionaryData(uint32_t resourceId,
+                           const std::span<const uint8_t> data);
+
+    /**
+     * @brief Get a dictionary.
+     *
+     * @param[in] resourceId - PDR resource id corresponding to the dictionary.
+     * @return a pointer to the dictionary, if the dictionary is complete else
+     * std::nullopt.
+     */
+    std::optional<std::span<const uint8_t>> getDictionary(uint32_t resourceId);
+
+    /**
+     * @brief Get the annotation dictionary.
+     *
+     * @return a pointer to the annotation dictionary, if the dictionary is
+     * complete else std::nullopt.
+     */
+    std::optional<std::span<const uint8_t>> getAnnotationDictionary();
+
+    /**
+     * @brief Get the completed dictionary count.
+     *
+     * @return number of completed dictionaries available.
+     */
+    uint32_t getDictionaryCount();
+
+    /**
+     * @brief Invalidate all dictionaries.
+     */
+    void invalidateDictionaries();
+
+  private:
+    uint32_t validDictionaryCount;
+    std::unordered_map<uint32_t, std::unique_ptr<DictionaryEntry>> dictionaries;
+
+    /**
+     * @brief Set a dictionary entry to be invalid and reduce the valid
+     * dictionary count.
+     *
+     * @param[in] entry - A dictionary entry.
+     */
+    void invalidateDictionaryEntry(DictionaryEntry& entry);
+
+    /**
+     * @brief Set the dictionary entry valid flag and increase the valid
+     * dictionary count.
+     *
+     * @param[in] entry - A dictionary entry.
+     */
+    void validateDictionaryEntry(DictionaryEntry& entry);
+};
+
+} // namespace rde
+} // namespace bios_bmc_smm_error_logger
diff --git a/meson.build b/meson.build
index 85d7272..1b989ae 100644
--- a/meson.build
+++ b/meson.build
@@ -11,8 +11,10 @@
 
 root_inc = include_directories('.')
 bios_bmc_smm_error_logger_inc = include_directories('include')
+rde_inc = include_directories('include')
 
 subdir('src')
+subdir('src/rde')
 if not get_option('tests').disabled()
   subdir('test')
 endif
diff --git a/src/rde/meson.build b/src/rde/meson.build
new file mode 100644
index 0000000..4e6220f
--- /dev/null
+++ b/src/rde/meson.build
@@ -0,0 +1,8 @@
+rde_lib = static_library(
+  'rde',
+  'rde_dictionary_manager.cpp',
+  include_directories : rde_inc,
+  implicit_include_directories: false)
+
+rde_dep = declare_dependency(
+  link_with: rde_lib)
diff --git a/src/rde/rde_dictionary_manager.cpp b/src/rde/rde_dictionary_manager.cpp
new file mode 100644
index 0000000..a71114c
--- /dev/null
+++ b/src/rde/rde_dictionary_manager.cpp
@@ -0,0 +1,125 @@
+#include "rde/rde_dictionary_manager.hpp"
+
+#include <fmt/format.h>
+
+namespace bios_bmc_smm_error_logger
+{
+namespace rde
+{
+
+DictionaryManager::DictionaryManager() : validDictionaryCount(0)
+{}
+
+void DictionaryManager::startDictionaryEntry(
+    uint32_t resourceId, const std::span<const uint8_t> data)
+{
+    // Check whether the resourceId is already available.
+    auto itemIt = dictionaries.find(resourceId);
+    if (itemIt == dictionaries.end())
+    {
+        dictionaries[resourceId] =
+            std::make_unique<DictionaryEntry>(false, data);
+        return;
+    }
+
+    // Since we are creating a new dictionary on an existing entry, invalidate
+    // the existing entry.
+    invalidateDictionaryEntry(*itemIt->second);
+
+    // Flush the existing data.
+    itemIt->second->data.clear();
+    itemIt->second->data.insert(itemIt->second->data.begin(), data.begin(),
+                                data.end());
+}
+
+bool DictionaryManager::markDataComplete(uint32_t resourceId)
+{
+    auto itemIt = dictionaries.find(resourceId);
+    if (itemIt == dictionaries.end())
+    {
+        fmt::print(stderr, "Resource ID {} not found.\n", resourceId);
+        return false;
+    }
+    validateDictionaryEntry(*itemIt->second);
+    return true;
+}
+
+bool DictionaryManager::addDictionaryData(uint32_t resourceId,
+                                          const std::span<const uint8_t> data)
+{
+    auto itemIt = dictionaries.find(resourceId);
+    if (itemIt == dictionaries.end())
+    {
+        fmt::print(stderr, "Resource ID {} not found.\n", resourceId);
+        return false;
+    }
+    // Since we are modifying an existing entry, invalidate the existing entry.
+    invalidateDictionaryEntry(*itemIt->second);
+    itemIt->second->data.insert(itemIt->second->data.end(), data.begin(),
+                                data.end());
+    return true;
+}
+
+std::optional<std::span<const uint8_t>>
+    DictionaryManager::getDictionary(uint32_t resourceId)
+{
+    auto itemIt = dictionaries.find(resourceId);
+    if (itemIt == dictionaries.end())
+    {
+        fmt::print(stderr, "Resource ID {} not found.\n", resourceId);
+        return std::nullopt;
+    }
+
+    if (!itemIt->second->valid)
+    {
+        fmt::print(stderr,
+                   "Requested an incomplete dictionary. Resource ID {}\n",
+                   resourceId);
+        return std::nullopt;
+    }
+    return itemIt->second->data;
+}
+
+std::optional<std::span<const uint8_t>>
+    DictionaryManager::getAnnotationDictionary()
+{
+    return getDictionary(annotationResourceId);
+}
+
+uint32_t DictionaryManager::getDictionaryCount()
+{
+    return validDictionaryCount;
+}
+
+void DictionaryManager::invalidateDictionaries()
+{
+    // We won't flush the existing data. The data will be flushed if a new entry
+    // is added for an existing resource ID.
+    for (const auto& element : dictionaries)
+    {
+        element.second->valid = false;
+    }
+    validDictionaryCount = 0;
+}
+
+void DictionaryManager::invalidateDictionaryEntry(DictionaryEntry& entry)
+{
+    // If this is a valid entry, reduce the valid dictionary count.
+    if (entry.valid)
+    {
+        --validDictionaryCount;
+    }
+    entry.valid = false;
+}
+
+void DictionaryManager::validateDictionaryEntry(DictionaryEntry& entry)
+{
+    if (!entry.valid)
+    {
+        ++validDictionaryCount;
+    }
+    entry.valid = true;
+}
+
+} // namespace rde
+} // namespace bios_bmc_smm_error_logger
diff --git a/test/meson.build b/test/meson.build
index 93ddce9..081fcd7 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -19,10 +19,11 @@
 
 gtests = [
   'pci_handler',
+  'rde_dictionary_manager',
 ]
 foreach t : gtests
   test(t, executable(t.underscorify(), t + '_test.cpp',
                      build_by_default: false,
                      implicit_include_directories: false,
-                     dependencies: [bios_bmc_smm_error_logger_dep, gtest, gmock]))
+                     dependencies: [bios_bmc_smm_error_logger_dep, gtest, gmock, rde_dep]))
 endforeach
diff --git a/test/rde_dictionary_manager_test.cpp b/test/rde_dictionary_manager_test.cpp
new file mode 100644
index 0000000..5784bf9
--- /dev/null
+++ b/test/rde_dictionary_manager_test.cpp
@@ -0,0 +1,178 @@
+#include "rde/rde_dictionary_manager.hpp"
+
+#include <cstring>
+#include <memory>
+#include <optional>
+#include <span>
+
+#include <gmock/gmock-matchers.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace bios_bmc_smm_error_logger
+{
+namespace rde
+{
+
+constexpr std::array<uint8_t, 132> dummyDictionary1{
+    {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}};
+
+constexpr std::array<uint8_t, 14> dummyDictionary2{
+    {0x65, 0x0, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x41, 0x72, 0x72, 0x61, 0x79,
+     0x50, 0x72}};
+
+class RdeDictionaryManagerTest : public ::testing::Test
+{
+  protected:
+    uint32_t resourceId = 1;
+    DictionaryManager dm;
+};
+
+TEST_F(RdeDictionaryManagerTest, DictionarySetTest)
+{
+    // Add a single dictionary.
+    dm.startDictionaryEntry(resourceId, std::span(dummyDictionary1));
+    EXPECT_THAT(dm.getDictionaryCount(), 0);
+
+    // Mark the dictionary as a valid dictionary.
+    dm.markDataComplete(resourceId);
+    EXPECT_THAT(dm.getDictionaryCount(), 1);
+
+    // Request the dictionary back and verify the data.
+    auto dataOrErr = dm.getDictionary(resourceId);
+    EXPECT_TRUE(dataOrErr);
+    EXPECT_THAT((*dataOrErr).size_bytes(), dummyDictionary1.size());
+    EXPECT_THAT(memcmp((*dataOrErr).data(), dummyDictionary1.data(),
+                       dummyDictionary1.size()),
+                0);
+}
+
+TEST_F(RdeDictionaryManagerTest, DictionaryNotSetTest)
+{
+    // Add a single dictionary.
+    dm.startDictionaryEntry(resourceId, std::span(dummyDictionary1));
+    EXPECT_THAT(dm.getDictionaryCount(), 0);
+    // Request the dictionary back without marking it complete. Request should
+    // fail.
+    EXPECT_FALSE(dm.getDictionary(resourceId));
+}
+
+TEST_F(RdeDictionaryManagerTest, DictionaryMultiSetTest)
+{
+    // Creates a dictionary
+    dm.startDictionaryEntry(resourceId, std::span(dummyDictionary1));
+    EXPECT_THAT(dm.getDictionaryCount(), 0);
+    dm.markDataComplete(resourceId);
+    EXPECT_THAT(dm.getDictionaryCount(), 1);
+
+    // Creates a second dictionary.
+    dm.startDictionaryEntry(annotationResourceId, std::span(dummyDictionary2));
+    dm.markDataComplete(annotationResourceId);
+    EXPECT_THAT(dm.getDictionaryCount(), 2);
+
+    auto data1OrErr = dm.getDictionary(resourceId);
+    EXPECT_TRUE(data1OrErr);
+    EXPECT_THAT((*data1OrErr).size_bytes(), dummyDictionary1.size());
+    EXPECT_THAT(memcmp((*data1OrErr).data(), dummyDictionary1.data(),
+                       dummyDictionary1.size()),
+                0);
+
+    auto data2OrErr = dm.getDictionary(annotationResourceId);
+    EXPECT_TRUE(data2OrErr);
+    EXPECT_THAT((*data2OrErr).size_bytes(), dummyDictionary2.size());
+    EXPECT_THAT(memcmp((*data2OrErr).data(), dummyDictionary2.data(),
+                       dummyDictionary2.size()),
+                0);
+}
+
+TEST_F(RdeDictionaryManagerTest, DictionaryOverwriteTest)
+{
+    dm.startDictionaryEntry(resourceId, std::span(dummyDictionary2));
+
+    // Recreate another one on the same location.
+    dm.startDictionaryEntry(resourceId, std::span(dummyDictionary1));
+    EXPECT_THAT(dm.getDictionaryCount(), 0);
+    dm.markDataComplete(resourceId);
+    EXPECT_THAT(dm.getDictionaryCount(), 1);
+
+    auto dataOrErr = dm.getDictionary(resourceId);
+    EXPECT_TRUE(dataOrErr);
+    EXPECT_THAT((*dataOrErr).size_bytes(), dummyDictionary1.size());
+    EXPECT_THAT(memcmp((*dataOrErr).data(), dummyDictionary1.data(),
+                       dummyDictionary1.size()),
+                0);
+
+    // Recreate another one on the same location.
+    dm.startDictionaryEntry(resourceId, std::span(dummyDictionary2));
+    EXPECT_THAT(dm.getDictionaryCount(), 0);
+    dm.markDataComplete(resourceId);
+    EXPECT_THAT(dm.getDictionaryCount(), 1);
+
+    auto newDataOrErr = dm.getDictionary(resourceId);
+    EXPECT_TRUE(newDataOrErr);
+    EXPECT_THAT((*newDataOrErr).size_bytes(), dummyDictionary2.size());
+    EXPECT_THAT(memcmp((*newDataOrErr).data(), dummyDictionary2.data(),
+                       dummyDictionary2.size()),
+                0);
+}
+
+TEST_F(RdeDictionaryManagerTest, DictionaryAppendDataTest)
+{
+    // Creates a dictionary
+    dm.startDictionaryEntry(resourceId, std::span(dummyDictionary1));
+    EXPECT_THAT(dm.getDictionaryCount(), 0);
+
+    // Lets copy the dictionary in two sizes.
+    const uint32_t copySize1 = dummyDictionary2.size() / 2;
+    const uint32_t copySize2 = dummyDictionary2.size() - copySize1;
+
+    // Overwrite on the same location as before.
+    dm.startDictionaryEntry(resourceId,
+                            std::span(dummyDictionary2.data(), copySize1));
+    dm.addDictionaryData(
+        resourceId, std::span(dummyDictionary2.data() + copySize1, copySize2));
+    dm.markDataComplete(resourceId);
+    EXPECT_THAT(dm.getDictionaryCount(), 1);
+
+    auto dataOrErr = dm.getDictionary(resourceId);
+    EXPECT_TRUE(dataOrErr);
+    EXPECT_THAT((*dataOrErr).size_bytes(), dummyDictionary2.size());
+    EXPECT_THAT(memcmp((*dataOrErr).data(), dummyDictionary2.data(),
+                       dummyDictionary2.size()),
+                0);
+}
+
+TEST_F(RdeDictionaryManagerTest, DictionaryOverrideWithAddDataTest)
+{
+    dm.startDictionaryEntry(resourceId, std::span(dummyDictionary1));
+    dm.markDataComplete(resourceId);
+    EXPECT_THAT(dm.getDictionaryCount(), 1);
+
+    dm.addDictionaryData(resourceId, std::span(dummyDictionary2));
+    EXPECT_THAT(dm.getDictionaryCount(), 0);
+    dm.markDataComplete(resourceId);
+    EXPECT_THAT(dm.getDictionaryCount(), 1);
+}
+
+TEST_F(RdeDictionaryManagerTest, DictionaryInvalidateTest)
+{
+    dm.startDictionaryEntry(resourceId, std::span(dummyDictionary1));
+    dm.markDataComplete(resourceId);
+    EXPECT_THAT(dm.getDictionaryCount(), 1);
+
+    dm.invalidateDictionaries();
+    EXPECT_THAT(dm.getDictionaryCount(), 0);
+}
+
+} // namespace rde
+} // namespace bios_bmc_smm_error_logger