Implement File I/O attribute table

This patch provides the framework for PLDM File I/O commands. Interface is
provided to lookup file information based on the file handle. This interface
will be consumed by the ReadFileIntoMemory and WriteFileFromMemory commands.
Interface is provided to read the file attribute table to be used by the
GetFileTable command.

This commit doesn't have support for implementing UpdateFileTable command.

Change-Id: Iad9c7f05589d26401bf7083ec87d13e3296a485b
Signed-off-by: Tom Joseph <tomjoseph@in.ibm.com>
diff --git a/libpldm/pldm_types.h b/libpldm/pldm_types.h
index 7ff1527..bd8197f 100644
--- a/libpldm/pldm_types.h
+++ b/libpldm/pldm_types.h
@@ -27,4 +27,42 @@
 	uint8_t alpha;
 } __attribute__((packed)) ver32_t;
 
+typedef union {
+	uint32_t value;
+	struct {
+		uint8_t bit0 : 1;
+		uint8_t bit1 : 1;
+		uint8_t bit2 : 1;
+		uint8_t bit3 : 1;
+		uint8_t bit4 : 1;
+		uint8_t bit5 : 1;
+		uint8_t bit6 : 1;
+		uint8_t bit7 : 1;
+		uint8_t bit8 : 1;
+		uint8_t bit9 : 1;
+		uint8_t bit10 : 1;
+		uint8_t bit11 : 1;
+		uint8_t bit12 : 1;
+		uint8_t bit13 : 1;
+		uint8_t bit14 : 1;
+		uint8_t bit15 : 1;
+		uint8_t bit16 : 1;
+		uint8_t bit17 : 1;
+		uint8_t bit18 : 1;
+		uint8_t bit19 : 1;
+		uint8_t bit20 : 1;
+		uint8_t bit21 : 1;
+		uint8_t bit22 : 1;
+		uint8_t bit23 : 1;
+		uint8_t bit24 : 1;
+		uint8_t bit25 : 1;
+		uint8_t bit26 : 1;
+		uint8_t bit27 : 1;
+		uint8_t bit28 : 1;
+		uint8_t bit29 : 1;
+		uint8_t bit30 : 1;
+		uint8_t bit31 : 1;
+	} __attribute__((packed)) bits;
+} bitfield32_t;
+
 #endif /* PLDM_TYPES_H */
diff --git a/libpldmresponder/Makefile.am b/libpldmresponder/Makefile.am
index c043c8b..0e880b9 100644
--- a/libpldmresponder/Makefile.am
+++ b/libpldmresponder/Makefile.am
@@ -1,7 +1,8 @@
 libpldmoemresponder_LTLIBRARIES = libpldmoemresponder.la
 libpldmoemresponderdir = ${libdir}
 libpldmoemresponder_la_SOURCES = \
-	file_io.cpp
+	file_io.cpp \
+	file_table.cpp
 
 libpldmoemresponder_la_LIBADD = \
 	../libpldm/libpldmoem.la
diff --git a/libpldmresponder/file_table.cpp b/libpldmresponder/file_table.cpp
new file mode 100644
index 0000000..85f417c
--- /dev/null
+++ b/libpldmresponder/file_table.cpp
@@ -0,0 +1,119 @@
+#include "file_table.hpp"
+
+#include <boost/crc.hpp>
+#include <fstream>
+#include <phosphor-logging/log.hpp>
+
+namespace pldm
+{
+
+namespace filetable
+{
+
+using namespace phosphor::logging;
+
+FileTable::FileTable(const std::string& fileTableConfigPath)
+{
+    std::ifstream jsonFile(fileTableConfigPath);
+    if (!jsonFile.is_open())
+    {
+        log<level::ERR>("File table config file does not exist",
+                        entry("FILE=%s", fileTableConfigPath.c_str()));
+        return;
+    }
+
+    auto data = Json::parse(jsonFile, nullptr, false);
+    if (data.is_discarded())
+    {
+        log<level::ERR>("Parsing config file failed");
+        return;
+    }
+
+    uint16_t fileNameLength = 0;
+    uint32_t fileSize = 0;
+    uint32_t traits = 0;
+    size_t tableSize = 0;
+    Handle handle = 0;
+    auto iter = fileTable.begin();
+
+    // Iterate through each JSON object in the config file
+    for (const auto& record : data)
+    {
+        constexpr auto path = "path";
+        constexpr auto fileTraits = "file_traits";
+
+        std::string filepath = record.value(path, "");
+        traits = static_cast<uint32_t>(record.value(fileTraits, 0));
+
+        fs::path fsPath(filepath);
+        if (!fs::is_regular_file(fsPath))
+        {
+            continue;
+        }
+
+        fileNameLength =
+            static_cast<uint16_t>(fsPath.filename().string().size());
+        fileSize = static_cast<uint32_t>(fs::file_size(fsPath));
+        tableSize = fileTable.size();
+
+        fileTable.resize(tableSize + sizeof(handle) + sizeof(fileNameLength) +
+                         fileNameLength + sizeof(fileSize) + sizeof(traits));
+        iter = fileTable.begin() + tableSize;
+
+        // Populate the file table with the contents of the JSON entry
+        std::copy_n(reinterpret_cast<uint8_t*>(&handle), sizeof(handle), iter);
+        std::advance(iter, sizeof(handle));
+
+        std::copy_n(reinterpret_cast<uint8_t*>(&fileNameLength),
+                    sizeof(fileNameLength), iter);
+        std::advance(iter, sizeof(fileNameLength));
+
+        std::copy_n(reinterpret_cast<const uint8_t*>(fsPath.filename().c_str()),
+                    fileNameLength, iter);
+        std::advance(iter, fileNameLength);
+
+        std::copy_n(reinterpret_cast<uint8_t*>(&fileSize), sizeof(fileSize),
+                    iter);
+        std::advance(iter, sizeof(fileSize));
+
+        std::copy_n(reinterpret_cast<uint8_t*>(&traits), sizeof(traits), iter);
+        std::advance(iter, sizeof(traits));
+
+        // Create the file entry for the JSON entry
+        FileEntry entry{};
+        entry.handle = handle;
+        entry.fsPath = std::move(fsPath);
+        entry.traits.value = traits;
+
+        // Insert the file entries in the map
+        tableEntries.emplace(handle, std::move(entry));
+        handle++;
+    }
+
+    constexpr uint8_t padWidth = 4;
+    tableSize = fileTable.size();
+    // Add pad bytes
+    if ((tableSize % padWidth) != 0)
+    {
+        padCount = padWidth - (tableSize % padWidth);
+        fileTable.resize(tableSize + padCount, 0);
+    }
+
+    // Calculate the checksum
+    boost::crc_32_type result;
+    result.process_bytes(fileTable.data(), fileTable.size());
+    checkSum = result.checksum();
+}
+
+Table FileTable::operator()() const
+{
+    Table table(fileTable);
+    table.resize(fileTable.size() + sizeof(checkSum));
+    auto iter = table.begin() + fileTable.size();
+    std::copy_n(reinterpret_cast<const uint8_t*>(&checkSum), sizeof(checkSum),
+                iter);
+    return table;
+}
+
+} // namespace filetable
+} // namespace pldm
diff --git a/libpldmresponder/file_table.hpp b/libpldmresponder/file_table.hpp
new file mode 100644
index 0000000..385f57b
--- /dev/null
+++ b/libpldmresponder/file_table.hpp
@@ -0,0 +1,115 @@
+#pragma once
+
+#include <stdint.h>
+
+#include <filesystem>
+#include <nlohmann/json.hpp>
+#include <vector>
+
+#include "libpldm/pldm_types.h"
+
+namespace pldm
+{
+
+namespace filetable
+{
+
+namespace fs = std::filesystem;
+using Handle = uint32_t;
+using Json = nlohmann::json;
+using Table = std::vector<uint8_t>;
+
+/** @struct FileEntry
+ *
+ *  Data structure for storing information regarding the files supported by
+ *  PLDM File I/O. The file handle is used to uniquely identify the file. The
+ *  traits provide information whether the file is Read only, Read/Write and
+ *  preserved across firmware upgrades.
+ */
+struct FileEntry
+{
+    Handle handle;       //!< File handle
+    fs::path fsPath;     //!< File path
+    bitfield32_t traits; //!< File traits
+};
+
+/** @class FileTable
+ *
+ *  FileTable class encapsulates the data related to files supported by PLDM
+ *  File I/O and provides interfaces to lookup files information based on the
+ *  file handle and extract the file attribute table. The file attribute table
+ *  comprises of metadata for files. Metadata includes the file handle, file
+ *  name, current file size and file traits.
+ */
+class FileTable
+{
+  public:
+    /** @brief The file table is initialised by parsing the config file
+     *         containing information about the files.
+     *
+     * @param[in] fileTableConfigPath - path to the json file containing
+     *                                  information
+     */
+    FileTable(const std::string& fileTableConfigPath);
+    FileTable() = default;
+    ~FileTable() = default;
+    FileTable(const FileTable&) = default;
+    FileTable& operator=(const FileTable&) = default;
+    FileTable(FileTable&&) = default;
+    FileTable& operator=(FileTable&&) = default;
+
+    /** @brief Get the file attribute table
+     *
+     * @return Table- contents of the file attribute table
+     */
+    Table operator()() const;
+
+    /** @brief Get the FileEntry at the file handle
+     *
+     * @param[in] handle - file handle
+     *
+     * @return FileEntry - file entry at the handle
+     */
+    FileEntry at(Handle handle) const
+    {
+        return tableEntries.at(handle);
+    }
+
+    /** @brief Check is file attribute table is empty
+     *
+     * @return bool - true if file attribute table is empty, false otherwise.
+     */
+    bool isEmpty() const
+    {
+        return fileTable.empty();
+    }
+
+    /** @brief Clear the file table contents
+     *
+     */
+    void clear()
+    {
+        tableEntries.clear();
+        fileTable.clear();
+        padCount = 0;
+        checkSum = 0;
+    }
+
+  private:
+    /** @brief handle to FileEntry mappings for lookups based on file handle */
+    std::unordered_map<Handle, FileEntry> tableEntries;
+
+    /** @brief file attribute table including the pad bytes, except the checksum
+     */
+    std::vector<uint8_t> fileTable;
+
+    /** @brief the pad count of the file attribute table, the number of pad
+     * bytes is between 0 and 3 */
+    uint8_t padCount = 0;
+
+    /** @brief the checksum of the file attribute table */
+    uint32_t checkSum = 0;
+};
+
+} // namespace filetable
+} // namespace pldm
diff --git a/test/Makefile.am b/test/Makefile.am
index 70da63a..d276ec4 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -38,6 +38,7 @@
 libpldmoemresponder_fileio_test_LDADD = \
 	$(top_builddir)/libpldm/base.o \
 	$(top_builddir)/libpldm/file_io.o \
-	$(top_builddir)/libpldmresponder/file_io.o
+	$(top_builddir)/libpldmresponder/file_io.o \
+	$(top_builddir)/libpldmresponder/file_table.o
 libpldmoemresponder_fileio_test_SOURCES = libpldmresponder_fileio_test.cpp
 
diff --git a/test/files/NVRAM-IMAGE b/test/files/NVRAM-IMAGE
new file mode 100644
index 0000000..06d7405
--- /dev/null
+++ b/test/files/NVRAM-IMAGE
Binary files differ
diff --git a/test/files/NVRAM-IMAGE-CKSUM b/test/files/NVRAM-IMAGE-CKSUM
new file mode 100644
index 0000000..01d633b
--- /dev/null
+++ b/test/files/NVRAM-IMAGE-CKSUM
Binary files differ
diff --git a/test/libpldmresponder_fileio_test.cpp b/test/libpldmresponder_fileio_test.cpp
index 060b0ed..424e09c 100644
--- a/test/libpldmresponder_fileio_test.cpp
+++ b/test/libpldmresponder_fileio_test.cpp
@@ -1,4 +1,9 @@
 #include "libpldmresponder/file_io.hpp"
+#include "libpldmresponder/file_table.hpp"
+
+#include <filesystem>
+#include <fstream>
+#include <nlohmann/json.hpp>
 
 #include "libpldm/base.h"
 #include "libpldm/file_io.h"
@@ -6,6 +11,7 @@
 #include <gmock/gmock-matchers.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+
 #define SD_JOURNAL_SUPPRESS_LOCATION
 
 #include <systemd/sd-journal.h>
@@ -28,6 +34,72 @@
 }
 }
 
+namespace fs = std::filesystem;
+using Json = nlohmann::json;
+using namespace pldm::filetable;
+
+class TestFileTable : public testing::Test
+{
+  public:
+    void SetUp() override
+    {
+        // Create a temporary directory to hold the config file and files to
+        // populate the file table.
+        char tmppldm[] = "/tmp/pldm_fileio_table.XXXXXX";
+        dir = fs::path(mkdtemp(tmppldm));
+
+        // Copy the sample image files to the directory
+        fs::copy("./files", dir);
+
+        imageFile = dir / "NVRAM-IMAGE";
+        auto jsonObjects = Json::array();
+        auto obj = Json::object();
+        obj["path"] = imageFile.c_str();
+        obj["file_traits"] = 1;
+
+        jsonObjects.push_back(obj);
+        obj.clear();
+        cksumFile = dir / "NVRAM-IMAGE-CKSUM";
+        obj["path"] = cksumFile.c_str();
+        obj["file_traits"] = 4;
+        jsonObjects.push_back(obj);
+
+        fileTableConfig = dir / "configFile.json";
+        std::ofstream file(fileTableConfig.c_str());
+        file << std::setw(4) << jsonObjects << std::endl;
+    }
+
+    void TearDown() override
+    {
+        fs::remove_all(dir);
+    }
+
+    fs::path dir;
+    fs::path imageFile;
+    fs::path cksumFile;
+    fs::path fileTableConfig;
+
+    // <4 bytes - File handle - 0 (0x00 0x00 0x00 0x00)>,
+    // <2 bytes - Filename length - 11 (0x0b 0x00>
+    // <11 bytes - Filename - ASCII for NVRAM-IMAGE>
+    // <4 bytes - File size - 1024 (0x00 0x04 0x00 0x00)>
+    // <4 bytes - File traits - 1 (0x01 0x00 0x00 0x00)>
+    // <4 bytes - File handle - 1 (0x01 0x00 0x00 0x00)>,
+    // <2 bytes - Filename length - 17 (0x11 0x00>
+    // <17 bytes - Filename - ASCII for NVRAM-IMAGE-CKSUM>
+    // <4 bytes - File size - 16 (0x0f 0x00 0x00 0x00)>
+    // <4 bytes - File traits - 4 (0x04 0x00 0x00 0x00)>
+    // No pad bytes added since the length for both the file entries in the
+    // table is 56, which is a multiple of 4.
+    // <4 bytes - Checksum - 2088303182(0x4e 0xfa 0x78 0x7c)>
+    Table attrTable = {
+        0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x4e, 0x56, 0x52, 0x41, 0x4d, 0x2d,
+        0x49, 0x4d, 0x41, 0x47, 0x45, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00,
+        0x00, 0x01, 0x00, 0x00, 0x00, 0x11, 0x00, 0x4e, 0x56, 0x52, 0x41, 0x4d,
+        0x2d, 0x49, 0x4d, 0x41, 0x47, 0x45, 0x2d, 0x43, 0x4b, 0x53, 0x55, 0x4d,
+        0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x4e, 0xfa, 0x78, 0x7c};
+};
+
 namespace pldm
 {
 
@@ -187,3 +259,44 @@
     responsePtr = reinterpret_cast<pldm_msg*>(response.data());
     ASSERT_EQ(responsePtr->payload[0], PLDM_INVALID_WRITE_LENGTH);
 }
+
+TEST(FileTable, ConfigNotExist)
+{
+    logs.clear();
+    FileTable tableObj("");
+    EXPECT_EQ(logs.size(), 1);
+}
+
+TEST_F(TestFileTable, ValidateFileEntry)
+{
+    FileTable tableObj(fileTableConfig.c_str());
+
+    // Test file handle 0, the file size is 1K bytes.
+    auto value = tableObj.at(0);
+    ASSERT_EQ(value.handle, 0);
+    ASSERT_EQ(strcmp(value.fsPath.c_str(), imageFile.c_str()), 0);
+    ASSERT_EQ(static_cast<uint32_t>(fs::file_size(value.fsPath)), 1024);
+    ASSERT_EQ(value.traits.value, 1);
+    ASSERT_EQ(true, fs::exists(value.fsPath));
+
+    // Test file handle 1, the file size is 16 bytes
+    auto value1 = tableObj.at(1);
+    ASSERT_EQ(value1.handle, 1);
+    ASSERT_EQ(strcmp(value1.fsPath.c_str(), cksumFile.c_str()), 0);
+    ASSERT_EQ(static_cast<uint32_t>(fs::file_size(value1.fsPath)), 16);
+    ASSERT_EQ(value1.traits.value, 4);
+    ASSERT_EQ(true, fs::exists(value1.fsPath));
+
+    // Test invalid file handle
+    ASSERT_THROW(tableObj.at(2), std::out_of_range);
+}
+
+TEST_F(TestFileTable, ValidateFileTable)
+{
+    FileTable tableObj(fileTableConfig.c_str());
+
+    // Validate file attribute table
+    auto table = tableObj();
+    ASSERT_EQ(true,
+              std::equal(attrTable.begin(), attrTable.end(), table.begin()));
+}