Intialize new phosphor-time-manager

phosphor-time-manager will be refactored to use sdbusplus interfaces.
This is a initial commit that EpochBase is implemented based on dbus
interface xyz/openbmc_project/Time/EpochTime.interface.yaml.

EpochBase is the base class that wraps EpochTime interface, and is
initialized with time mode and owner from settingsd.

An initial unit test case is added.

Change-Id: Ic944b70f63ec3c0329762cc8874f0f57b09ddce3
Signed-off-by: Lei YU <mine260309@gmail.com>
diff --git a/Makefile.am b/Makefile.am
index b25fa12..4b4cf02 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,17 +1,28 @@
 AM_DEFAULT_SOURCE_EXT = .cpp
 
-sbin_PROGRAMS = \
-	timemanager
+sbin_PROGRAMS = phosphor-timemanager
 
-timemanager_SOURCES = \
-	time-register.c \
-	time-config.cpp \
-	time-manager.cpp \
-	settings.cpp
+noinst_LTLIBRARIES = libtimemanager.la
 
-timemanager_LDFLAGS = \
-	$(SYSTEMD_LIBS) \
-	$(libmapper_LIBS) \
-	$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
-	$(SDBUSPLUS_LIBS) \
-	$(PHOSPHOR_LOGGING_LIBS)
+libtimemanager_la_SOURCES = \
+	epoch_base.cpp
+
+phosphor_timemanager_SOURCES = \
+	main.cpp
+
+phosphor_timemanager_LDADD = libtimemanager.la
+
+generic_cxx_flags = $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+                    $(SDBUSPLUS_CFLAGS)
+
+generic_ld_flags = $(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+                   $(SDBUSPLUS_LIBS)
+
+libtimemanager_la_CXXFLAGS = $(generic_cxx_flags)
+libtimemanager_la_LIBADD = $(generic_ld_flags)
+
+phosphor_timemanager_CXXFLAGS = $(generic_cxx_flags)
+
+phosphor_timemanager_LDFLAGS = $(generic_ld_flags)
+
+SUBDIRS = . test
diff --git a/configure.ac b/configure.ac
index fc0da82..03f8347 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,43 +1,34 @@
 # Initialization
 AC_PREREQ([2.69])
 AC_INIT([phosphor-time-manager], [1.0], [https://github.com/openbmc/phosphor-time-manager/issues])
+AC_LANG([C++])
 AC_CONFIG_HEADERS([config.h])
 AM_INIT_AUTOMAKE([subdir-objects -Wall -Werror foreign dist-xz])
 AM_SILENT_RULES([yes])
 
-# Checks for programs.
+# Checks for programs
 AC_PROG_CXX
-AX_CXX_COMPILE_STDCXX_14([noext])
-AC_PROG_CC
 AM_PROG_AR
-AC_PROG_INSTALL
-AC_PROG_MAKE_SET
 
-# Checks for libraries.
-AC_CHECK_LIB([mapper], [mapper_get_service], ,[AC_MSG_ERROR([Could not find libmapper...openbmc/phosphor-objmgr package required])])
-PKG_CHECK_MODULES([SYSTEMD], [libsystemd >= 221])
+# Surpress the --with-libtool-sysroot error
+LT_INIT
+
+# Check for libraries
+PKG_CHECK_MODULES([SYSTEMD], [libsystemd >= 221],,\
+    AC_MSG_ERROR(["Systemd required and not found"]))
 PKG_CHECK_MODULES([PHOSPHOR_DBUS_INTERFACES], [phosphor-dbus-interfaces],,\
     AC_MSG_ERROR(["Requires phosphor-dbus-interfaces package."]))
-PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus],,
+PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus],,\
     AC_MSG_ERROR(["Requires sdbusplus package."]))
 PKG_CHECK_MODULES([PHOSPHOR_LOGGING], [phosphor-logging],,\
     AC_MSG_ERROR(["Requires phosphor-logging package."]))
 
-# Checks for header files.
-AC_CHECK_HEADER(systemd/sd-bus.h, ,[AC_MSG_ERROR([Could not find systemd/sd-bus.h...systemd developement package required])])
-
-# Checks for typedefs, structures, and compiler characteristics.
-AX_CXX_COMPILE_STDCXX_14([noext])
-AX_APPEND_COMPILE_FLAGS([-Wall -Werror], [CFLAGS])
-AX_APPEND_COMPILE_FLAGS([-Wall -Werror], [CXXFLAGS])
-
-# Checks for library functions.
-LT_INIT # Removes 'unrecognized options: --with-libtool-sysroot'
-
+# gtest
 # Check/set gtest specific functions.
 AX_PTHREAD([GTEST_CPPFLAGS="-DGTEST_HAS_PTHREAD=1"],[GTEST_CPPFLAGS="-DGTEST_HAS_PTHREAD=0"])
 AC_SUBST(GTEST_CPPFLAGS)
 
+# Test cases require SDK so only build if we're told to (and SDK is available)
 AC_ARG_ENABLE([oe-sdk],
     AS_HELP_STRING([--enable-oe-sdk], [Link testcases absolutely against OE SDK so they can be ran within it.])
 )
@@ -56,6 +47,23 @@
     AC_SUBST([OESDK_TESTCASE_FLAGS], [$testcase_flags])
 )
 
-# Create configured output
-AC_CONFIG_FILES([Makefile])
+# Checks for typedefs, structures, and compiler characteristics.
+AX_CXX_COMPILE_STDCXX_14([noext])
+AX_APPEND_COMPILE_FLAGS([-fpic -Wall -Werror], [CXXFLAGS])
+
+# DBUS interface
+AC_ARG_VAR(BUSNAME, [The Time Manager Dbus busname to own])
+AS_IF([test "x$BUSNAME" == "x"], [BUSNAME="xyz.openbmc_project.Time.Manager"])
+AC_DEFINE_UNQUOTED([BUSNAME], ["$BUSNAME"], [The Time Manager DBus busname to own])
+
+AC_ARG_VAR(OBJPATH_BMC, [The bmc epoch Dbus root])
+AS_IF([test "x$OBJPATH_BMC" == "x"], [OBJPATH_BMC="/xyz/openbmc_project/time/bmc"])
+AC_DEFINE_UNQUOTED([OBJPATH_BMC], ["$OBJPATH_BMC"], [The bmc epoch Dbus root])
+
+AC_ARG_VAR(OBJPATH_HOST, [The host epoch Dbus root])
+AS_IF([test "x$OBJPATH_HOST" == "x"], [OBJPATH_HOST="/xyz/openbmc_project/time/host"])
+AC_DEFINE_UNQUOTED([OBJPATH_HOST], ["$OBJPATH_HOST"], [The host epoch Dbus root])
+
+
+AC_CONFIG_FILES([Makefile test/Makefile])
 AC_OUTPUT
diff --git a/epoch_base.cpp b/epoch_base.cpp
new file mode 100644
index 0000000..06232bb
--- /dev/null
+++ b/epoch_base.cpp
@@ -0,0 +1,131 @@
+#include "epoch_base.hpp"
+
+#include <phosphor-logging/log.hpp>
+
+#include <iomanip>
+#include <sstream>
+
+namespace // anonymous
+{
+constexpr auto SETTINGS_SERVICE = "org.openbmc.settings.Host";
+constexpr auto SETTINGS_PATH = "/org/openbmc/settings/host0";
+constexpr auto SETTINGS_INTERFACE = "org.openbmc.settings.Host";
+constexpr auto PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties";
+constexpr auto METHOD_GET = "Get";
+}
+
+namespace phosphor
+{
+namespace time
+{
+
+using namespace phosphor::logging;
+
+const std::map<std::string, EpochBase::Owner>
+EpochBase::ownerMap = {
+    { "BMC", EpochBase::Owner::BMC },
+    { "HOST", EpochBase::Owner::HOST },
+    { "SPLIT", EpochBase::Owner::SPLIT },
+    { "BOTH", EpochBase::Owner::BOTH },
+};
+
+EpochBase::EpochBase(sdbusplus::bus::bus& bus,
+                     const char* objPath)
+    : sdbusplus::server::object::object<EpochTime>(bus, objPath, true),
+      bus(bus)
+{
+    initialize();
+    // Deferred this until we could get our property correct
+    emit_object_added();
+}
+
+void EpochBase::setCurrentTimeMode(const std::string& mode)
+{
+    log<level::INFO>("Time mode is changed",
+                     entry("MODE=%s", mode.c_str()));
+    timeMode = convertToMode(mode);
+}
+
+void EpochBase::setCurrentTimeOwner(const std::string& owner)
+{
+    log<level::INFO>("Time owner is changed",
+                     entry("OWNER=%s", owner.c_str()));
+    timeOwner = convertToOwner(owner);
+}
+
+void EpochBase::initialize()
+{
+    setCurrentTimeMode(getSettings("time_mode"));
+    setCurrentTimeOwner(getSettings("time_owner"));
+    // TODO: subscribe settingsd's property changes callback
+}
+
+std::string EpochBase::getSettings(const char* value) const
+{
+    sdbusplus::message::variant<std::string> mode;
+    auto method = bus.new_method_call(SETTINGS_SERVICE,
+                                      SETTINGS_PATH,
+                                      PROPERTY_INTERFACE,
+                                      METHOD_GET);
+    method.append(SETTINGS_INTERFACE, value);
+    auto reply = bus.call(method);
+    if (reply)
+    {
+        reply.read(mode);
+    }
+
+    return mode.get<std::string>();
+}
+
+EpochBase::Mode EpochBase::convertToMode(const std::string& mode)
+{
+    if (mode == "NTP")
+    {
+        return Mode::NTP;
+    }
+    else if (mode == "MANUAL")
+    {
+        return Mode::MANUAL;
+    }
+    else
+    {
+        log<level::ERR>("Unrecognized mode",
+                        entry("%s", mode.c_str()));
+        return Mode::NTP;
+    }
+}
+
+EpochBase::Owner EpochBase::convertToOwner(const std::string& owner)
+{
+    auto it = ownerMap.find(owner);
+    if (it == ownerMap.end())
+    {
+        log<level::ERR>("Unrecognized owner",
+                        entry("%s", owner.c_str()));
+        return Owner::BMC;
+    }
+    return it->second;
+}
+
+using namespace std::chrono;
+void EpochBase::setTime(const microseconds& usec)
+{
+    auto method = bus.new_method_call("org.freedesktop.timedate1",
+                                      "/org/freedesktop/timedate1",
+                                      "org.freedesktop.timedate1",
+                                      "SetTime");
+    method.append(static_cast<int64_t>(usec.count()),
+                  false, // relative
+                  false); // user_interaction
+    bus.call_noreply(method);
+}
+
+microseconds EpochBase::getTime() const
+{
+    auto now = system_clock::now();
+    return duration_cast<microseconds>
+           (now.time_since_epoch());
+}
+
+} // namespace time
+} // namespace phosphor
diff --git a/epoch_base.hpp b/epoch_base.hpp
new file mode 100644
index 0000000..fc505d0
--- /dev/null
+++ b/epoch_base.hpp
@@ -0,0 +1,146 @@
+#pragma once
+
+#include <sdbusplus/bus.hpp>
+#include <xyz/openbmc_project/Time/EpochTime/server.hpp>
+
+#include <chrono>
+
+namespace phosphor
+{
+namespace time
+{
+
+/** @class EpochBase
+ *  @brief Base class for OpenBMC EpochTime implementation.
+ *  @details A base class that implements xyz.openbmc_project.Time.EpochTime
+ *  DBus API for epoch time.
+ */
+class EpochBase : public sdbusplus::server::object::object <
+    sdbusplus::xyz::openbmc_project::Time::server::EpochTime >
+{
+    public:
+        friend class TestEpochBase;
+
+        /** @brief Supported time modes
+         *  NTP     Time sourced by Network Time Server
+         *  MANUAL  User of the system need to set the time
+         */
+        enum class Mode
+        {
+            NTP,
+            MANUAL,
+        };
+
+        /** @brief Supported time owners
+         *  BMC     Time source may be NTP or MANUAL but it has to be set natively
+         *          on the BMC. Meaning, host can not set the time. What it also
+         *          means is that when BMC gets IPMI_SET_SEL_TIME, then its ignored.
+         *          similarly, when BMC gets IPMI_GET_SEL_TIME, then the BMC's time
+         *          is returned.
+         *
+         *  HOST    Its only IPMI_SEL_SEL_TIME that will set the time on BMC.
+         *          Meaning, IPMI_GET_SEL_TIME and request to get BMC time will
+         *          result in same value.
+         *
+         *  SPLIT   Both BMC and HOST will maintain their individual clocks but then
+         *          the time information is stored in BMC. BMC can have either NTP
+         *          or MANUAL as it's source of time and will set the time directly
+         *          on the BMC. When IPMI_SET_SEL_TIME is received, then the delta
+         *          between that and BMC's time is calculated and is stored.
+         *          When BMC reads the time, the current time is returned.
+         *          When IPMI_GET_SEL_TIME is received, BMC's time is retrieved and
+         *          then the delta offset is factored in prior to returning.
+         *
+         *  BOTH:   BMC's time is set with whoever that sets the time. Similarly,
+         *          BMC's time is returned to whoever that asks the time.
+         */
+        enum class Owner
+        {
+            BMC,
+            HOST,
+            SPLIT,
+            BOTH,
+        };
+
+        EpochBase(sdbusplus::bus::bus& bus,
+                  const char* objPath);
+
+    protected:
+        /** @brief Persistent sdbusplus DBus connection */
+        sdbusplus::bus::bus& bus;
+
+        /** @brief The current time mode */
+        Mode timeMode;
+
+        /** @brief The current time owner */
+        Owner timeOwner;
+
+        /** @brief Set current time to system
+         *
+         * This function set the time to system by invoking systemd
+         * org.freedesktop.timedate1's SetTime method.
+         *
+         * @param[in] timeOfDayUsec - Microseconds since UTC
+         */
+        void setTime(const std::chrono::microseconds& timeOfDayUsec);
+
+        /** @brief Get current time
+         *
+         * @return Microseconds since UTC
+         */
+        std::chrono::microseconds getTime() const;
+
+        /** @brief Convert a string to enum Mode
+         *
+         * Convert the time mode string to enum.
+         * Valid strings are "NTP", "MANUAL"
+         * If it's not a valid time mode string, return NTP.
+         *
+         * @param[in] mode - The string of time mode
+         *
+         * @return The Mode enum
+         */
+        static Mode convertToMode(const std::string& mode);
+
+        /** @brief Convert a string to enum Owner
+         *
+         * Convert the time owner string to enum.
+         * Valid strings are "BMC", "HOST", "SPLIT", "BOTH"
+         * If it's not a valid time owner string, return BMC.
+         *
+         * @param[in] owner - The string of time owner
+         *
+         * @return The Owner enum
+         */
+        static Owner convertToOwner(const std::string& owner);
+
+    private:
+        /** @brief Initialize the time mode and owner */
+        void initialize();
+
+        /** @brief Set current time mode
+         *
+         * @param[in] mode - The string of time mode
+         */
+        void setCurrentTimeMode(const std::string& mode);
+
+        /** @brief Set current time owner
+         *
+         * @param[in] owner - The string of time owner
+         */
+        void setCurrentTimeOwner(const std::string& owner);
+
+        /** @brief Get setting value from settings manager
+         *
+         * @param[in] setting - The string of the setting to get
+         *
+         * @return The value of the setting
+         */
+        std::string getSettings(const char* setting) const;
+
+        /** @brief The map maps the string key to enum Owner */
+        static const std::map<std::string, Owner> ownerMap;
+};
+
+} // namespace time
+} // namespace phosphor
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..0cf2807
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,17 @@
+#include <sdbusplus/bus.hpp>
+
+#include "config.h"
+
+int main()
+{
+    auto bus = sdbusplus::bus::new_default();
+
+    bus.request_name(BUSNAME);
+
+    while (true)
+    {
+        bus.process_discard();
+        bus.wait();
+    }
+    return 0;
+}
diff --git a/test/Makefile.am b/test/Makefile.am
new file mode 100644
index 0000000..66e1ae5
--- /dev/null
+++ b/test/Makefile.am
@@ -0,0 +1,26 @@
+AM_CPPFLAGS = -I${top_srcdir}
+check_PROGRAMS =
+
+TESTS = $(check_PROGRAMS)
+
+check_PROGRAMS += test
+
+test_SOURCES = \
+    TestEpochBase.cpp
+
+test_LDADD = $(top_builddir)/libtimemanager.la
+
+test_CPPFLAGS = $(GTEST_CPPFLAGS) \
+                $(AM_CPPFLAGS)
+
+test_CXXFLAGS = $(PTHREAD_CFLAGS) \
+                $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+                $(SDBUSPLUS_CFLAGS)
+
+test_LDFLAGS = -lgmock_main \
+               -lgmock \
+               $(PTHREAD_LIBS) \
+               $(OESDK_TESTCASE_FLAGS)
+
+test_LDFLAGS += $(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+                $(SDBUSPLUS_LIBS)
diff --git a/test/TestEpochBase.cpp b/test/TestEpochBase.cpp
new file mode 100644
index 0000000..c3ca7e1
--- /dev/null
+++ b/test/TestEpochBase.cpp
@@ -0,0 +1,64 @@
+#include <sdbusplus/bus.hpp>
+#include <gtest/gtest.h>
+
+#include "epoch_base.hpp"
+
+namespace phosphor
+{
+namespace time
+{
+
+class TestEpochBase : public testing::Test
+{
+    public:
+        using Mode = EpochBase::Mode;
+        using Owner = EpochBase::Owner;
+
+        sdbusplus::bus::bus bus;
+        EpochBase epochBase;
+
+        TestEpochBase()
+            : bus(sdbusplus::bus::new_default()),
+              epochBase(bus, "")
+        {
+            // Empty
+        }
+
+        // Proxies for EpochBase's private members and functions
+        Mode convertToMode(const std::string& mode)
+        {
+            return EpochBase::convertToMode(mode);
+        }
+        Owner convertToOwner(const std::string& owner)
+        {
+            return EpochBase::convertToOwner(owner);
+        }
+};
+
+TEST_F(TestEpochBase, convertToMode)
+{
+    EXPECT_EQ(Mode::NTP, convertToMode("NTP"));
+    EXPECT_EQ(Mode::MANUAL, convertToMode("MANUAL"));
+
+    // All unrecognized strings are mapped to Ntp
+    EXPECT_EQ(Mode::NTP, convertToMode(""));
+    EXPECT_EQ(Mode::NTP, convertToMode("Manual"));
+    EXPECT_EQ(Mode::NTP, convertToMode("whatever"));
+}
+
+
+TEST_F(TestEpochBase, convertToOwner)
+{
+    EXPECT_EQ(Owner::BMC, convertToOwner("BMC"));
+    EXPECT_EQ(Owner::HOST, convertToOwner("HOST"));
+    EXPECT_EQ(Owner::SPLIT, convertToOwner("SPLIT"));
+    EXPECT_EQ(Owner::BOTH, convertToOwner("BOTH"));
+
+    // All unrecognized strings are mapped to Bmc
+    EXPECT_EQ(Owner::BMC, convertToOwner(""));
+    EXPECT_EQ(Owner::BMC, convertToOwner("Split"));
+    EXPECT_EQ(Owner::BMC, convertToOwner("xyz"));
+}
+
+}
+}