PEL: Add Repo API to update transmission states

Provided APIs for the Repository class to update the host and HMC
transmission states on a PEL it contains.  It saves the updated PEL data
in the filesystem.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Iadbc589ee85d4408339e1171c36b8324910f4f0a
diff --git a/extensions/openpower-pels/repository.cpp b/extensions/openpower-pels/repository.cpp
index b15fd38..f90a628 100644
--- a/extensions/openpower-pels/repository.cpp
+++ b/extensions/openpower-pels/repository.cpp
@@ -279,5 +279,81 @@
     return std::nullopt;
 }
 
+void Repository::setPELHostTransState(uint32_t pelID, TransmissionState state)
+{
+    LogID id{LogID::Pel{pelID}};
+    auto attr = std::find_if(_pelAttributes.begin(), _pelAttributes.end(),
+                             [&id](const auto& a) { return a.first == id; });
+
+    if ((attr != _pelAttributes.end()) && (attr->second.hostState != state))
+    {
+        PELUpdateFunc func = [state](PEL& pel) {
+            pel.setHostTransmissionState(state);
+        };
+
+        try
+        {
+            updatePEL(attr->second.path, func);
+
+            attr->second.hostState = state;
+        }
+        catch (std::exception& e)
+        {
+            log<level::ERR>("Unable to update PEL host transmission state",
+                            entry("PATH=%s", attr->second.path.c_str()),
+                            entry("ERROR=%s", e.what()));
+        }
+    }
+}
+
+void Repository::setPELHMCTransState(uint32_t pelID, TransmissionState state)
+{
+    LogID id{LogID::Pel{pelID}};
+    auto attr = std::find_if(_pelAttributes.begin(), _pelAttributes.end(),
+                             [&id](const auto& a) { return a.first == id; });
+
+    if ((attr != _pelAttributes.end()) && (attr->second.hmcState != state))
+    {
+        PELUpdateFunc func = [state](PEL& pel) {
+            pel.setHMCTransmissionState(state);
+        };
+
+        try
+        {
+            updatePEL(attr->second.path, func);
+
+            attr->second.hmcState = state;
+        }
+        catch (std::exception& e)
+        {
+            log<level::ERR>("Unable to update PEL HMC transmission state",
+                            entry("PATH=%s", attr->second.path.c_str()),
+                            entry("ERROR=%s", e.what()));
+        }
+    }
+}
+
+void Repository::updatePEL(const fs::path& path, PELUpdateFunc updateFunc)
+{
+    std::ifstream file{path};
+    std::vector<uint8_t> data{std::istreambuf_iterator<char>(file),
+                              std::istreambuf_iterator<char>()};
+    file.close();
+
+    PEL pel{data};
+
+    if (pel.valid())
+    {
+        updateFunc(pel);
+
+        write(pel, path);
+    }
+    else
+    {
+        throw std::runtime_error(
+            "Unable to read a valid PEL when trying to update it");
+    }
+}
+
 } // namespace pels
 } // namespace openpower
diff --git a/extensions/openpower-pels/repository.hpp b/extensions/openpower-pels/repository.hpp
index 70e8e61..b6ac545 100644
--- a/extensions/openpower-pels/repository.hpp
+++ b/extensions/openpower-pels/repository.hpp
@@ -261,7 +261,42 @@
     std::optional<std::reference_wrapper<const PELAttributes>>
         getPELAttributes(const LogID& id) const;
 
+    /**
+     * @brief Sets the host transmission state on a PEL file
+     *
+     * Writes the host transmission state field in the User Header
+     * section in the PEL data specified by the ID.
+     *
+     * @param[in] pelID - The PEL ID
+     * @param[in] state - The state to write
+     */
+    void setPELHostTransState(uint32_t pelID, TransmissionState state);
+
+    /**
+     * @brief Sets the HMC transmission state on a PEL file
+     *
+     * Writes the HMC transmission state field in the User Header
+     * section in the PEL data specified by the ID.
+     *
+     * @param[in] pelID - The PEL ID
+     * @param[in] state - The state to write
+     */
+    void setPELHMCTransState(uint32_t pelID, TransmissionState state);
+
   private:
+    using PELUpdateFunc = std::function<void(PEL&)>;
+
+    /**
+     * @brief Lets a function modify a PEL and saves the results
+     *
+     * Runs updateFunc (a void(PEL&) function) on the PEL data
+     * on the file specified, and writes the results back to the file.
+     *
+     * @param[in] path - The file path to use
+     * @param[in] updateFunc - The function to run to update the PEL.
+     */
+    void updatePEL(const std::filesystem::path& path, PELUpdateFunc updateFunc);
+
     /**
      * @brief Finds an entry in the _pelAttributes map.
      *
diff --git a/test/openpower-pels/repository_test.cpp b/test/openpower-pels/repository_test.cpp
index 8596840..5c4db47 100644
--- a/test/openpower-pels/repository_test.cpp
+++ b/test/openpower-pels/repository_test.cpp
@@ -300,3 +300,89 @@
         EXPECT_FALSE(a);
     }
 }
+
+TEST_F(RepositoryTest, TestSetHostState)
+{
+    // Add a PEL to the repo
+    auto data = pelDataFactory(TestPELType::pelSimple);
+    auto pel = std::make_unique<PEL>(data);
+    using ID = Repository::LogID;
+    ID id{ID::Pel(pel->id())};
+
+    {
+        Repository repo{repoPath};
+
+        repo.add(pel);
+
+        auto a = repo.getPELAttributes(id);
+        EXPECT_EQ((*a).get().hostState, TransmissionState::newPEL);
+
+        repo.setPELHostTransState(pel->id(), TransmissionState::acked);
+
+        // First, check the attributes
+        a = repo.getPELAttributes(id);
+        EXPECT_EQ((*a).get().hostState, TransmissionState::acked);
+
+        // Next, check the PEL data itself
+        auto pelData = repo.getPELData(id);
+        PEL newPEL{*pelData};
+        EXPECT_EQ(newPEL.hostTransmissionState(), TransmissionState::acked);
+    }
+
+    {
+        // Now restore, and check again
+        Repository repo{repoPath};
+
+        // First, check the attributes
+        auto a = repo.getPELAttributes(id);
+        EXPECT_EQ((*a).get().hostState, TransmissionState::acked);
+
+        // Next, check the PEL data itself
+        auto pelData = repo.getPELData(id);
+        PEL newPEL{*pelData};
+        EXPECT_EQ(newPEL.hostTransmissionState(), TransmissionState::acked);
+    }
+}
+
+TEST_F(RepositoryTest, TestSetHMCState)
+{
+    // Add a PEL to the repo
+    auto data = pelDataFactory(TestPELType::pelSimple);
+    auto pel = std::make_unique<PEL>(data);
+    using ID = Repository::LogID;
+    ID id{ID::Pel(pel->id())};
+
+    {
+        Repository repo{repoPath};
+
+        repo.add(pel);
+
+        auto a = repo.getPELAttributes(id);
+        EXPECT_EQ((*a).get().hmcState, TransmissionState::newPEL);
+
+        repo.setPELHMCTransState(pel->id(), TransmissionState::acked);
+
+        // First, check the attributes
+        a = repo.getPELAttributes(id);
+        EXPECT_EQ((*a).get().hmcState, TransmissionState::acked);
+
+        // Next, check the PEL data itself
+        auto pelData = repo.getPELData(id);
+        PEL newPEL{*pelData};
+        EXPECT_EQ(newPEL.hmcTransmissionState(), TransmissionState::acked);
+    }
+
+    {
+        // Now restore, and check again
+        Repository repo{repoPath};
+
+        // First, check the attributes
+        auto a = repo.getPELAttributes(id);
+        EXPECT_EQ((*a).get().hmcState, TransmissionState::acked);
+
+        // Next, check the PEL data itself
+        auto pelData = repo.getPELData(id);
+        PEL newPEL{*pelData};
+        EXPECT_EQ(newPEL.hmcTransmissionState(), TransmissionState::acked);
+    }
+}