Implement HostEpoch set time logic

1. When setting host epoch, follow below logic:

        Mode  | Owner | Set Host Time
        ----- | ----- | -------------
        NTP   | BMC   | Not allowed
        NTP   | HOST  | Not allowed
        NTP   | SPLIT | OK, and just save offset
        NTP   | BOTH  | Not allowed
        MANUAL| BMC   | Not allowed
        MANUAL| HOST  | OK, and set time to BMC
        MANUAL| SPLIT | OK, and just save offset
        MANUAL| BOTH  | OK, and set time to BMC

2. If owner is SPLIT and BMC time is changed, update the offset accordinly;
3. Use timerfd to get notified on BMC time change, and update host time
diff accordingly;
4. Add unit test cases.

Change-Id: I2d60a821f7da9b689c579ae7ab672cc37967322c
Signed-off-by: Lei YU <mine260309@gmail.com>
diff --git a/bmc_epoch.cpp b/bmc_epoch.cpp
index ed482ad..7bc6c22 100644
--- a/bmc_epoch.cpp
+++ b/bmc_epoch.cpp
@@ -1,6 +1,22 @@
 #include "bmc_epoch.hpp"
 
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/elog-errors.hpp>
 #include <phosphor-logging/log.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <sys/timerfd.h>
+#include <unistd.h>
+
+
+// Neeed to do this since its not exported outside of the kernel.
+// Refer : https://gist.github.com/lethean/446cea944b7441228298
+#ifndef TFD_TIMER_CANCEL_ON_SET
+#define TFD_TIMER_CANCEL_ON_SET (1 << 1)
+#endif
+
+// Needed to make sure timerfd does not misfire even though we set CANCEL_ON_SET
+#define TIME_T_MAX (time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1)
 
 namespace phosphor
 {
@@ -11,9 +27,61 @@
 
 BmcEpoch::BmcEpoch(sdbusplus::bus::bus& bus,
                    const char* objPath)
-    : EpochBase(bus, objPath)
+    : EpochBase(bus, objPath),
+      bus(bus)
 {
-    // Empty
+    initialize();
+}
+
+void BmcEpoch::initialize()
+{
+    using InternalFailure = sdbusplus::xyz::openbmc_project::Common::
+                                Error::InternalFailure;
+
+    // Subscribe time change event
+    // Choose the MAX time that is possible to avoid mis fires.
+    constexpr itimerspec maxTime = {
+        {0, 0}, // it_interval
+        {TIME_T_MAX, 0}, //it_value
+    };
+
+    timeFd = timerfd_create(CLOCK_REALTIME, 0);
+    if (timeFd == -1)
+    {
+        log<level::ERR>("Failed to create timerfd",
+                        entry("ERRNO=%d", errno),
+                        entry("ERR=%s", strerror(errno)));
+        elog<InternalFailure>();
+    }
+
+    auto r = timerfd_settime(timeFd,
+                             TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET,
+                             &maxTime,
+                             nullptr);
+    if (r != 0)
+    {
+        log<level::ERR>("Failed to set timerfd",
+                        entry("ERRNO=%d", errno),
+                        entry("ERR=%s", strerror(errno)));
+        elog<InternalFailure>();
+    }
+
+    sd_event_source* es;
+    r = sd_event_add_io(bus.get_event(), &es,
+                        timeFd, EPOLLIN, onTimeChange, this);
+    if (r < 0)
+    {
+        log<level::ERR>("Failed to add event",
+                        entry("ERRNO=%d", -r),
+                        entry("ERR=%s", strerror(-r)));
+        elog<InternalFailure>();
+    }
+    timeChangeEventSource.reset(es);
+}
+
+BmcEpoch::~BmcEpoch()
+{
+    close(timeFd);
 }
 
 uint64_t BmcEpoch::elapsed() const
@@ -24,6 +92,18 @@
 
 uint64_t BmcEpoch::elapsed(uint64_t value)
 {
+    /*
+        Mode  | Owner | Set BMC Time
+        ----- | ----- | -------------
+        NTP   | BMC   | Not allowed
+        NTP   | HOST  | Not allowed
+        NTP   | SPLIT | Not allowed
+        NTP   | BOTH  | Not allowed
+        MANUAL| BMC   | OK
+        MANUAL| HOST  | Not allowed
+        MANUAL| SPLIT | OK
+        MANUAL| BOTH  | OK
+    */
     if (timeMode == Mode::NTP)
     {
         log<level::ERR>("Setting BmcTime with NTP mode is not allowed");
@@ -37,13 +117,46 @@
         return 0;
     }
 
-    auto time = std::chrono::microseconds(value);
+    auto time = microseconds(value);
     setTime(time);
 
+    notifyBmcTimeChange(time);
+
     server::EpochTime::elapsed(value);
     return value;
 }
 
+void BmcEpoch::setBmcTimeChangeListener(BmcTimeChangeListener* listener)
+{
+    timeChangeListener = listener;
+}
+
+void BmcEpoch::notifyBmcTimeChange(const microseconds& time)
+{
+    // Notify listener if it exists
+    if (timeChangeListener)
+    {
+        timeChangeListener->onBmcTimeChanged(time);
+    }
+}
+
+int BmcEpoch::onTimeChange(sd_event_source* es, int fd,
+                           uint32_t /* revents */, void* userdata)
+{
+    auto bmcEpoch = static_cast<BmcEpoch*>(userdata);
+
+    std::array<char, 64> time {};
+
+    // We are not interested in the data here.
+    // So read until there is no new data here in the FD
+    while (read(fd, time.data(), time.max_size()) > 0);
+
+    log<level::INFO>("BMC system time is changed");
+    bmcEpoch->notifyBmcTimeChange(bmcEpoch->getTime());
+
+    return 0;
+}
+
 } // namespace time
 } // namespace phosphor
 
diff --git a/bmc_epoch.hpp b/bmc_epoch.hpp
index 8c841a7..228a39d 100644
--- a/bmc_epoch.hpp
+++ b/bmc_epoch.hpp
@@ -1,12 +1,17 @@
 #pragma once
 
+#include "bmc_time_change_listener.hpp"
 #include "epoch_base.hpp"
 
+#include <chrono>
+
 namespace phosphor
 {
 namespace time
 {
 
+using namespace std::chrono;
+
 /** @class BmcEpoch
  *  @brief OpenBMC BMC EpochTime implementation.
  *  @details A concrete implementation for xyz.openbmc_project.Time.EpochTime
@@ -18,6 +23,7 @@
         friend class TestBmcEpoch;
         BmcEpoch(sdbusplus::bus::bus& bus,
                  const char* objPath);
+        ~BmcEpoch();
 
         /**
          * @brief Get value of Elapsed property
@@ -33,6 +39,55 @@
          * @return The updated elapsed microseconds since UTC
          **/
         uint64_t elapsed(uint64_t value) override;
+
+        /** @brief Set the listner for bmc time change
+         *
+         * @param[in] listener - The pointer to the listener
+         */
+        void setBmcTimeChangeListener(BmcTimeChangeListener* listener);
+
+    private:
+        /** @brief The fd for time change event */
+        int timeFd = -1;
+
+        /** @brief Initialize timerFd related resource */
+        void initialize();
+
+        /** @brief Notify the listeners that bmc time is changed
+         *
+         * @param[in] time - The epoch time in microseconds to notify
+         */
+        void notifyBmcTimeChange(const microseconds& time);
+
+        /** @brief The callback function on system time change
+         *
+         * @param[in] es - Source of the event
+         * @param[in] fd - File descriptor of the timer
+         * @param[in] revents - Not used
+         * @param[in] userdata - User data pointer
+         */
+        static int onTimeChange(sd_event_source* es, int fd,
+                                uint32_t revents, void* userdata);
+
+        /** @brief The reference of sdbusplus bus */
+        sdbusplus::bus::bus& bus;
+
+        /** @brief The deleter of sd_event_source */
+        std::function<void(sd_event_source*)> sdEventSourceDeleter =
+            [] (sd_event_source* p) {
+                if (p)
+                {
+                    sd_event_source_unref(p);
+                }
+            };
+        using SdEventSource = std::unique_ptr<sd_event_source,
+                                              decltype(sdEventSourceDeleter)>;
+
+        /** @brief The event source on system time change */
+        SdEventSource timeChangeEventSource {nullptr, sdEventSourceDeleter};
+
+        /** @brief The listener for bmc time change */
+        BmcTimeChangeListener* timeChangeListener = nullptr;
 };
 
 } // namespace time
diff --git a/bmc_time_change_listener.hpp b/bmc_time_change_listener.hpp
new file mode 100644
index 0000000..44fc479
--- /dev/null
+++ b/bmc_time_change_listener.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <chrono>
+
+namespace phosphor
+{
+namespace time
+{
+
+class BmcTimeChangeListener
+{
+    public:
+        virtual ~BmcTimeChangeListener() = default;
+
+        /** @brief Notified on bmc time is changed
+         *
+         * @param[in] bmcTime - The epoch time in microseconds
+         */
+        virtual void onBmcTimeChanged(
+            const std::chrono::microseconds& bmcTime) = 0;
+};
+
+}
+}
diff --git a/host_epoch.cpp b/host_epoch.cpp
index 3b4c00e..129d224 100644
--- a/host_epoch.cpp
+++ b/host_epoch.cpp
@@ -9,43 +9,107 @@
 {
 using namespace sdbusplus::xyz::openbmc_project::Time;
 using namespace phosphor::logging;
+using namespace std::chrono;
 
 HostEpoch::HostEpoch(sdbusplus::bus::bus& bus,
                      const char* objPath)
     : EpochBase(bus, objPath),
       offset(utils::readData<decltype(offset)::rep>(offsetFile))
 {
-    // Empty
+    // Initialize the diffToSteadyClock
+    auto steadyTime = duration_cast<microseconds>(
+        steady_clock::now().time_since_epoch());
+    diffToSteadyClock = getTime() + offset - steadyTime;
 }
 
 uint64_t HostEpoch::elapsed() const
 {
-    // It does not needs to check owner when getting time
-    return (getTime() + offset).count();
+    auto ret = getTime();
+    if (timeOwner == Owner::SPLIT)
+    {
+        ret += offset;
+    }
+    return ret.count();
 }
 
 uint64_t HostEpoch::elapsed(uint64_t value)
 {
-    if (timeOwner == Owner::BMC)
+    /*
+        Mode  | Owner | Set Host Time
+        ----- | ----- | -------------
+        NTP   | BMC   | Not allowed
+        NTP   | HOST  | Not allowed
+        NTP   | SPLIT | OK, and just save offset
+        NTP   | BOTH  | Not allowed
+        MANUAL| BMC   | Not allowed
+        MANUAL| HOST  | OK, and set time to BMC
+        MANUAL| SPLIT | OK, and just save offset
+        MANUAL| BOTH  | OK, and set time to BMC
+    */
+    if (timeOwner == Owner::BMC ||
+        (timeMode == Mode::NTP
+         && (timeOwner == Owner::HOST || timeOwner == Owner::BOTH)))
     {
-        log<level::ERR>("Setting HostTime in BMC owner is not allowed");
+        log<level::ERR>("Setting HostTime is not allowed");
         // TODO: throw NotAllowed exception
         return 0;
     }
 
-    // TODO: implement the logic of setting host time
-    // based on timeOwner and timeMode
+    auto time = microseconds(value);
+    if (timeOwner == Owner::SPLIT)
+    {
+        // Calculate the offset between host and bmc time
+        offset = time - getTime();
+        saveOffset();
 
-    auto time = std::chrono::microseconds(value);
-    offset = time - getTime();
-
-    // Store the offset to file
-    utils::writeData(offsetFile, offset.count());
+        // Calculate the diff between host and steady time
+        auto steadyTime = duration_cast<microseconds>(
+            steady_clock::now().time_since_epoch());
+        diffToSteadyClock = time - steadyTime;
+    }
+    else
+    {
+        // Set time to BMC
+        setTime(time);
+    }
 
     server::EpochTime::elapsed(value);
     return value;
 }
 
+void HostEpoch::onOwnerChanged(Owner owner)
+{
+    // If timeOwner is changed to SPLIT, the offset shall be preserved
+    // Otherwise it shall be cleared;
+    timeOwner = owner;
+    if (timeOwner != Owner::SPLIT)
+    {
+        offset = microseconds(0);
+        saveOffset();
+    }
+}
+
+void HostEpoch::saveOffset()
+{
+    // Store the offset to file
+    utils::writeData(offsetFile, offset.count());
+}
+
+void HostEpoch::onBmcTimeChanged(const microseconds& bmcTime)
+{
+    // If owner is split and BMC time is changed,
+    // the offset shall be adjusted
+    if (timeOwner == Owner::SPLIT)
+    {
+        auto steadyTime = duration_cast<microseconds>(
+            steady_clock::now().time_since_epoch());
+        auto hostTime = steadyTime + diffToSteadyClock;
+        offset = hostTime - bmcTime;
+
+        saveOffset();
+    }
+}
+
 } // namespace time
 } // namespace phosphor
 
diff --git a/host_epoch.hpp b/host_epoch.hpp
index 3798f8f..1eb3167 100644
--- a/host_epoch.hpp
+++ b/host_epoch.hpp
@@ -1,5 +1,6 @@
 #pragma once
 
+#include "bmc_time_change_listener.hpp"
 #include "config.h"
 #include "epoch_base.hpp"
 
@@ -15,7 +16,7 @@
  *  @details A concrete implementation for xyz.openbmc_project.Time.EpochTime
  *  DBus API for HOST's epoch time.
  */
-class HostEpoch : public EpochBase
+class HostEpoch : public EpochBase, public BmcTimeChangeListener
 {
     public:
         friend class TestHostEpoch;
@@ -38,10 +39,31 @@
          **/
         uint64_t elapsed(uint64_t value) override;
 
+        /** @brief Notified on time owner changed */
+        void onOwnerChanged(Owner owner) override;
+
+        /** @brief Notified on bmc time is changed
+         *
+         * @param[in] bmcTime - The epoch time in microseconds
+         */
+        void onBmcTimeChanged(
+            const std::chrono::microseconds& bmcTime) override;
+
     private:
         /** @brief The diff between BMC and Host time */
         std::chrono::microseconds offset;
 
+        /**
+         * @brief The diff between host time and steady clock
+         * @details This diff is used to calculate the host time if BMC time
+         * is changed and the owner is SPLIT.
+         * Without this the host time is lost if BMC time is changed.
+        */
+        std::chrono::microseconds diffToSteadyClock;
+
+        /** @brief Save the offset value into offsetFile */
+        void saveOffset();
+
         /** @brief The file to store the offset in File System.
          *  Read back when starts
          **/
diff --git a/main.cpp b/main.cpp
index b8a5adb..1798dc0 100644
--- a/main.cpp
+++ b/main.cpp
@@ -8,6 +8,20 @@
 int main()
 {
     auto bus = sdbusplus::bus::new_default();
+    sd_event* event = nullptr;
+
+    auto eventDeleter = [](sd_event* e) {
+        e = sd_event_unref(e);
+    };
+    using SdEvent = std::unique_ptr<sd_event, decltype(eventDeleter)>;
+
+    // acquire a referece to the default event loop
+    sd_event_default(&event);
+    SdEvent sdEvent {event, eventDeleter};
+    event = nullptr;
+
+    // attach bus to this event loop
+    bus.attach_event(sdEvent.get(), SD_EVENT_PRIORITY_NORMAL);
 
     // Add sdbusplus ObjectManager
     sdbusplus::server::manager::manager bmcEpochObjManager(bus, OBJPATH_BMC);
@@ -19,13 +33,14 @@
 
     manager.addListener(&bmc);
     manager.addListener(&host);
+    bmc.setBmcTimeChangeListener(&host);
 
     bus.request_name(BUSNAME);
 
-    while (true)
-    {
-        bus.process_discard();
-        bus.wait();
-    }
+    // Start event loop for all sd-bus events and timer event
+    sd_event_loop(bus.get_event());
+
+    bus.detach_event();
+
     return 0;
 }
diff --git a/test/TestBmcEpoch.cpp b/test/TestBmcEpoch.cpp
index 2fd55a5..4661e20 100644
--- a/test/TestBmcEpoch.cpp
+++ b/test/TestBmcEpoch.cpp
@@ -1,45 +1,67 @@
 #include <sdbusplus/bus.hpp>
 #include <gtest/gtest.h>
+#include <memory>
 
 #include "bmc_epoch.hpp"
 #include "config.h"
 #include "types.hpp"
+#include "mocked_bmc_time_change_listener.hpp"
 
 namespace phosphor
 {
 namespace time
 {
 
+using ::testing::_;
 using namespace std::chrono;
+
 class TestBmcEpoch : public testing::Test
 {
     public:
         sdbusplus::bus::bus bus;
-        BmcEpoch bmcEpoch;
+        sd_event* event;
+        MockBmcTimeChangeListener listener;
+        std::unique_ptr<BmcEpoch> bmcEpoch;
 
         TestBmcEpoch()
-            : bus(sdbusplus::bus::new_default()),
-              bmcEpoch(bus, OBJPATH_BMC)
+            : bus(sdbusplus::bus::new_default())
         {
-            // Empty
+            // BmcEpoch requires sd_event to init
+            sd_event_default(&event);
+            bus.attach_event(event, SD_EVENT_PRIORITY_NORMAL);
+            bmcEpoch = std::make_unique<BmcEpoch>(bus, OBJPATH_BMC);
+            bmcEpoch->setBmcTimeChangeListener(&listener);
+        }
+
+        ~TestBmcEpoch()
+        {
+            bus.detach_event();
+            sd_event_unref(event);
         }
 
         // Proxies for BmcEpoch's private members and functions
         Mode getTimeMode()
         {
-            return bmcEpoch.timeMode;
+            return bmcEpoch->timeMode;
         }
         Owner getTimeOwner()
         {
-            return bmcEpoch.timeOwner;
+            return bmcEpoch->timeOwner;
         }
         void setTimeOwner(Owner owner)
         {
-            bmcEpoch.timeOwner = owner;
+            bmcEpoch->timeOwner = owner;
         }
         void setTimeMode(Mode mode)
         {
-            bmcEpoch.timeMode = mode;
+            bmcEpoch->timeMode = mode;
+        }
+        void triggerTimeChange()
+        {
+            bmcEpoch->onTimeChange(nullptr,
+                                   -1,
+                                   0,
+                                   bmcEpoch.get());
         }
 };
 
@@ -51,9 +73,9 @@
 
 TEST_F(TestBmcEpoch, getElapsed)
 {
-    auto t1 = bmcEpoch.elapsed();
+    auto t1 = bmcEpoch->elapsed();
     EXPECT_NE(0, t1);
-    auto t2 = bmcEpoch.elapsed();
+    auto t2 = bmcEpoch->elapsed();
     EXPECT_GE(t2, t1);
 }
 
@@ -62,13 +84,13 @@
     auto epochNow = duration_cast<microseconds>(
         system_clock::now().time_since_epoch()).count();
     // In NTP mode, setting time is not allowed
-    auto ret = bmcEpoch.elapsed(epochNow);
+    auto ret = bmcEpoch->elapsed(epochNow);
     EXPECT_EQ(0, ret);
 
     // In Host owner, setting time is not allowed
     setTimeMode(Mode::MANUAL);
     setTimeOwner(Owner::HOST);
-    ret = bmcEpoch.elapsed(epochNow);
+    ret = bmcEpoch->elapsed(epochNow);
     EXPECT_EQ(0, ret);
 }
 
@@ -79,5 +101,12 @@
     // But for now we can not test it
 }
 
+TEST_F(TestBmcEpoch, onTimeChange)
+{
+    // On BMC time change, the listner is expected to be notified
+    EXPECT_CALL(listener, onBmcTimeChanged(_)).Times(1);
+    triggerTimeChange();
+}
+
 }
 }
diff --git a/test/TestHostEpoch.cpp b/test/TestHostEpoch.cpp
index 0ff8a11..13d604d 100644
--- a/test/TestHostEpoch.cpp
+++ b/test/TestHostEpoch.cpp
@@ -14,6 +14,8 @@
 using namespace std::chrono;
 using namespace std::chrono_literals;
 
+const constexpr microseconds USEC_ZERO{0};
+
 class TestHostEpoch : public testing::Test
 {
     public:
@@ -22,7 +24,7 @@
 
         static constexpr auto FILE_NOT_EXIST = "path/to/file-not-exist";
         static constexpr auto FILE_OFFSET = "saved_host_offset";
-        static constexpr auto delta = 2s;
+        const microseconds delta = 2s;
 
         TestHostEpoch()
             : bus(sdbusplus::bus::new_default()),
@@ -50,9 +52,86 @@
         {
             return hostEpoch.offset;
         }
+        void setOffset(microseconds us)
+        {
+            hostEpoch.offset = us;
+        }
         void setTimeOwner(Owner owner)
         {
-            hostEpoch.timeOwner = owner;
+            hostEpoch.onOwnerChanged(owner);
+        }
+        void setTimeMode(Mode mode)
+        {
+            hostEpoch.onModeChanged(mode);
+        }
+
+        void checkSettingTimeNotAllowed()
+        {
+            // By default offset shall be 0
+            EXPECT_EQ(0, getOffset().count());
+
+            // Set time is not allowed,
+            // so verify offset is still 0 after set time
+            microseconds diff = 1min;
+            hostEpoch.elapsed(hostEpoch.elapsed() + diff.count());
+            EXPECT_EQ(0, getOffset().count());
+            // TODO: when gmock is ready, check there is no call to timedatectl
+        }
+
+        void checkSetSplitTimeInFuture()
+        {
+            // Get current time, and set future +1min time
+            auto t1 = hostEpoch.elapsed();
+            EXPECT_NE(0, t1);
+            microseconds diff = 1min;
+            auto t2 = t1 + diff.count();
+            hostEpoch.elapsed(t2);
+
+            // Verify that the offset shall be positive,
+            // and less or equal to diff, and shall be not too less.
+            auto offset = getOffset();
+            EXPECT_GT(offset, USEC_ZERO);
+            EXPECT_LE(offset, diff);
+            diff -= delta;
+            EXPECT_GE(offset, diff);
+
+            // Now get time shall be around future +1min time
+            auto epochNow = duration_cast<microseconds>(
+                                system_clock::now().time_since_epoch()).count();
+            auto elapsedGot = hostEpoch.elapsed();
+            EXPECT_LT(epochNow, elapsedGot);
+            auto epochDiff = elapsedGot - epochNow;
+            diff = 1min;
+            EXPECT_GT(epochDiff, (diff - delta).count());
+            EXPECT_LT(epochDiff, (diff + delta).count());
+        }
+        void checkSetSplitTimeInPast()
+        {
+            // Get current time, and set past -1min time
+            auto t1 = hostEpoch.elapsed();
+            EXPECT_NE(0, t1);
+            microseconds diff = 1min;
+            auto t2 = t1 - diff.count();
+            hostEpoch.elapsed(t2);
+
+            // Verify that the offset shall be negative, and the absolute value
+            // shall be equal or greater than diff, and shall not be too greater
+            auto offset = getOffset();
+            EXPECT_LT(offset, USEC_ZERO);
+            offset = -offset;
+            EXPECT_GE(offset, diff);
+            diff += 10s;
+            EXPECT_LE(offset, diff);
+
+            // Now get time shall be around past -1min time
+            auto epochNow = duration_cast<microseconds>(
+                                system_clock::now().time_since_epoch()).count();
+            auto elapsedGot = hostEpoch.elapsed();
+            EXPECT_LT(elapsedGot, epochNow);
+            auto epochDiff = epochNow - elapsedGot;
+            diff = 1min;
+            EXPECT_GT(epochDiff, (diff - delta).count());
+            EXPECT_LT(epochDiff, (diff + delta).count());
         }
 };
 
@@ -80,26 +159,90 @@
     // Read it back
     microseconds offsetToRead;
     offsetToRead = microseconds(
-        utils::readData<decltype(offsetToRead)::rep>(FILE_OFFSET));
+                       utils::readData<decltype(offsetToRead)::rep>(FILE_OFFSET));
     EXPECT_EQ(offsetToWrite, offsetToRead);
 }
 
-TEST_F(TestHostEpoch, setElapsedNotAllowed)
+TEST_F(TestHostEpoch, setElapsedInNtpBmc)
 {
-    // By default offset shall be 0
-    EXPECT_EQ(0, getOffset().count());
-
-    // Set time in BMC mode is not allowed,
-    // so verify offset is still 0 after set time
-    microseconds diff = 1min;
-    hostEpoch.elapsed(hostEpoch.elapsed() + diff.count());
-    EXPECT_EQ(0, getOffset().count());
+    // Set time in NTP/BMC is not allowed
+    setTimeMode(Mode::NTP);
+    setTimeOwner(Owner::BMC);
+    checkSettingTimeNotAllowed();
 }
 
-TEST_F(TestHostEpoch, setElapsedInFutureAndGet)
+TEST_F(TestHostEpoch, setElapsedInNtpHost)
 {
-    // Set to HOST owner so that we can set elapsed
+    // Set time in NTP/HOST is not allowed
+    setTimeMode(Mode::NTP);
     setTimeOwner(Owner::HOST);
+    checkSettingTimeNotAllowed();
+}
+
+TEST_F(TestHostEpoch, setElapsedInNtpSplit)
+{
+    // Set time in NTP/SPLIT, offset will be set
+    setTimeMode(Mode::NTP);
+    setTimeOwner(Owner::SPLIT);
+
+    checkSetSplitTimeInFuture();
+
+    // Reset offset
+    setOffset(USEC_ZERO);
+    checkSetSplitTimeInPast();
+}
+
+TEST_F(TestHostEpoch, setElapsedInNtpBoth)
+{
+    // Set time in NTP/BOTH is not allowed
+    setTimeMode(Mode::NTP);
+    setTimeOwner(Owner::BOTH);
+    checkSettingTimeNotAllowed();
+}
+
+TEST_F(TestHostEpoch, setElapsedInManualBmc)
+{
+    // Set time in MANUAL/BMC is not allowed
+    setTimeMode(Mode::MANUAL);
+    setTimeOwner(Owner::BMC);
+    checkSettingTimeNotAllowed();
+}
+
+TEST_F(TestHostEpoch, setElapsedInManualHost)
+{
+    // Set time in MANUAL/HOST, time will be set to BMC
+    // However it requies gmock to test this case
+    // TODO: when gmock is ready, test this case.
+    setTimeMode(Mode::MANUAL);
+    setTimeOwner(Owner::HOST);
+}
+
+TEST_F(TestHostEpoch, setElapsedInManualSplit)
+{
+    // Set to SPLIT owner so that offset will be set
+    setTimeMode(Mode::MANUAL);
+    setTimeOwner(Owner::SPLIT);
+
+    checkSetSplitTimeInFuture();
+
+    // Reset offset
+    setOffset(USEC_ZERO);
+    checkSetSplitTimeInPast();
+}
+
+TEST_F(TestHostEpoch, setElapsedInManualBoth)
+{
+    // Set time in MANUAL/BOTH, time will be set to BMC
+    // However it requies gmock to test this case
+    // TODO: when gmock is ready, test this case.
+    setTimeMode(Mode::MANUAL);
+    setTimeOwner(Owner::BOTH);
+}
+
+TEST_F(TestHostEpoch, setElapsedInSplitAndBmcTimeIsChanged)
+{
+    // Set to SPLIT owner so that offset will be set
+    setTimeOwner(Owner::SPLIT);
 
     // Get current time, and set future +1min time
     auto t1 = hostEpoch.elapsed();
@@ -111,52 +254,38 @@
     // Verify that the offset shall be positive,
     // and less or equal to diff, and shall be not too less.
     auto offset = getOffset();
-    EXPECT_GT(offset, microseconds(0));
+    EXPECT_GT(offset, USEC_ZERO);
     EXPECT_LE(offset, diff);
     diff -= delta;
     EXPECT_GE(offset, diff);
 
-    // Now get time shall be around future +1min time
-    auto epochNow = duration_cast<microseconds>(
-        system_clock::now().time_since_epoch()).count();
-    auto elapsedGot = hostEpoch.elapsed();
-    EXPECT_LT(epochNow, elapsedGot);
-    auto epochDiff = elapsedGot - epochNow;
-    diff = 1min;
-    EXPECT_GT(epochDiff, (diff - delta).count());
-    EXPECT_LT(epochDiff, (diff + delta).count());
+    // Now BMC time is changed to future +1min
+    hostEpoch.onBmcTimeChanged(microseconds(t2));
+
+    // Verify that the offset shall be around zero since it's almost
+    // the same as BMC time
+    offset = getOffset();
+    if (offset.count() < 0)
+    {
+        offset = microseconds(-offset.count());
+    }
+    EXPECT_LE(offset, delta);
 }
 
-TEST_F(TestHostEpoch, setElapsedInPastAndGet)
+TEST_F(TestHostEpoch, clearOffsetOnOwnerChange)
 {
-    // Set to HOST owner so that we can set elapsed
-    setTimeOwner(Owner::HOST);
+    EXPECT_EQ(USEC_ZERO, getOffset());
 
-    // Get current time, and set past -1min time
-    auto t1 = hostEpoch.elapsed();
-    EXPECT_NE(0, t1);
-    microseconds diff = 1min;
-    auto t2 = t1 - diff.count();
-    hostEpoch.elapsed(t2);
+    setTimeOwner(Owner::SPLIT);
+    hostEpoch.onBmcTimeChanged(microseconds(hostEpoch.elapsed()) + 1min);
 
-    // Verify that the offset shall be negative, and the absolute value
-    // shall be equal or greater than diff, and shall not be too greater
-    auto offset = getOffset();
-    EXPECT_LT(offset, microseconds(0));
-    offset = -offset;
-    EXPECT_GE(offset, diff);
-    diff += 10s;
-    EXPECT_LE(offset, diff);
+    // Now offset shall be non zero
+    EXPECT_NE(USEC_ZERO, getOffset());
 
-    // Now get time shall be around past -1min time
-    auto epochNow = duration_cast<microseconds>(
-        system_clock::now().time_since_epoch()).count();
-    auto elapsedGot = hostEpoch.elapsed();
-    EXPECT_LT(elapsedGot, epochNow);
-    auto epochDiff = epochNow - elapsedGot;
-    diff = 1min;
-    EXPECT_GT(epochDiff, (diff - delta).count());
-    EXPECT_LT(epochDiff, (diff + delta).count());
+    setTimeOwner(Owner::BOTH);
+
+    // Now owner is BOTH, the offset shall be cleared
+    EXPECT_EQ(USEC_ZERO, getOffset());
 }
 
 }
diff --git a/test/mocked_bmc_time_change_listener.hpp b/test/mocked_bmc_time_change_listener.hpp
new file mode 100644
index 0000000..9b2c81c
--- /dev/null
+++ b/test/mocked_bmc_time_change_listener.hpp
@@ -0,0 +1,16 @@
+#pragma once
+#include <gmock/gmock.h>
+#include "bmc_time_change_listener.hpp"
+
+namespace phosphor {
+namespace time {
+
+class MockBmcTimeChangeListener : public BmcTimeChangeListener {
+ public:
+  MOCK_METHOD1(onBmcTimeChanged,
+      void(const std::chrono::microseconds&));
+};
+
+}  // namespace time
+}  // namespace phosphor
+