boot-time-blob: exporting boot info to blob

This CL exports boot info via ipmi blob to external.
Including the checkpoint, durations and statistic infomation.
The format is proto 3.

Tested:
\# Read `/btm/host/0` blob from host and ensure the data can be encoded
correctly.
\# Blob raw data (proto 3 format)
```
00000000  12 19 0a 0b 52 65 62 6f  6f 74 53 74 61 72 74 10  |....RebootStart.|
00000010  80 e9 ef b0 ab 31 18 be  bc ab 26 12 16 0a 08 53  |.....1....&....S|
00000020  68 75 74 64 6f 77 6e 10  dc 92 bf b7 ab 31 18 c4  |hutdown......1..|
00000030  c4 ab 26 12 1c 0a 0e 4b  65 72 6e 65 6c 53 68 75  |..&....KernelShu|
00000040  74 64 6f 77 6e 10 c5 a2  bf b7 ab 31 18 a8 d4 ab  |tdown......1....|
00000050  26 12 10 0a 02 53 35 10  d9 a2 bf b7 ab 31 18 bc  |&....S5......1..|
00000060  d4 ab 26 12 10 0a 02 53  30 10 b6 ba bf b7 ab 31  |..&....S0......1|
00000070  18 9c ec ab 26 12 19 0a  0b 54 6f 5f 46 69 72 6d  |....&....To_Firm|
00000080  77 61 72 65 10 d2 c2 bf  b7 ab 31 18 b6 f4 ab 26  |ware......1....&|
00000090  12 16 0a 08 46 69 72 6d  77 61 72 65 10 f2 e1 bf  |....Firmware....|
000000a0  b7 ab 31 18 d6 93 ac 26  12 17 0a 09 54 6f 5f 4c  |..1....&....To_L|
000000b0  6f 61 64 65 72 10 c5 f9  bf b7 ab 31 18 ac ab ac  |oader......1....|
000000c0  26 12 14 0a 06 4c 6f 61  64 65 72 10 fd 90 c0 b7  |&....Loader.....|
000000d0  ab 31 18 e4 c2 ac 26 12  17 0a 09 54 6f 5f 4b 65  |.1....&....To_Ke|
000000e0  72 6e 65 6c 10 b0 b8 c0  b7 ab 31 18 94 ea ac 26  |rnel......1....&|
000000f0  12 14 0a 06 4b 65 72 6e  65 6c 10 80 c8 c0 b7 ab  |....Kernel......|
00000100  31 18 e4 f9 ac 26 12 17  0a 09 54 6f 5f 49 6e 69  |1....&....To_Ini|
00000110  74 72 64 10 f8 fe c0 b7  ab 31 18 da b0 ad 26 12  |trd......1....&.|
00000120  14 0a 06 49 6e 69 74 72  64 10 e0 86 c1 b7 ab 31  |...Initrd......1|
00000130  18 c2 b8 ad 26 12 1a 0a  0c 54 6f 5f 55 73 65 72  |....&....To_User|
00000140  73 70 61 63 65 10 80 e3  f1 b0 ab 31 18 e0 b8 ad  |space......1....|
00000150  26 12 17 0a 09 55 73 65  72 73 70 61 63 65 10 c8  |&....Userspace..|
00000160  c8 f2 b0 ab 31 18 a8 9e  ae 26 1a 08 0a 04 55 45  |....1....&....UE|
00000170  46 49 10 64 1a 0d 0a 08  4e 45 52 46 44 68 63 70  |FI.d....NERFDhcp|
00000180  10 c8 01 1a 10 0a 0b 4e  45 52 46 4e 65 74 62 6f  |.......NERFNetbo|
00000190  6f 74 10 ac 02 1a 11 0a  0c 4e 45 52 46 53 6d 61  |ot.......NERFSma|
000001a0  72 74 4e 49 43 10 90 03  1a 0c 0a 07 55 45 46 49  |rtNIC.......UEFI|
000001b0  53 65 63 10 f4 03 1a 0c  0a 07 55 45 46 49 50 65  |Sec.......UEFIPe|
000001c0  69 10 d8 04 1a 0c 0a 07  55 45 46 49 44 78 65 10  |i.......UEFIDxe.|
000001d0  bc 05 1a 10 0a 0b 42 4d  43 53 68 75 74 64 6f 77  |......BMCShutdow|
000001e0  6e 10 a0 06 1a 0e 0a 09  46 6c 61 73 68 42 49 4f  |n.......FlashBIO|
000001f0  53 10 84 07                                       |S...|
```

Change-Id: Ifb0e381d517c147ca6d086c033b4a5d918dc391b
Signed-off-by: Michael Shen <gpgpgp@google.com>
diff --git a/boot-time-blob/blob_handler.cpp b/boot-time-blob/blob_handler.cpp
new file mode 100644
index 0000000..6fa8d62
--- /dev/null
+++ b/boot-time-blob/blob_handler.cpp
@@ -0,0 +1,265 @@
+#include "blob_handler.hpp"
+
+#include <boot_time.pb.h>
+#include <fmt/printf.h>
+
+#include <sdbusplus/bus.hpp>
+
+#include <cstdint>
+#include <string>
+#include <string_view>
+#include <variant>
+#include <vector>
+
+namespace blobs
+{
+
+using CheckpointType = std::vector<std::tuple<std::string, int64_t, int64_t>>;
+using DurationType = std::vector<std::tuple<std::string, int64_t>>;
+
+constexpr static std::string_view kHostBlobPath = "/btm/host/0";
+constexpr static std::string_view kBMCBlobPath = "/btm/bmc";
+
+namespace BootTimeMonitor
+{
+constexpr std::string_view kService = "com.google.gbmc.boot_time_monitor";
+constexpr std::string_view kHostPath = "/xyz/openbmc_project/time/boot/host0";
+constexpr std::string_view kBMCPath = "/xyz/openbmc_project/time/boot/bmc";
+} // namespace BootTimeMonitor
+
+namespace Checkpoint
+{
+constexpr std::string_view kIface = "xyz.openbmc_project.Time.Boot.Checkpoint";
+constexpr std::string_view kMethod = "GetCheckpointList";
+} // namespace Checkpoint
+
+namespace Duration
+{
+constexpr std::string_view kIface = "xyz.openbmc_project.Time.Boot.Duration";
+constexpr std::string_view kMethod = "GetAdditionalDurations";
+} // namespace Duration
+
+namespace Statistic
+{
+constexpr std::string_view kIface = "org.freedesktop.DBus.Properties";
+constexpr std::string_view kMethod = "Get";
+constexpr std::string_view kParam1 = "xyz.openbmc_project.Time.Boot.Statistic";
+constexpr std::string_view kParam2 = "IsRebooting";
+} // namespace Statistic
+
+bool BlobHandler::canHandleBlob(const std::string& path)
+{
+    return (path == kHostBlobPath) || (path == kBMCBlobPath);
+}
+
+// A blob handler may have multiple Blobs.
+std::vector<std::string> BlobHandler::getBlobIds()
+{
+    return {kHostBlobPath.data(), kBMCBlobPath.data()};
+}
+
+// BmcBlobDelete (7) is not supported.
+bool BlobHandler::deleteBlob([[maybe_unused]] const std::string& path)
+{
+    return false;
+}
+
+// BmcBlobStat (8) (global stat) is not supported.
+bool BlobHandler::stat([[maybe_unused]] const std::string& path,
+                       [[maybe_unused]] BlobMeta* meta)
+{
+    return false;
+}
+
+// BmcBlobOpen(2) handler.
+bool BlobHandler::open(uint16_t session, uint16_t flags,
+                       const std::string& path)
+{
+    if (!isReadOnlyOpenFlags(flags))
+    {
+        fmt::print(stderr, "[{}] Flag is not read-only. flag={}\n",
+                   __FUNCTION__, flags);
+        return false;
+    }
+    if (!canHandleBlob(path))
+    {
+        fmt::print(stderr, "[{}] Can't handle path={}\n", __FUNCTION__, path);
+        return false;
+    }
+
+    sdbusplus::bus_t bus = sdbusplus::bus::new_default();
+    boottimeproto::BootTime btProto;
+    std::string btmPath;
+    if (path == kBMCBlobPath)
+    {
+        btmPath = BootTimeMonitor::kBMCPath;
+    }
+    else
+    {
+        btmPath = BootTimeMonitor::kHostPath;
+    }
+
+    // Get checkpoints
+    try
+    {
+        auto method = bus.new_method_call(
+            BootTimeMonitor::kService.data(), btmPath.c_str(),
+            Checkpoint::kIface.data(), Checkpoint::kMethod.data());
+        CheckpointType checkpoints;
+        bus.call(method).read(checkpoints);
+        for (auto checkpoint : checkpoints)
+        {
+            auto cpProto = btProto.add_checkpoints();
+            cpProto->set_name(std::get<0>(checkpoint));
+            cpProto->set_wall_time(std::get<1>(checkpoint));
+            cpProto->set_mono_time(std::get<2>(checkpoint));
+        }
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        fmt::print(
+            stderr,
+            "[{}] Failed in method call. service={}, path={}, Iface={}, method={}, error={}\n",
+            __FUNCTION__, BootTimeMonitor::kService.data(), btmPath.c_str(),
+            Checkpoint::kIface.data(), Checkpoint::kMethod.data(), e.what());
+        return false;
+    }
+
+    // Get durations
+    try
+    {
+        auto method = bus.new_method_call(
+            BootTimeMonitor::kService.data(), btmPath.c_str(),
+            Duration::kIface.data(), Duration::kMethod.data());
+        DurationType durations;
+        bus.call(method).read(durations);
+        for (auto duration : durations)
+        {
+            auto durProto = btProto.add_durations();
+            durProto->set_name(std::get<0>(duration));
+            durProto->set_duration(std::get<1>(duration));
+        }
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        fmt::print(
+            stderr,
+            "[{}] Failed in method call. service={}, path={}, Iface={}, method={}, error={}\n",
+            __FUNCTION__, BootTimeMonitor::kService.data(), btmPath.c_str(),
+            Duration::kIface.data(), Duration::kMethod.data(), e.what());
+        return false;
+    }
+
+    // Get `IsRebooting`
+    try
+    {
+        auto method = bus.new_method_call(
+            BootTimeMonitor::kService.data(), btmPath.c_str(),
+            Statistic::kIface.data(), Statistic::kMethod.data());
+        method.append(Statistic::kParam1.data(), Statistic::kParam2.data());
+
+        std::variant<bool> isRebooting;
+        bus.call(method).read(isRebooting);
+        btProto.set_is_rebooting(std::get<bool>(isRebooting));
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        fmt::print(
+            stderr,
+            "[{}] Failed in method call. service={}, path={}, Iface={}, method={}, param1={}, param2={}, error={}\n",
+            __FUNCTION__, BootTimeMonitor::kService.data(), btmPath.c_str(),
+            Statistic::kIface.data(), Statistic::kMethod.data(),
+            Statistic::kParam1.data(), Statistic::kParam2.data(), e.what());
+        return false;
+    }
+
+    std::vector<char> vec(btProto.ByteSizeLong());
+    if (!btProto.SerializeToArray(vec.data(), vec.size()))
+    {
+        fmt::print(stderr, "[{}]: Could not serialize protobuf to array\n",
+                   __FUNCTION__);
+        return false;
+    }
+
+    sessions[session] = std::move(vec);
+    return true;
+}
+
+// BmcBlobRead(3) handler.
+std::vector<uint8_t> BlobHandler::read(uint16_t session, uint32_t offset,
+                                       uint32_t requestedSize)
+{
+    auto it = sessions.find(session);
+    if (it == sessions.end())
+    {
+        return {};
+    }
+
+    if (offset + requestedSize > it->second.size())
+    {
+        return std::vector<uint8_t>(it->second.data() + offset,
+                                    it->second.data() + it->second.size());
+    }
+    return std::vector<uint8_t>(it->second.data() + offset,
+                                it->second.data() + offset + requestedSize);
+}
+
+// BmcBlobWrite(4) is not supported.
+bool BlobHandler::write([[maybe_unused]] uint16_t session,
+                        [[maybe_unused]] uint32_t offset,
+                        [[maybe_unused]] const std::vector<uint8_t>& data)
+{
+    return false;
+}
+
+// BmcBlobWriteMeta(10) is not supported.
+bool BlobHandler::writeMeta([[maybe_unused]] uint16_t session,
+                            [[maybe_unused]] uint32_t offset,
+                            [[maybe_unused]] const std::vector<uint8_t>& data)
+{
+    return false;
+}
+
+// BmcBlobCommit(5) is not supported.
+bool BlobHandler::commit([[maybe_unused]] uint16_t session,
+                         [[maybe_unused]] const std::vector<uint8_t>& data)
+{
+    return false;
+}
+
+// BmcBlobClose(6) handler.
+bool BlobHandler::close(uint16_t session)
+{
+    auto itr = sessions.find(session);
+    if (itr == sessions.end())
+    {
+        return false;
+    }
+    sessions.erase(itr);
+    return true;
+}
+
+// BmcBlobSessionStat(9) is not supported.
+bool BlobHandler::stat([[maybe_unused]] uint16_t session,
+                       [[maybe_unused]] BlobMeta* meta)
+{
+    return false;
+}
+
+bool BlobHandler::expire(uint16_t session)
+{
+    return close(session);
+}
+
+// Checks for a read-only flag.
+bool BlobHandler::isReadOnlyOpenFlags(const uint16_t flags)
+{
+    if (((flags & blobs::OpenFlags::read) == blobs::OpenFlags::read) &&
+        ((flags & blobs::OpenFlags::write) == 0))
+    {
+        return true;
+    }
+    return false;
+}
+
+} // namespace blobs
diff --git a/boot-time-blob/blob_handler.hpp b/boot-time-blob/blob_handler.hpp
new file mode 100644
index 0000000..bce4bb2
--- /dev/null
+++ b/boot-time-blob/blob_handler.hpp
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <blobs-ipmid/blobs.hpp>
+
+#include <cstdint>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace blobs
+{
+
+// This blob is a read-only blob can only handle single blob id.
+class BlobHandler : public GenericBlobInterface
+{
+  public:
+    BlobHandler() = default;
+
+    bool canHandleBlob(const std::string& path) override;
+    std::vector<std::string> getBlobIds() override;
+    bool deleteBlob(const std::string& path) override;
+    bool stat(const std::string& path, BlobMeta* meta) override;
+    bool open(uint16_t session, uint16_t flags,
+              const std::string& path) override;
+    std::vector<uint8_t> read(uint16_t session, uint32_t offset,
+                              uint32_t requestedSize) override;
+    bool write(uint16_t session, uint32_t offset,
+               const std::vector<uint8_t>& data) override;
+    bool writeMeta(uint16_t session, uint32_t offset,
+                   const std::vector<uint8_t>& data) override;
+    bool commit(uint16_t session, const std::vector<uint8_t>& data) override;
+    bool close(uint16_t session) override;
+    bool stat(uint16_t session, BlobMeta* meta) override;
+    bool expire(uint16_t session) override;
+
+  private:
+    bool isReadOnlyOpenFlags(const uint16_t flag);
+    std::unordered_map<uint16_t, std::vector<char>> sessions;
+};
+
+} // namespace blobs
diff --git a/boot-time-blob/blob_main.cpp b/boot-time-blob/blob_main.cpp
new file mode 100644
index 0000000..ba3a0c0
--- /dev/null
+++ b/boot-time-blob/blob_main.cpp
@@ -0,0 +1,21 @@
+#include "blob_handler.hpp"
+
+#include <blobs-ipmid/blobs.hpp>
+
+#include <memory>
+
+// Extern "C" is used due to the usage of dlopen() for loading IPMI handlers
+// and IPMI blob handlers. This happens in two steps:
+//
+// 1) ipmid loads all libraries in /usr/lib/ipmid-providers from its
+//    loadLibraries() function using dlopen().
+// 2) The blobs library, libblobcmds.so, loads all libraries in
+//    /usr/lib/blobs-ipmid from its loadLibraries() function. It uses dlsym()
+//    to locate the createHandler function by its un-mangled name
+//    "createHandler". Using extern "C" prevents its name from being mangled
+//    into, for example, "_Z13createHandlerv".
+
+extern "C" std::unique_ptr<blobs::GenericBlobInterface> createHandler()
+{
+    return std::make_unique<blobs::BlobHandler>();
+}
diff --git a/boot-time-blob/boot_time.proto b/boot-time-blob/boot_time.proto
new file mode 100644
index 0000000..e6f0576
--- /dev/null
+++ b/boot-time-blob/boot_time.proto
@@ -0,0 +1,21 @@
+syntax = "proto3";
+
+package boottimeproto;
+
+message Checkpoint {
+  string name = 1;
+  int64 wall_time = 2;
+  int64 mono_time = 3;
+}
+
+message Duration {
+  string name = 1;
+  int64 duration = 2;
+}
+
+message BootTime {
+  string name = 1;
+  repeated Checkpoint checkpoints = 2;
+  repeated Duration durations = 3;
+  bool is_rebooting = 4;
+}
diff --git a/boot-time-blob/meson.build b/boot-time-blob/meson.build
new file mode 100644
index 0000000..69b326b
--- /dev/null
+++ b/boot-time-blob/meson.build
@@ -0,0 +1,38 @@
+ipmi_blob_dep = dependency('phosphor-ipmi-blobs', required : false)
+if not ipmi_blob_dep.found()
+    ipmi_blob_proj = subproject('phosphor-ipmi-blobs')
+    ipmi_blob_dep = ipmi_blob_proj.get_variable('ipmi_blob_dep')
+endif
+protobuf_dep = dependency('protobuf')
+
+proto = custom_target(
+    'boot_time_proto',
+    command: [
+        find_program('protoc', native: true),
+        '--proto_path=@CURRENT_SOURCE_DIR@',
+        '--cpp_out=@OUTDIR@',
+        '@INPUT@'
+    ],
+    output: [
+        'boot_time.pb.cc',
+        'boot_time.pb.h',
+    ],
+    input: 'boot_time.proto'
+)
+
+shared_module(
+    'boottimeblob',
+    'blob_main.cpp',
+    'blob_handler.cpp',
+    proto,
+    dependencies: [
+        ipmi_blob_dep,
+        protobuf_dep,
+        boost_dep,
+        sdbusplus_dep,
+        fmt_dep,
+    ],
+    include_directories : ['.'],
+    install: true,
+    install_dir: get_option('libdir') / 'blob-ipmid'
+)
diff --git a/meson.build b/meson.build
index 09b79d7..be59a53 100644
--- a/meson.build
+++ b/meson.build
@@ -41,6 +41,7 @@
 endif
 
 subdir('src')
+subdir('boot-time-blob')
 subdir('service_files')
 
 if not get_option('tests').disabled()
diff --git a/subprojects/phosphor-ipmi-blobs.wrap b/subprojects/phosphor-ipmi-blobs.wrap
new file mode 100644
index 0000000..e4ab65a
--- /dev/null
+++ b/subprojects/phosphor-ipmi-blobs.wrap
@@ -0,0 +1,3 @@
+[wrap-git]
+url = https://github.com/openbmc/phosphor-ipmi-blobs.git
+revision = HEAD