Add a class to handle ExternalStorer file writes.

Signed-off-by: Kasun Athukorala <kasunath@google.com>
Change-Id: Ic1092a6a1da3375f595774018abfecd08a3cb7d8
diff --git a/src/rde/external_storer_file.cpp b/src/rde/external_storer_file.cpp
new file mode 100644
index 0000000..0768134
--- /dev/null
+++ b/src/rde/external_storer_file.cpp
@@ -0,0 +1,180 @@
+#include "rde/external_storer_file.hpp"
+
+#include <fmt/format.h>
+
+#include <boost/uuid/uuid.hpp>
+#include <boost/uuid/uuid_io.hpp>
+
+#include <fstream>
+#include <string_view>
+
+namespace bios_bmc_smm_error_logger
+{
+namespace rde
+{
+
+bool ExternalStorerFileWriter::createFolder(const std::string& folderPath) const
+{
+    std::filesystem::path path(folderPath);
+    if (!std::filesystem::is_directory(path))
+    {
+        if (!std::filesystem::create_directories(path))
+        {
+            fmt::print(stderr, "Failed to create a folder at {}\n", folderPath);
+            return false;
+        }
+    }
+    return true;
+}
+
+bool ExternalStorerFileWriter::createFile(const std::string& folderPath,
+                                          const nlohmann::json& jsonPdr) const
+{
+    if (!createFolder(folderPath))
+    {
+        return false;
+    }
+    std::filesystem::path path(folderPath);
+    path /= "index.json";
+    // If the file already exist, overwrite it.
+    std::ofstream output(path);
+    output << jsonPdr;
+    output.close();
+    return true;
+}
+
+ExternalStorerFileInterface::ExternalStorerFileInterface(
+    std::string_view rootPath,
+    std::unique_ptr<FileHandlerInterface> fileHandler) :
+    rootPath(rootPath),
+    fileHandler(std::move(fileHandler)), logServiceId("")
+{}
+
+bool ExternalStorerFileInterface::publishJson(std::string_view jsonStr)
+{
+    nlohmann::json jsonDecoded;
+    try
+    {
+        jsonDecoded = nlohmann::json::parse(jsonStr);
+    }
+    catch (nlohmann::json::parse_error& e)
+    {
+        fmt::print(stderr, "JSON parse error: \n{}\n", e.what());
+        return false;
+    }
+
+    // We need to know the type to determine how to process the decoded JSON
+    // output.
+    if (!jsonDecoded.contains("@odata.type"))
+    {
+        fmt::print(stderr, "@odata.type field doesn't exist in:\n {}\n",
+                   jsonDecoded.dump(4));
+        return false;
+    }
+
+    auto schemaType = getSchemaType(jsonDecoded);
+    if (schemaType == JsonPdrType::logEntry)
+    {
+        return processLogEntry(jsonDecoded);
+    }
+    if (schemaType == JsonPdrType::logService)
+    {
+        return processLogService(jsonDecoded);
+    }
+    return processOtherTypes(jsonDecoded);
+}
+
+JsonPdrType ExternalStorerFileInterface::getSchemaType(
+    const nlohmann::json& jsonSchema) const
+{
+    auto logEntryFound =
+        std::string(jsonSchema["@odata.type"]).find("LogEntry");
+    if (logEntryFound != std::string::npos)
+    {
+        return JsonPdrType::logEntry;
+    }
+
+    auto logServiceFound =
+        std::string(jsonSchema["@odata.type"]).find("LogService");
+    if (logServiceFound != std::string::npos)
+    {
+        return JsonPdrType::logService;
+    }
+
+    return JsonPdrType::other;
+}
+
+bool ExternalStorerFileInterface::processLogEntry(nlohmann::json& logEntry)
+{
+    // TODO: Add policies for LogEntry retention.
+    // https://github.com/openbmc/bios-bmc-smm-error-logger/issues/1.
+    if (logServiceId.empty())
+    {
+        fmt::print(stderr, "First need a LogService PDR with a new UUID.\n");
+        return false;
+    }
+
+    std::string id = boost::uuids::to_string(randomGen());
+    std::string path = "/redfish/v1/Systems/system/LogServices/" +
+                       logServiceId + "/Entries/" + id;
+
+    // Populate the "Id" with the UUID we generated.
+    logEntry["Id"] = id;
+    // Remove the @odata.id from the JSON since ExternalStorer will fill it for
+    // a client.
+    logEntry.erase("@odata.id");
+
+    return createFile(path, logEntry);
+}
+
+bool ExternalStorerFileInterface::processLogService(
+    const nlohmann::json& logService)
+{
+    if (!logService.contains("@odata.id"))
+    {
+        fmt::print(stderr, "@odata.id field doesn't exist in:\n {}\n",
+                   logService.dump(4));
+        return false;
+    }
+
+    if (!logService.contains("Id"))
+    {
+        fmt::print(stderr, "Id field doesn't exist in:\n {}\n",
+                   logService.dump(4));
+        return false;
+    }
+
+    logServiceId = logService["Id"].get<std::string>();
+
+    if (!createFile(logService["@odata.id"].get<std::string>(), logService))
+    {
+        fmt::print(stderr, "Failed to create LogService index file for:\n{}\n",
+                   logService.dump(4));
+        return false;
+    }
+    // ExternalStorer needs a .../Entries/index.json file with no data.
+    nlohmann::json jEmpty = "{}"_json;
+    return createFile(logService["@odata.id"].get<std::string>() + "/Entries",
+                      jEmpty);
+}
+
+bool ExternalStorerFileInterface::processOtherTypes(
+    const nlohmann::json& jsonPdr) const
+{
+    if (!jsonPdr.contains("@odata.id"))
+    {
+        fmt::print(stderr, "@odata.id field doesn't exist in:\n {}\n",
+                   jsonPdr.dump(4));
+        return false;
+    }
+    return createFile(jsonPdr["@odata.id"].get<std::string>(), jsonPdr);
+}
+
+bool ExternalStorerFileInterface::createFile(
+    const std::string& subPath, const nlohmann::json& jsonPdr) const
+{
+    return fileHandler->createFile(rootPath + subPath, jsonPdr);
+}
+
+} // namespace rde
+} // namespace bios_bmc_smm_error_logger