Implement config parsing

Add a configuration json parsing function to read the configuration file
from /usr/share/binaryblob/config.json for which system file and location
to serialize/deserialize the binarystore blobs. Add a simple unit test for
parsing configs as well.

Signed-off-by: Kun Yi <kunyi731@gmail.com>
Change-Id: Ie86d622e83991365bc202d659f5860ff01190311
diff --git a/configure.ac b/configure.ac
index 2b09776..5ea68ed 100644
--- a/configure.ac
+++ b/configure.ac
@@ -24,6 +24,7 @@
 PKG_CHECK_MODULES([PHOSPHOR_LOGGING], [phosphor-logging],, [AC_MSG_ERROR([Could not find phosphor-logging...openbmc/phosphor-logging package required])])
 AC_CHECK_HEADER([blobs-ipmid], [AC_MSG_ERROR(["phosphor-ipmi-blobs required and not found."])])
 AC_CHECK_HEADER(boost/endian/arithmetic.hpp, [])
+AC_CHECK_HEADER(nlohmann/json.hpp, [])
 
 # Check/set gtest specific functions.
 PKG_CHECK_MODULES([GTEST], [gtest], [], [AC_MSG_NOTICE([gtest not found, tests will not build])])
diff --git a/main.cpp b/main.cpp
index 97b3aa9..b5964b8 100644
--- a/main.cpp
+++ b/main.cpp
@@ -1,7 +1,12 @@
 #include "handler.hpp"
+#include "parse_config.hpp"
+#include "sys_file.hpp"
 
 #include <blobs-ipmid/blobs.hpp>
+#include <exception>
+#include <fstream>
 #include <memory>
+#include <phosphor-logging/elog.hpp>
 
 #ifdef __cplusplus
 extern "C" {
@@ -17,7 +22,57 @@
 }
 #endif
 
+/* Configuration file path */
+constexpr auto blobConfigPath = "/usr/share/binaryblob/config.json";
+
 std::unique_ptr<blobs::GenericBlobInterface> createHandler()
 {
-    return std::make_unique<blobs::BinaryStoreBlobHandler>();
+    using namespace phosphor::logging;
+    using nlohmann::json;
+
+    std::ifstream input(blobConfigPath);
+    json j;
+
+    try
+    {
+        input >> j;
+    }
+    catch (const std::exception& e)
+    {
+        log<level::ERR>("Failed to parse config into json",
+                        entry("ERR=%s", e.what()));
+        return nullptr;
+    }
+
+    // Construct binary blobs from config and add to handler
+    auto handler = std::make_unique<blobs::BinaryStoreBlobHandler>();
+
+    for (const auto& element : j)
+    {
+        conf::BinaryBlobConfig config;
+        try
+        {
+            conf::parseFromConfigFile(element, config);
+        }
+        catch (const std::exception& e)
+        {
+            log<level::ERR>("Encountered error when parsing config file",
+                            entry("ERR=%s", e.what()));
+            return nullptr;
+        }
+
+        log<level::INFO>("Loading from config with",
+                         entry("BASE_ID=%s", config.blobBaseId.c_str()),
+                         entry("FILE=%s", config.sysFilePath.c_str()),
+                         entry("MAX_SIZE=%llx", static_cast<unsigned long long>(
+                                                    config.maxSizeBytes)));
+
+        auto file = std::make_unique<binstore::SysFileImpl>(config.sysFilePath,
+                                                            config.offsetBytes);
+
+        handler->addNewBinaryStore(binstore::BinaryStore::createFromConfig(
+            config.blobBaseId, std::move(file), config.maxSizeBytes));
+    }
+
+    return std::move(handler);
 }
diff --git a/parse_config.hpp b/parse_config.hpp
new file mode 100644
index 0000000..b778923
--- /dev/null
+++ b/parse_config.hpp
@@ -0,0 +1,35 @@
+#pragma once
+
+#include <cstdint>
+#include <nlohmann/json.hpp>
+#include <string>
+
+using std::uint32_t;
+using json = nlohmann::json;
+
+namespace conf
+{
+
+struct BinaryBlobConfig
+{
+    std::string blobBaseId;  // Required
+    std::string sysFilePath; // Required
+    uint32_t offsetBytes;    // Optional
+    uint32_t maxSizeBytes;   // Optional
+};
+
+/**
+ * @brief Parse parameters from a config json
+ * @param j: input json object
+ * @param config: output BinaryBlobConfig
+ * @throws: exception if config doesn't have required fields
+ */
+static inline void parseFromConfigFile(const json& j, BinaryBlobConfig& config)
+{
+    j.at("blobBaseId").get_to(config.blobBaseId);
+    j.at("sysFilePath").get_to(config.sysFilePath);
+    config.offsetBytes = j.value("offsetBytes", 0);
+    config.maxSizeBytes = j.value("maxSizeBytes", 0);
+}
+
+} // namespace conf
diff --git a/test/Makefile.am b/test/Makefile.am
index 2e36c8e..3d53916 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -13,6 +13,7 @@
 
 # Run all 'check' test programs
 check_PROGRAMS = \
+	parse_config_unittest \
 	sys_file_unittest \
 	handler_commit_unittest \
 	handler_open_unittest \
@@ -20,6 +21,8 @@
 	handler_unittest
 TESTS = $(check_PROGRAMS)
 
+parse_config_unittest_SOURCES = parse_config_unittest.cpp
+
 sys_file_unittest_SOURCES = sys_file_unittest.cpp
 sys_file_unittest_LDADD = $(top_builddir)/sys_file.o
 
diff --git a/test/parse_config_unittest.cpp b/test/parse_config_unittest.cpp
new file mode 100644
index 0000000..b450477
--- /dev/null
+++ b/test/parse_config_unittest.cpp
@@ -0,0 +1,66 @@
+#include "parse_config.hpp"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using json = nlohmann::json;
+using namespace conf;
+
+TEST(ParseConfigTest, ExceptionWhenMissingRequiredFields)
+{
+    auto j = R"(
+    {
+      "blobBaseId": "/test/"
+    }
+  )"_json;
+
+    BinaryBlobConfig config;
+
+    EXPECT_THROW(parseFromConfigFile(j, config), std::exception);
+}
+
+TEST(ParseConfigTest, TestSimpleConfig)
+{
+    auto j = R"(
+    {
+      "blobBaseId": "/test/",
+      "sysFilePath": "/sys/fake/path",
+      "offsetBytes": 32,
+      "maxSizeBytes": 2
+    }
+  )"_json;
+
+    BinaryBlobConfig config;
+
+    EXPECT_NO_THROW(parseFromConfigFile(j, config));
+    EXPECT_EQ(config.blobBaseId, "/test/");
+    EXPECT_EQ(config.sysFilePath, "/sys/fake/path");
+    EXPECT_EQ(config.offsetBytes, 32);
+    EXPECT_EQ(config.maxSizeBytes, 2);
+}
+
+TEST(ParseConfigTest, TestConfigArray)
+{
+    auto j = R"(
+    [{
+      "blobBaseId": "/test/",
+      "sysFilePath": "/sys/fake/path",
+      "offsetBytes": 32,
+      "maxSizeBytes": 32
+     },
+     {
+       "blobBaseId": "/test/",
+       "sysFilePath": "/another/path"
+    }]
+  )"_json;
+
+    for (auto& element : j)
+    {
+        BinaryBlobConfig config;
+
+        EXPECT_NO_THROW(parseFromConfigFile(element, config));
+        EXPECT_EQ(config.blobBaseId, "/test/");
+        EXPECT_TRUE(config.offsetBytes == 32 || config.offsetBytes == 0);
+        EXPECT_TRUE(config.maxSizeBytes == 32 || config.maxSizeBytes == 0);
+    }
+}