google-ipmi-sys: adding new commands

This patch is already in multiple vendor layer.
Hence moving this to shared layer.

Add new commands `sendRebootCheckpoint`, `sendRebootComplete` and
`sendRebootAdditionalDuration`.

These commands are for the `BootTimeMonitor` feature in
https://gbmc.googlesource.com/BootTimeMonitor/

Basically that daemon is for monitoring the reboot process for BMC and
hosts. It will be useful for profiling the reboot process and knowing
how to optimize it.

Tested: Not required, just moved a patch and not using in this layer.
Fusion-Link:
https://fusion2.corp.google.com/d8a61873-afaf-3f5e-9794-b19fd03f40b9
https://fusion2.corp.google.com/743dda24-c942-35f2-8d19-9803a71323d5

Google-Bug-Id: 352691500
Change-Id: Ie89e61044579a1c2c6f9f04af50a02315f42f5ce
Signed-off-by: Munawar Hussain <munawarhussain@google.com>
(cherry picked from commit 9d39811f7829d3e184b8c1c8ce71cdcb2a4e374e)
diff --git a/recipes-google/ipmi/google-ipmi-sys/0002-boot-time-monitor-Add-commands.patch b/recipes-google/ipmi/google-ipmi-sys/0002-boot-time-monitor-Add-commands.patch
new file mode 100644
index 0000000..99231a9
--- /dev/null
+++ b/recipes-google/ipmi/google-ipmi-sys/0002-boot-time-monitor-Add-commands.patch
@@ -0,0 +1,944 @@
+From 9a87ff5d07318ce7733ef7f6eb157eb491c4ad04 Mon Sep 17 00:00:00 2001
+From: Michael Shen <gpgpgp@google.com>
+Date: Mon, 29 Apr 2024 03:30:08 +0000
+Subject: [PATCH 2/2] boot-time-monitor: Add commands
+
+Add new commands `sendRebootCheckpoint`, `sendRebootComplete` and
+`sendRebootAdditionalDuration`.
+
+These commands are for the `BootTimeMonitor` feature in
+https://gbmc.googlesource.com/BootTimeMonitor/
+
+Basically that daemon is for monitoring the reboot process for BMC and
+hosts. It will be useful for profiling the reboot process and knowing
+how to optimize it.
+
+Tested:
+```
+bmc:~# ipmitool raw 0x2e 0x32 0x79 0x2b 0x00 0x12 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x03 0x41 0x42 0x43
+ 79 2b 00 12
+bmc:~# ipmitool raw 0x2e 0x32 0x79 0x2b 0x00 0x14 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x03 0x41 0x42 0x43
+ 79 2b 00 14
+bmc:~# ipmitool raw 0x2e 0x32 0x79 0x2b 0x00 0x13
+ 79 2b 00 13
+```
+
+Patch Tracking Bug: b/310528119
+Upstream info / review: go/obmcl/66345
+Upstream-Status: Submitted
+Justification: [enable feature] Adding BootTimeMonitor needed IPMI
+commands.
+
+Signed-off-by: Michael Shen <gpgpgp@google.com>
+Change-Id: Ie740de503d110007820c0da77a6f1522fdf66a9f
+---
+ README.md                           |  43 ++++++
+ boot_time_monitor.cpp               | 135 ++++++++++++++++++
+ boot_time_monitor.hpp               |  50 +++++++
+ handler.cpp                         |  75 ++++++++++
+ handler.hpp                         |  29 ++++
+ handler_impl.hpp                    |   5 +
+ ipmi.cpp                            |   7 +
+ meson.build                         |   1 +
+ test/boot_time_monitor_unittest.cpp | 211 ++++++++++++++++++++++++++++
+ test/handler_mock.hpp               |   6 +
+ test/handler_unittest.cpp           | 196 +++++++++++++++++++++++++-
+ test/meson.build                    |   1 +
+ 12 files changed, 757 insertions(+), 2 deletions(-)
+ create mode 100644 boot_time_monitor.cpp
+ create mode 100644 boot_time_monitor.hpp
+ create mode 100644 test/boot_time_monitor_unittest.cpp
+
+diff --git a/README.md b/README.md
+index 71a213b..9e8097d 100644
+--- a/README.md
++++ b/README.md
+@@ -457,6 +457,49 @@ Response (if applicable)
+ | ------- | ----- | ---------- |
+ | 0x00    | 0x11  | Subcommand |
+ 
++### SendRebootCheckpoint - SubCommand 0x12
++
++Send checkpoints to BMC to monitor the current reboot process.
++User can put current wall lock time in the command or put `0` to let BMC fetch
++the wall clock time by itself.
++If user can somehow have the self measurement of a duration when they send
++checkpoint to BMC, BMC will help to calculate the transition time from previous
++checkpoint to current checkpoint. Then add a checkpoint with the name
++`To_${checkpoint}`. Or specify `0` to ignore it.
++
++Request
++
++| Byte(s)        | Value           | Data                                   |
++| -------------- | --------------- | -------------------------------------- |
++| 0x00           | 0x12            | Subcommand                             |
++| 0x01..0x08     | Wall Time       | Current epoch time in milliseconds     |
++| 0x09..0x10     | Duration        | Self measured duration in milliseconds |
++| 0x11           | Length (N)      | Length of checkpoint name              |
++| 0x12..0x12 + N | Checkpoint name | Checkpoint name ([0-9a-ZA-Z_] only)    |
++
++### SendRebootComplete - SubCommand 0x13
++
++Informing BMC that the current reboot process is completed.
++
++Request
++
++| Byte(s) | Value | Data       |
++| ------- | ----- | ---------- |
++| 0x00    | 0x13  | Subcommand |
++
++### SendRebootAdditionalDuration - SubCommand 0x14
++
++This command allows users to store any duration that they are interested in BMC.
++
++Request
++
++| Byte(s)        | Value         | Data                                   |
++| -------------- | ------------- | -------------------------------------- |
++| 0x00           | 0x14          | Subcommand                             |
++| 0x01..0x08     | Duration      | Self measured duration in milliseconds |
++| 0x09           | Length (N)    | Length of duration name                |
++| 0x0A..0x0A + N | Duration name | Duration name ([0-9a-ZA-Z_] only)      |
++
+ ### SysGetAccelVrSettings - SubCommand 0x15
+ 
+ Get the accel's VR setting value for the given chip and settings ID
+diff --git a/boot_time_monitor.cpp b/boot_time_monitor.cpp
+new file mode 100644
+index 0000000..8c18162
+--- /dev/null
++++ b/boot_time_monitor.cpp
+@@ -0,0 +1,135 @@
++/*
++ * Copyright 2022 Google Inc.
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ *     http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++#include "boot_time_monitor.hpp"
++
++#include "commands.hpp"
++#include "errors.hpp"
++#include "handler.hpp"
++
++#include <cstdint>
++#include <cstring>
++#include <ipmid/api-types.hpp>
++#include <optional>
++#include <regex>
++#include <span>
++#include <string>
++#include <vector>
++
++namespace google
++{
++namespace ipmi
++{
++Resp sendRebootCheckpoint(std::span<const uint8_t> data,
++                          const HandlerInterface* handler)
++{
++    constexpr auto kNameOffset = sizeof(struct CheckpointReqHeader);
++    if (data.size() < kNameOffset ||
++        data.size() < kNameOffset + data[offsetof(CheckpointReqHeader, length)])
++    {
++        std::fprintf(stderr, "Invalid command length: %u\n",
++                     static_cast<unsigned int>(data.size()));
++        return ::ipmi::responseReqDataLenInvalid();
++    }
++    const auto kNameLen = data[offsetof(CheckpointReqHeader, length)];
++
++    const std::string name{data.data() + kNameOffset,
++                           data.data() + kNameOffset + kNameLen};
++    std::fprintf(stderr, "name = %s", name.c_str());
++    if (std::regex_search(name, std::regex("[^0-9a-zA-Z_]")))
++    {
++        std::fprintf(stderr,
++                     "Invalid checkpoint name, only [0-9a-zA-Z_] are allowed");
++        return ::ipmi::responseParmOutOfRange();
++    }
++
++    int64_t wallTime = 0, duration = 0;
++    memcpy(&wallTime, data.data() + offsetof(CheckpointReqHeader, wallTime),
++           sizeof(wallTime));
++    memcpy(&duration, data.data() + offsetof(CheckpointReqHeader, duration),
++           sizeof(duration));
++
++    try
++    {
++        handler->sendRebootCheckpoint(name, wallTime, duration);
++    }
++    catch (const IpmiException& e)
++    {
++        return ::ipmi::response(e.getIpmiError());
++    }
++
++    return ::ipmi::responseSuccess(SysOEMCommands::SysSendRebootCheckpoint,
++                                   std::vector<uint8_t>{});
++}
++
++Resp sendRebootComplete([[maybe_unused]] std::span<const uint8_t> data,
++                        const HandlerInterface* handler)
++{
++    try
++    {
++        handler->sendRebootComplete();
++    }
++    catch (const IpmiException& e)
++    {
++        return ::ipmi::response(e.getIpmiError());
++    }
++
++    return ::ipmi::responseSuccess(SysOEMCommands::SysSendRebootComplete,
++                                   std::vector<uint8_t>{});
++}
++
++Resp sendRebootAdditionalDuration(std::span<const uint8_t> data,
++                                  const HandlerInterface* handler)
++{
++    constexpr auto kNameOffset = sizeof(struct DurationReqHeader);
++    if (data.size() < kNameOffset ||
++        data.size() < kNameOffset + data[offsetof(DurationReqHeader, length)])
++    {
++        std::fprintf(stderr, "Invalid command length: %u\n",
++                     static_cast<unsigned int>(data.size()));
++        return ::ipmi::responseReqDataLenInvalid();
++    }
++    const auto kNameLen = data[offsetof(DurationReqHeader, length)];
++
++    const std::string name{data.data() + kNameOffset,
++                           data.data() + kNameOffset + kNameLen};
++    if (std::regex_search(name, std::regex("[^0-9a-zA-Z_]")))
++    {
++        std::fprintf(stderr,
++                     "Invalid duration name, only [0-9a-zA-Z_] are allowed");
++        return ::ipmi::responseParmOutOfRange();
++    }
++
++    int64_t duration = 0;
++    memcpy(&duration, data.data() + offsetof(DurationReqHeader, duration),
++           sizeof(duration));
++
++    try
++    {
++        handler->sendRebootAdditionalDuration(name, duration);
++    }
++    catch (const IpmiException& e)
++    {
++        return ::ipmi::response(e.getIpmiError());
++    }
++
++    return ::ipmi::responseSuccess(
++        SysOEMCommands::SysSendRebootAdditionalDuration,
++        std::vector<uint8_t>{});
++}
++
++} // namespace ipmi
++} // namespace google
+diff --git a/boot_time_monitor.hpp b/boot_time_monitor.hpp
+new file mode 100644
+index 0000000..0bea4bd
+--- /dev/null
++++ b/boot_time_monitor.hpp
+@@ -0,0 +1,50 @@
++// Copyright 2021 Google LLC
++//
++// Licensed under the Apache License, Version 2.0 (the "License");
++// you may not use this file except in compliance with the License.
++// You may obtain a copy of the License at
++//
++//      http://www.apache.org/licenses/LICENSE-2.0
++//
++// Unless required by applicable law or agreed to in writing, software
++// distributed under the License is distributed on an "AS IS" BASIS,
++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++// See the License for the specific language governing permissions and
++// limitations under the License.
++
++#pragma once
++
++#include "handler.hpp"
++
++#include <ipmid/api.h>
++
++#include <ipmid/api-types.hpp>
++#include <span>
++
++namespace google
++{
++namespace ipmi
++{
++
++struct CheckpointReqHeader
++{
++    int64_t wallTime;
++    int64_t duration;
++    uint8_t length;
++} __attribute__((packed));
++
++struct DurationReqHeader
++{
++    int64_t duration;
++    uint8_t length;
++} __attribute__((packed));
++
++Resp sendRebootCheckpoint(std::span<const uint8_t> data,
++                          const HandlerInterface* handler);
++Resp sendRebootComplete(std::span<const uint8_t> data,
++                        const HandlerInterface* handler);
++Resp sendRebootAdditionalDuration(std::span<const uint8_t> data,
++                                  const HandlerInterface* handler);
++
++} // namespace ipmi
++} // namespace google
+diff --git a/handler.cpp b/handler.cpp
+index 2bcb634..7b051f3 100644
+--- a/handler.cpp
++++ b/handler.cpp
+@@ -753,6 +753,81 @@ void Handler::linuxBootDone() const
+     }
+ }
+ 
++constexpr std::string_view BTMONITOR_SERVICE =
++    "com.google.gbmc.boot_time_monitor";
++constexpr std::string_view BTMONITOR_OBJECT =
++    "/xyz/openbmc_project/time/boot/host0";
++constexpr std::string_view BTMONITOR_CHECKPOINT_INTERFACE =
++    "xyz.openbmc_project.Time.Boot.Checkpoint";
++constexpr std::string_view BTMONITOR_SETCHECKPOINT = "SetCheckpoint";
++constexpr std::string_view BTMONITOR_COMPLETE = "RebootComplete";
++constexpr std::string_view BTMONITOR_DURATION_INTERFACE =
++    "xyz.openbmc_project.Time.Boot.Duration";
++constexpr std::string_view BTMONITOR_SETDURATION = "SetDuration";
++
++void Handler::sendRebootCheckpoint(std::string_view name, int64_t wallTime,
++                                   int64_t duration) const
++{
++    try
++    {
++        auto bus = getDbus();
++        auto method = bus.new_method_call(BTMONITOR_SERVICE.data(),
++                                          BTMONITOR_OBJECT.data(),
++                                          BTMONITOR_CHECKPOINT_INTERFACE.data(),
++                                          BTMONITOR_SETCHECKPOINT.data());
++        method.append(name.data());
++        method.append(wallTime);
++        method.append(duration);
++        bus.call_noreply(method);
++    }
++    catch (const sdbusplus::exception::SdBusError& ex)
++    {
++        std::fprintf(stderr, "Failed to call btmanager `SetCheckpoint`: %s",
++                     ex.what());
++        throw IpmiException(::ipmi::ccUnspecifiedError);
++    }
++}
++
++void Handler::sendRebootComplete() const
++{
++    try
++    {
++        auto bus = getDbus();
++        auto method = bus.new_method_call(
++            BTMONITOR_SERVICE.data(), BTMONITOR_OBJECT.data(),
++            BTMONITOR_CHECKPOINT_INTERFACE.data(), BTMONITOR_COMPLETE.data());
++
++        bus.call_noreply(method);
++    }
++    catch (const sdbusplus::exception::SdBusError& ex)
++    {
++        std::fprintf(stderr, "Failed to call btmanager `Complete`: %s",
++                     ex.what());
++        throw IpmiException(::ipmi::ccUnspecifiedError);
++    }
++}
++
++void Handler::sendRebootAdditionalDuration(std::string_view name,
++                                           int64_t duration) const
++{
++    try
++    {
++        auto bus = getDbus();
++        auto method = bus.new_method_call(
++            BTMONITOR_SERVICE.data(), BTMONITOR_OBJECT.data(),
++            BTMONITOR_DURATION_INTERFACE.data(), BTMONITOR_SETDURATION.data());
++        method.append(name.data());
++        method.append(duration);
++        bus.call_noreply(method);
++    }
++    catch (const sdbusplus::exception::SdBusError& ex)
++    {
++        std::fprintf(stderr, "Failed to call btmanager `SetDuration`: %s",
++                     ex.what());
++        throw IpmiException(::ipmi::ccUnspecifiedError);
++    }
++}
++
+ static constexpr char ACCEL_POWER_SERVICE[] = "xyz.openbmc_project.AccelPower";
+ static constexpr char ACCEL_POWER_PATH_PREFIX[] =
+     "/xyz/openbmc_project/control/accel_power_";
+diff --git a/handler.hpp b/handler.hpp
+index d805b56..5b3328e 100644
+--- a/handler.hpp
++++ b/handler.hpp
+@@ -216,6 +216,35 @@ class HandlerInterface
+      */
+     virtual void linuxBootDone() const = 0;
+ 
++    /**
++     * Send checkpoint to BMC to monitor reboot process.
++     *
++     * @param[in] name - Checkpoint name.
++     * @param[in] wallTime - Current epoch time in microseconds.
++     * @param[in] duration - Self measured duration in microsecond.
++     * @throw IpmiException on failure.
++     */
++    virtual void sendRebootCheckpoint(std::string_view name, int64_t wallTime,
++                                      int64_t duration) const = 0;
++
++    /**
++     * Inform BMC that current reboot process is completed.
++     *
++     * @throw IpmiException on failure.
++     */
++    virtual void sendRebootComplete() const = 0;
++
++    /**
++     * Send additional durations to BMC to store any duration that user is
++     * interested.
++     *
++     * @param[in] name - Duration name.
++     * @param[in] duration - duration in millisecond.
++     * @throw IpmiException on failure.
++     */
++    virtual void sendRebootAdditionalDuration(std::string_view name,
++                                              int64_t duration) const = 0;
++
+     /**
+      * Update the VR settings for the given settings_id
+      *
+diff --git a/handler_impl.hpp b/handler_impl.hpp
+index 7fbae3e..10ba504 100644
+--- a/handler_impl.hpp
++++ b/handler_impl.hpp
+@@ -84,6 +84,11 @@ class Handler : public HandlerInterface
+     void accelOobWrite(std::string_view name, uint64_t address,
+                        uint8_t num_bytes, uint64_t data) const override;
+     void linuxBootDone() const override;
++    void sendRebootComplete() const override;
++    void sendRebootCheckpoint(std::string_view name, int64_t wallTime,
++                              int64_t duration) const override;
++    void sendRebootAdditionalDuration(std::string_view name,
++                                      int64_t duration) const override;
+     void accelSetVrSettings(::ipmi::Context::ptr ctx, uint8_t chip_id,
+                             uint8_t settings_id, uint16_t value) const override;
+     uint16_t accelGetVrSettings(::ipmi::Context::ptr ctx, uint8_t chip_id,
+diff --git a/ipmi.cpp b/ipmi.cpp
+index 170304d..836f57f 100644
+--- a/ipmi.cpp
++++ b/ipmi.cpp
+@@ -18,6 +18,7 @@
+ 
+ #include "bm_instance.hpp"
+ #include "bmc_mode.hpp"
++#include "boot_time_monitor.hpp"
+ #include "cable.hpp"
+ #include "commands.hpp"
+ #include "cpld.hpp"
+@@ -90,6 +91,12 @@ Resp handleSysCommand(HandlerInterface* handler, ::ipmi::Context::ptr ctx,
+             return pcieBifurcation(ctx, data, handler, DYNAMIC_BIFURCATION);
+         case SysLinuxBootDone:
+             return linuxBootDone(data, handler);
++        case SysSendRebootCheckpoint:
++            return sendRebootCheckpoint(data, handler);
++        case SysSendRebootComplete:
++            return sendRebootComplete(data, handler);
++        case SysSendRebootAdditionalDuration:
++            return sendRebootAdditionalDuration(data, handler);
+         case SysGetAccelVrSettings:
+             return accelGetVrSettings(ctx, data, handler);
+         case SysSetAccelVrSettings:
+diff --git a/meson.build b/meson.build
+index e1145d7..b7e3f44 100644
+--- a/meson.build
++++ b/meson.build
+@@ -60,6 +60,7 @@ sys_lib = static_library(
+   'eth.cpp',
+   'flash_size.cpp',
+   'handler.cpp',
++  'boot_time_monitor.cpp',
+   'host_power_off.cpp',
+   'ipmi.cpp',
+   'linux_boot_done.cpp',
+diff --git a/test/boot_time_monitor_unittest.cpp b/test/boot_time_monitor_unittest.cpp
+new file mode 100644
+index 0000000..be597af
+--- /dev/null
++++ b/test/boot_time_monitor_unittest.cpp
+@@ -0,0 +1,211 @@
++// Copyright 2021 Google LLC
++//
++// Licensed under the Apache License, Version 2.0 (the "License");
++// you may not use this file except in compliance with the License.
++// You may obtain a copy of the License at
++//
++//      http://www.apache.org/licenses/LICENSE-2.0
++//
++// Unless required by applicable law or agreed to in writing, software
++// distributed under the License is distributed on an "AS IS" BASIS,
++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++// See the License for the specific language governing permissions and
++// limitations under the License.
++
++#include "boot_time_monitor.hpp"
++#include "commands.hpp"
++#include "errors.hpp"
++#include "handler_mock.hpp"
++#include "helper.hpp"
++
++#include <cstdint>
++#include <memory>
++#include <string>
++#include <vector>
++
++#include <gtest/gtest.h>
++
++namespace google
++{
++namespace ipmi
++{
++
++using ::testing::Return;
++using ::testing::Throw;
++
++constexpr uint32_t cpHeadLen = sizeof(struct CheckpointReqHeader);
++constexpr uint32_t durHeadLen = sizeof(struct DurationReqHeader);
++
++TEST(SendRebootCheckpointTest, RequestLenTest)
++{
++    HandlerMock hMock;
++    std::vector<uint8_t> request = {};
++
++    // Empty request test
++    EXPECT_EQ(::ipmi::responseReqDataLenInvalid(),
++              sendRebootCheckpoint(request, &hMock));
++
++    constexpr std::string_view kName = "GoodName";
++    constexpr uint32_t kExtraSize = 1;
++    CheckpointReqHeader r = {
++        .wallTime = 0,
++        .duration = 0,
++        .length = kName.size(),
++    };
++    request.resize(cpHeadLen + kName.size());
++
++    // Test if request len is correct.
++    // Expect OK
++    memcpy(request.data(), &r, cpHeadLen);
++    memcpy(request.data() + cpHeadLen, kName.data(), r.length);
++    EXPECT_NO_THROW(sendRebootCheckpoint(request, &hMock));
++
++    // Test if request is too long
++    // Expect OK
++    r.length = kName.size() - kExtraSize;
++    memcpy(request.data(), &r, cpHeadLen);
++    memcpy(request.data() + cpHeadLen, kName.data(), r.length);
++    EXPECT_NO_THROW(sendRebootCheckpoint(request, &hMock));
++
++    // Test if request is too short
++    // Expect Error
++    r.length = kName.size() + kExtraSize;
++    memcpy(request.data(), &r, cpHeadLen);
++    memcpy(request.data() + cpHeadLen, kName.data(), kName.size());
++    EXPECT_EQ(::ipmi::responseReqDataLenInvalid(),
++              sendRebootCheckpoint(request, &hMock));
++}
++
++TEST(SendRebootCheckpointTest, InvalidName)
++{
++    HandlerMock hMock;
++    constexpr std::string_view kName = "+BadName-";
++    std::vector<uint8_t> request(cpHeadLen + kName.size());
++
++    CheckpointReqHeader r = {
++        .wallTime = 0,
++        .duration = 0,
++        .length = kName.size(),
++    };
++    memcpy(request.data(), &r, cpHeadLen + kName.size());
++    memcpy(request.data() + cpHeadLen, kName.data(), kName.size());
++    EXPECT_EQ(::ipmi::responseParmOutOfRange(),
++              sendRebootCheckpoint(request, &hMock));
++}
++
++TEST(SendRebootCheckpointTest, DBusCallThrow)
++{
++    HandlerMock hMock;
++    constexpr std::string_view kName = "GoodName";
++    std::vector<uint8_t> request(cpHeadLen + kName.size());
++
++    CheckpointReqHeader r = {
++        .wallTime = 0,
++        .duration = 0,
++        .length = kName.size(),
++    };
++    memcpy(request.data(), &r, cpHeadLen + kName.size());
++    memcpy(request.data() + cpHeadLen, kName.data(), kName.size());
++
++    EXPECT_CALL(hMock, sendRebootCheckpoint(kName, r.wallTime, r.duration))
++        .WillOnce(Throw(IpmiException(::ipmi::ccUnspecifiedError)));
++    EXPECT_EQ(::ipmi::response(
++                  IpmiException(::ipmi::ccUnspecifiedError).getIpmiError()),
++              sendRebootCheckpoint(request, &hMock));
++}
++
++TEST(SendRebootComplete, Success)
++{
++    HandlerMock hMock;
++
++    EXPECT_NO_THROW(sendRebootComplete(std::vector<uint8_t>{}, &hMock));
++}
++
++TEST(SendRebootComplete, DBusCallThrow)
++{
++    HandlerMock hMock;
++
++    EXPECT_CALL(hMock, sendRebootComplete())
++        .WillOnce(Throw(IpmiException(::ipmi::ccUnspecifiedError)));
++    EXPECT_EQ(::ipmi::response(
++                  IpmiException(::ipmi::ccUnspecifiedError).getIpmiError()),
++              sendRebootComplete(std::vector<uint8_t>{}, &hMock));
++}
++
++TEST(SendRebootAdditionalDurationTest, RequestLenTest)
++{
++    HandlerMock hMock;
++    std::vector<uint8_t> request = {};
++
++    // Empty request test
++    EXPECT_EQ(::ipmi::responseReqDataLenInvalid(),
++              sendRebootAdditionalDuration(request, &hMock));
++
++    constexpr std::string_view kName = "GoodName";
++    constexpr uint32_t kExtraSize = 1;
++    DurationReqHeader r = {
++        .duration = 0,
++        .length = kName.size(),
++    };
++    request.resize(durHeadLen + kName.size());
++
++    // Test if request len is correct.
++    // Expect OK
++    memcpy(request.data(), &r, durHeadLen);
++    memcpy(request.data() + durHeadLen, kName.data(), r.length);
++    EXPECT_NO_THROW(sendRebootAdditionalDuration(request, &hMock));
++
++    // Test if request is too long
++    // Expect OK
++    r.length = kName.size() - kExtraSize;
++    memcpy(request.data(), &r, durHeadLen);
++    memcpy(request.data() + durHeadLen, kName.data(), r.length);
++    EXPECT_NO_THROW(sendRebootAdditionalDuration(request, &hMock));
++
++    // Test if request is too short
++    // Expect Error
++    r.length = kName.size() + kExtraSize;
++    memcpy(request.data(), &r, durHeadLen);
++    memcpy(request.data() + durHeadLen, kName.data(), kName.size());
++    EXPECT_EQ(::ipmi::responseReqDataLenInvalid(),
++              sendRebootAdditionalDuration(request, &hMock));
++}
++
++TEST(SendRebootAdditionalDurationTest, InvalidName)
++{
++    HandlerMock hMock;
++    constexpr std::string_view kName = "+BadName-";
++    std::vector<uint8_t> request(durHeadLen + kName.size());
++
++    DurationReqHeader r = {
++        .duration = 0,
++        .length = kName.size(),
++    };
++    memcpy(request.data(), &r, durHeadLen + kName.size());
++    memcpy(request.data() + durHeadLen, kName.data(), kName.size());
++    EXPECT_EQ(::ipmi::responseParmOutOfRange(),
++              sendRebootAdditionalDuration(request, &hMock));
++}
++
++TEST(SendRebootAdditionalDurationTest, DBusCallThrow)
++{
++    HandlerMock hMock;
++    constexpr std::string_view kName = "GoodName";
++    std::vector<uint8_t> request(durHeadLen + kName.size());
++
++    DurationReqHeader r = {
++        .duration = 0,
++        .length = kName.size(),
++    };
++    memcpy(request.data(), &r, durHeadLen + kName.size());
++    memcpy(request.data() + durHeadLen, kName.data(), kName.size());
++
++    EXPECT_CALL(hMock, sendRebootAdditionalDuration(kName, r.duration))
++        .WillOnce(Throw(IpmiException(::ipmi::ccUnspecifiedError)));
++    EXPECT_EQ(::ipmi::response(
++                  IpmiException(::ipmi::ccUnspecifiedError).getIpmiError()),
++              sendRebootAdditionalDuration(request, &hMock));
++}
++
++} // namespace ipmi
++} // namespace google
+diff --git a/test/handler_mock.hpp b/test/handler_mock.hpp
+index c5b2df0..7a3e96b 100644
+--- a/test/handler_mock.hpp
++++ b/test/handler_mock.hpp
+@@ -67,6 +67,12 @@ class HandlerMock : public HandlerInterface
+     MOCK_METHOD(std::vector<uint8_t>, pcieBifurcation,
+                 (::ipmi::Context::ptr, uint8_t, bool), (override));
+     MOCK_METHOD(void, linuxBootDone, (), (const, override));
++    MOCK_METHOD((void), sendRebootCheckpoint,
++                (std::string_view name, int64_t wallTime, int64_t duration),
++                (const, override));
++    MOCK_METHOD((void), sendRebootComplete, (), (const, override));
++    MOCK_METHOD((void), sendRebootAdditionalDuration,
++                (std::string_view name, int64_t duration), (const, override));
+     MOCK_METHOD(void, accelSetVrSettings,
+                 (::ipmi::Context::ptr, uint8_t, uint8_t, uint16_t),
+                 (const, override));
+diff --git a/test/handler_unittest.cpp b/test/handler_unittest.cpp
+index aabec39..ec0fdfc 100644
+--- a/test/handler_unittest.cpp
++++ b/test/handler_unittest.cpp
+@@ -307,8 +307,8 @@ void ExpectGetManagedObjects(StrictMock<sdbusplus::SdBusMock>& mock,
+ }
+ 
+ void ExpectSdBusError(StrictMock<sdbusplus::SdBusMock>& mock,
+-                      const std::string& service, const std::string& objPath,
+-                      const std::string& interface, const std::string& function)
++                      std::string_view service, std::string_view objPath,
++                      std::string_view interface, std::string_view function)
+ {
+     EXPECT_CALL(
+         mock, sd_bus_message_new_method_call(_,         // sd_bus *bus,
+@@ -866,6 +866,198 @@ TEST(HandlerTest, PcieBifurcationDynamic)
+     std::remove(testFilename);
+ }
+ 
++constexpr std::string_view BTMONITOR_SERVICE =
++    "com.google.gbmc.boot_time_monitor";
++constexpr std::string_view BTMONITOR_OBJECT =
++    "/xyz/openbmc_project/time/boot/host0";
++constexpr std::string_view BTMONITOR_CHECKPOINT_INTERFACE =
++    "xyz.openbmc_project.Time.Boot.Checkpoint";
++constexpr std::string_view BTMONITOR_SETCHECKPOINT = "SetCheckpoint";
++constexpr std::string_view BTMONITOR_COMPLETE = "RebootComplete";
++constexpr std::string_view BTMONITOR_DURATION_INTERFACE =
++    "xyz.openbmc_project.Time.Boot.Duration";
++constexpr std::string_view BTMONITOR_SETDURATION = "SetDuration";
++
++void expectSendCheckpoint(StrictMock<sdbusplus::SdBusMock>& mock,
++                          std::string_view kName, int64_t kWallTime,
++                          int64_t kDur)
++{
++    ::testing::InSequence s;
++
++    constexpr sd_bus_message* method = nullptr;
++
++    EXPECT_CALL(mock, sd_bus_message_new_method_call(
++                          _,         // sd_bus *bus,
++                          NotNull(), // sd_bus_message **m
++                          StrEq(BTMONITOR_SERVICE), StrEq(BTMONITOR_OBJECT),
++                          StrEq(BTMONITOR_CHECKPOINT_INTERFACE),
++                          StrEq(BTMONITOR_SETCHECKPOINT)))
++        .WillOnce(DoAll(TraceDbus("sd_bus_message_new_method_call"),
++                        SetArgPointee<1>(method), Return(0)));
++
++    EXPECT_CALL(mock, sd_bus_message_append_basic(
++                          method, SD_BUS_TYPE_STRING,
++                          MatcherCast<const void*>(SafeMatcherCast<const char*>(
++                              StrEq(kName.data())))))
++        .WillOnce(DoAll(TraceDbus("sd_bus_message_append_basic"), Return(1)));
++
++    EXPECT_CALL(
++        mock, sd_bus_message_append_basic(
++                  method, SD_BUS_TYPE_INT64,
++                  MatcherCast<const void*>(
++                      SafeMatcherCast<const int64_t*>(Pointee(Eq(kWallTime))))))
++        .WillOnce(
++            DoAll(TraceDbus2("sd_bus_message_append_basic(kWallTime) -> 1"),
++                  Return(1)));
++
++    EXPECT_CALL(mock,
++                sd_bus_message_append_basic(
++                    method, SD_BUS_TYPE_INT64,
++                    MatcherCast<const void*>(
++                        SafeMatcherCast<const int64_t*>(Pointee(Eq(kDur))))))
++        .WillOnce(DoAll(TraceDbus2("sd_bus_message_append_basic(kDur) -> 1"),
++                        Return(1)));
++
++    EXPECT_CALL(mock, sd_bus_call(_,         // sd_bus *bus,
++                                  method,    // sd_bus_message *m
++                                  _,         // uint64_t timeout
++                                  NotNull(), // sd_bus_error *ret_error
++                                  IsNull())) // sd_bus_message **reply
++        .WillOnce(DoAll(TraceDbus("sd_bus_call() -> ret_val"),
++                        SetArgPointee<3>(SD_BUS_ERROR_NULL), Return(0)));
++}
++
++void expectSendAdditionalDuration(StrictMock<sdbusplus::SdBusMock>& mock,
++                                  std::string_view kName, int64_t kDur)
++{
++    ::testing::InSequence s;
++
++    constexpr sd_bus_message* method = nullptr;
++
++    EXPECT_CALL(mock, sd_bus_message_new_method_call(
++                          _,         // sd_bus *bus,
++                          NotNull(), // sd_bus_message **m
++                          StrEq(BTMONITOR_SERVICE), StrEq(BTMONITOR_OBJECT),
++                          StrEq(BTMONITOR_DURATION_INTERFACE),
++                          StrEq(BTMONITOR_SETDURATION)))
++        .WillOnce(DoAll(TraceDbus("sd_bus_message_new_method_call"),
++                        SetArgPointee<1>(method), Return(0)));
++
++    EXPECT_CALL(mock, sd_bus_message_append_basic(
++                          method, SD_BUS_TYPE_STRING,
++                          MatcherCast<const void*>(SafeMatcherCast<const char*>(
++                              StrEq(kName.data())))))
++        .WillOnce(DoAll(TraceDbus("sd_bus_message_append_basic"), Return(1)));
++
++    EXPECT_CALL(mock,
++                sd_bus_message_append_basic(
++                    method, SD_BUS_TYPE_INT64,
++                    MatcherCast<const void*>(
++                        SafeMatcherCast<const int64_t*>(Pointee(Eq(kDur))))))
++        .WillOnce(DoAll(TraceDbus2("sd_bus_message_append_basic(kDur) -> 1"),
++                        Return(1)));
++
++    EXPECT_CALL(mock, sd_bus_call(_,         // sd_bus *bus,
++                                  method,    // sd_bus_message *m
++                                  _,         // uint64_t timeout
++                                  NotNull(), // sd_bus_error *ret_error
++                                  IsNull())) // sd_bus_message **reply
++        .WillOnce(DoAll(TraceDbus("sd_bus_call() -> ret_val"),
++                        SetArgPointee<3>(SD_BUS_ERROR_NULL), Return(0)));
++}
++
++TEST(HandlerTest, SendRebootCheckpointTest_Success)
++{
++    // Parameters are not important.
++    constexpr std::string_view kName = "NotImportant";
++    constexpr int64_t kWallTime = 0;
++    constexpr int64_t kDur = 0;
++
++    StrictMock<sdbusplus::SdBusMock> mock;
++    MockDbusHandler h(mock);
++
++    expectSendCheckpoint(mock, kName, kWallTime, kDur);
++    EXPECT_NO_THROW(h.sendRebootCheckpoint(kName, kWallTime, kDur));
++}
++
++TEST(HandlerTest, SendRebootCheckpointTest_Fail)
++{
++    // Parameters are not important.
++    constexpr std::string_view kName = "NotImportant";
++    constexpr int64_t kWallTime = 0;
++    constexpr int64_t kDur = 0;
++
++    StrictMock<sdbusplus::SdBusMock> mock;
++    MockDbusHandler h(mock);
++
++    ExpectSdBusError(mock, BTMONITOR_SERVICE, BTMONITOR_OBJECT,
++                     BTMONITOR_CHECKPOINT_INTERFACE, BTMONITOR_SETCHECKPOINT);
++    EXPECT_THROW(h.sendRebootCheckpoint(kName, kWallTime, kDur), IpmiException);
++}
++
++TEST(HandlerTest, sendRebootCompleteTest_Success)
++{
++    StrictMock<sdbusplus::SdBusMock> mock;
++    MockDbusHandler h(mock);
++
++    constexpr sd_bus_message* method = nullptr;
++    EXPECT_CALL(mock, sd_bus_message_new_method_call(
++                          _,         // sd_bus *bus,
++                          NotNull(), // sd_bus_message **m
++                          StrEq(BTMONITOR_SERVICE), StrEq(BTMONITOR_OBJECT),
++                          StrEq(BTMONITOR_CHECKPOINT_INTERFACE),
++                          StrEq(BTMONITOR_COMPLETE)))
++        .WillOnce(DoAll(TraceDbus("sd_bus_message_new_method_call"),
++                        SetArgPointee<1>(method), Return(0)));
++
++    EXPECT_CALL(mock, sd_bus_call(_,         // sd_bus *bus,
++                                  method,    // sd_bus_message *m
++                                  _,         // uint64_t timeout
++                                  NotNull(), // sd_bus_error *ret_error
++                                  IsNull())) // sd_bus_message **reply
++        .WillOnce(DoAll(TraceDbus("sd_bus_call() -> ret_val"),
++                        SetArgPointee<3>(SD_BUS_ERROR_NULL), Return(0)));
++
++    EXPECT_NO_THROW(h.sendRebootComplete());
++}
++
++TEST(HandlerTest, sendRebootCompleteTest_Fail)
++{
++    StrictMock<sdbusplus::SdBusMock> mock;
++    MockDbusHandler h(mock);
++
++    ExpectSdBusError(mock, BTMONITOR_SERVICE, BTMONITOR_OBJECT,
++                     BTMONITOR_CHECKPOINT_INTERFACE, BTMONITOR_COMPLETE);
++    EXPECT_THROW(h.sendRebootComplete(), IpmiException);
++}
++
++TEST(HandlerTest, sendRebootAdditionalDurationTest_Success)
++{
++    // Parameters are not important.
++    constexpr std::string_view kName = "NotImportant";
++    constexpr int64_t kDur = 0;
++
++    StrictMock<sdbusplus::SdBusMock> mock;
++    MockDbusHandler h(mock);
++
++    expectSendAdditionalDuration(mock, kName, kDur);
++    EXPECT_NO_THROW(h.sendRebootAdditionalDuration(kName, kDur));
++}
++
++TEST(HandlerTest, sendRebootAdditionalDurationTest_Fail)
++{
++    // Parameters are not important.
++    constexpr std::string_view kName = "NotImportant";
++    constexpr int64_t kDur = 0;
++
++    StrictMock<sdbusplus::SdBusMock> mock;
++    MockDbusHandler h(mock);
++
++    ExpectSdBusError(mock, BTMONITOR_SERVICE, BTMONITOR_OBJECT,
++                     BTMONITOR_DURATION_INTERFACE, BTMONITOR_SETDURATION);
++    EXPECT_THROW(h.sendRebootAdditionalDuration(kName, kDur), IpmiException);
++}
++
+ // TODO: Add checks for other functions of handler.
+ 
+ } // namespace ipmi
+diff --git a/test/meson.build b/test/meson.build
+index f7e3bb9..d6f50bd 100644
+--- a/test/meson.build
++++ b/test/meson.build
+@@ -23,6 +23,7 @@ tests = [
+   'flash',
+   'google_accel_oob',
+   'handler',
++  'boot_time_monitor',
+   'machine',
+   'pcie',
+   'poweroff',
+-- 
+2.45.1.288.g0e0cd299f1-goog
+