Add sbusplus properties server

Add properties server similar to boost-dbus that allows
for creating dbus interfaces during runtime. This adds
support for creating methods and get / set properties.

Get / set property callbacks are stored in flat_maps of
std::function allowing custom get / set functions. Methods
are also stored in this way allowing for creating of interfaces
without using any yaml. There is one C level callback for properties
get, properties set, and method calls that lookup the correct
std::function in the flat_map to call.

Tested: Ran asio-example on bmc, and updated fru-device.
Change-Id: I19881049f4307fe9c68f78df8854f14afdd6c362
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/Makefile.am b/Makefile.am
index 94a88f6..533f423 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -6,6 +6,7 @@
 	mapbox/recursive_wrapper.hpp \
 	mapbox/variant.hpp \
 	sdbusplus/asio/connection.hpp \
+	sdbusplus/asio/object_server.hpp \
 	sdbusplus/asio/detail/async_send_handler.hpp \
 	sdbusplus/bus.hpp \
 	sdbusplus/bus/match.hpp \
diff --git a/example/asio-example.cpp b/example/asio-example.cpp
index 7dde720..82d713e 100644
--- a/example/asio-example.cpp
+++ b/example/asio-example.cpp
@@ -1,9 +1,17 @@
 #include <iostream>
+#include <ctime>
+#include <chrono>
 #include <sdbusplus/bus.hpp>
 #include <sdbusplus/server.hpp>
 #include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
 #include <boost/asio.hpp>
 
+int foo(int test)
+{
+    return ++test;
+}
+
 int main()
 {
     using GetSubTreeType = std::vector<std::pair<
@@ -59,6 +67,56 @@
         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
         "/org/openbmc/control", 2, std::vector<std::string>());
 
+    // test object server
+    conn->request_name("xyz.openbmc_project.asio-test");
+    auto server = sdbusplus::asio::object_server(conn);
+    std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
+        server.add_interface("/xyz/openbmc_project/test",
+                             "xyz.openbmc_project.test");
+    // test generic properties
+    iface->register_property("int", 33,
+                             sdbusplus::asio::PropertyPermission::readWrite);
+    std::vector<std::string> myStringVec = {"some", "test", "data"};
+    std::vector<std::string> myStringVec2 = {"more", "test", "data"};
+
+    iface->register_property("myStringVec", myStringVec,
+                             sdbusplus::asio::PropertyPermission::readWrite);
+    iface->register_property("myStringVec2", myStringVec2);
+
+    // test properties with specialized callbacks
+    iface->register_property("lessThan50", 23,
+                             // custom set
+                             [](const int& req, int& propertyValue) {
+                                 if (req >= 50)
+                                 {
+                                     return -EINVAL;
+                                 }
+                                 propertyValue = req;
+                                 return 1; // success
+                             });
+    iface->register_property(
+        "TrailTime", std::string("foo"),
+        // custom set
+        [](const std::string& req, std::string& propertyValue) {
+            propertyValue = req;
+            return 1; // success
+        },
+        // custom get
+        [](const std::string& property) {
+            auto now = std::chrono::system_clock::now();
+            auto timePoint = std::chrono::system_clock::to_time_t(now);
+            return property + std::ctime(&timePoint);
+        });
+
+    // test method creation
+    iface->register_method("TestMethod", [](const int32_t& callCount) {
+        return "success: " + std::to_string(callCount);
+    });
+
+    iface->register_method("TestFunction", foo);
+
+    iface->initialize();
+    iface->set_property("int", 45);
     io.run();
 
     return 0;
diff --git a/sdbusplus/asio/object_server.hpp b/sdbusplus/asio/object_server.hpp
new file mode 100644
index 0000000..531b858
--- /dev/null
+++ b/sdbusplus/asio/object_server.hpp
@@ -0,0 +1,514 @@
+#pragma once
+
+#include <list>
+#include <sdbusplus/exception.hpp>
+#include <sdbusplus/server.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <sdbusplus/message/read.hpp>
+#include <sdbusplus/utility/tuple_to_array.hpp>
+#include <boost/any.hpp>
+#include <boost/container/flat_map.hpp>
+
+namespace sdbusplus
+{
+namespace asio
+{
+
+constexpr const char *PropertyNameAllowedCharacters =
+    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
+class callback
+{
+  public:
+    virtual int call(message::message &m) = 0;
+};
+class callback_set
+{
+  public:
+    virtual int call(message::message &m) = 0;
+    virtual int set(const boost::any &value) = 0;
+};
+
+template <typename CallbackType>
+class callback_method_instance : public callback
+{
+  public:
+    callback_method_instance(CallbackType &&func) : func_(std::move(func))
+    {
+    }
+    int call(message::message &m) override
+    {
+        InputTupleType inputArgs;
+        if (!utility::read_into_tuple(inputArgs, m))
+        {
+            return -EINVAL;
+        }
+
+        auto ret = m.new_method_return();
+        callFunction<ResultType>(ret, inputArgs);
+        ret.method_return();
+        return 1;
+    };
+
+  private:
+    using CallbackSignature = boost::callable_traits::args_t<CallbackType>;
+    using InputTupleType =
+        typename utility::decay_tuple<CallbackSignature>::type;
+    using ResultType = boost::callable_traits::return_type_t<CallbackType>;
+    CallbackType func_;
+    template <typename T>
+    std::enable_if_t<!std::is_void<T>::value, void>
+        callFunction(message::message &m, InputTupleType &inputArgs)
+    {
+        ResultType r = std::experimental::apply(func_, inputArgs);
+        m.append(r);
+    }
+    template <typename T>
+    std::enable_if_t<std::is_void<T>::value, void>
+        callFunction(message::message &m, InputTupleType &inputArgs)
+    {
+        std::experimental::apply(func_, inputArgs);
+    }
+};
+
+template <typename PropertyType, typename CallbackType>
+class callback_get_instance : public callback
+{
+  public:
+    callback_get_instance(const std::shared_ptr<PropertyType> &value,
+                          CallbackType &&func) :
+        value_(value),
+        func_(std::move(func))
+    {
+    }
+    int call(message::message &m) override
+    {
+        auto r = func_(*value_);
+        m.append(r);
+        return 1;
+    }
+
+  private:
+    std::shared_ptr<PropertyType> value_;
+    CallbackType func_;
+};
+
+template <typename PropertyType, typename CallbackType>
+class callback_set_instance : public callback_set
+{
+  public:
+    callback_set_instance(const std::shared_ptr<PropertyType> &value,
+                          CallbackType &&func) :
+        value_(value),
+        func_(std::move(func))
+    {
+    }
+    int call(message::message &m) override
+    {
+        PropertyType input;
+        m.read(input);
+
+        return func_(input, *value_);
+    }
+    int set(const boost::any &value)
+    {
+        return func_(boost::any_cast<PropertyType>(value), *value_);
+    }
+
+  private:
+    std::shared_ptr<PropertyType> value_;
+    CallbackType func_;
+};
+
+enum class PropertyPermission
+{
+    readOnly,
+    readWrite
+};
+class dbus_interface
+{
+  public:
+    dbus_interface(std::shared_ptr<sdbusplus::asio::connection> conn,
+                   const std::string &path, const std::string &name) :
+        conn_(conn),
+        path_(path), name_(name)
+
+    {
+        vtable_.emplace_back(vtable::start());
+    }
+
+    // default getter and setter
+    template <typename PropertyType>
+    bool register_property(
+        const std::string &name, const PropertyType &property,
+        PropertyPermission access = PropertyPermission::readOnly)
+    {
+        // can only register once
+        if (initialized_)
+        {
+            return false;
+        }
+        if (name.find_first_not_of(PropertyNameAllowedCharacters) !=
+            std::string::npos)
+        {
+            return false;
+        }
+        static const auto type =
+            utility::tuple_to_array(message::types::type_id<PropertyType>());
+
+        auto nameItr = propertyNames_.emplace(propertyNames_.end(), name);
+        auto propertyPtr = std::make_shared<PropertyType>(property);
+
+        callbacksGet_[name] = std::make_unique<callback_get_instance<
+            PropertyType, std::function<PropertyType(const PropertyType &)>>>(
+            propertyPtr, [](const PropertyType &value) { return value; });
+        callbacksSet_[name] = std::make_unique<callback_set_instance<
+            PropertyType,
+            std::function<int(const PropertyType &, PropertyType &)>>>(
+            propertyPtr, [](const PropertyType &req, PropertyType &old) {
+                old = req;
+                return 1;
+            });
+
+        if (access == PropertyPermission::readOnly)
+        {
+            vtable_.emplace_back(
+                vtable::property(nameItr->c_str(), type.data(), get_handler,
+                                 vtable::property_::emits_change));
+        }
+        else
+        {
+            vtable_.emplace_back(
+                vtable::property(nameItr->c_str(), type.data(), get_handler,
+                                 set_handler, vtable::property_::emits_change));
+        }
+        return true;
+    }
+
+    // custom setter, sets take an input property and respond with an int status
+    template <typename PropertyType, typename CallbackType>
+    bool register_property(const std::string &name,
+                           const PropertyType &property,
+                           CallbackType &&setFunction)
+    {
+        // can only register once
+        if (initialized_)
+        {
+            return false;
+        }
+        if (name.find_first_not_of(PropertyNameAllowedCharacters) !=
+            std::string::npos)
+        {
+            return false;
+        }
+        static const auto type =
+            utility::tuple_to_array(message::types::type_id<PropertyType>());
+
+        auto nameItr = propertyNames_.emplace(propertyNames_.end(), name);
+        auto propertyPtr = std::make_shared<PropertyType>(property);
+
+        callbacksGet_[name] = std::make_unique<callback_get_instance<
+            PropertyType, std::function<PropertyType(const PropertyType &)>>>(
+            propertyPtr, [](const PropertyType &value) { return value; });
+
+        callbacksSet_[name] =
+            std::make_unique<callback_set_instance<PropertyType, CallbackType>>(
+                propertyPtr, std::move(setFunction));
+        vtable_.emplace_back(vtable::property(nameItr->c_str(), type.data(),
+                                              get_handler, set_handler,
+                                              vtable::property_::emits_change));
+
+        return true;
+    }
+
+    // custom getter and setter, gets take an input of void and respond with a
+    // property. property is only passed for type deduction
+    template <typename PropertyType, typename CallbackType,
+              typename CallbackTypeGet>
+    bool register_property(const std::string &name,
+                           const PropertyType &property,
+                           CallbackType &&setFunction,
+                           CallbackTypeGet &&getFunction)
+    {
+        // can only register once
+        if (initialized_)
+        {
+            return false;
+        }
+        if (name.find_first_not_of(PropertyNameAllowedCharacters) !=
+            std::string::npos)
+        {
+            return false;
+        }
+        static const auto type =
+            utility::tuple_to_array(message::types::type_id<PropertyType>());
+
+        auto nameItr = propertyNames_.emplace(propertyNames_.end(), name);
+        auto propertyPtr = std::make_shared<PropertyType>(property);
+
+        callbacksGet_[name] = std::make_unique<
+            callback_get_instance<PropertyType, CallbackTypeGet>>(
+            propertyPtr, std::move(getFunction));
+        callbacksSet_[name] =
+            std::make_unique<callback_set_instance<PropertyType, CallbackType>>(
+                propertyPtr, std::move(setFunction));
+
+        vtable_.emplace_back(vtable::property(nameItr->c_str(), type.data(),
+                                              get_handler, set_handler,
+                                              vtable::property_::emits_change));
+
+        return true;
+    }
+    template <typename PropertyType>
+    bool set_property(const std::string &name, const PropertyType &value)
+    {
+        if (!initialized_)
+        {
+            return false;
+        }
+        auto func = callbacksSet_.find(name);
+        if (func != callbacksSet_.end())
+        {
+            if (func->second->set(value) != 1)
+            {
+                return false;
+            }
+            signal_property(name);
+            return true;
+        }
+        return false;
+    }
+
+    template <typename CallbackType>
+    bool register_method(const std::string &name, CallbackType &&handler)
+    {
+        using CallbackSignature = boost::callable_traits::args_t<CallbackType>;
+        using InputTupleType =
+            typename utility::decay_tuple<CallbackSignature>::type;
+        using ResultType = boost::callable_traits::return_type_t<CallbackType>;
+
+        if (initialized_)
+        {
+            return false;
+        }
+        static const auto argType = utility::strip_ends(
+            utility::tuple_to_array(message::types::type_id<InputTupleType>()));
+        static const auto resultType =
+            utility::tuple_to_array(message::types::type_id<ResultType>());
+
+        auto nameItr = methodNames_.emplace(methodNames_.end(), name);
+
+        callbacksMethod_[name] =
+            std::make_unique<callback_method_instance<CallbackType>>(
+                std::move(handler));
+
+        vtable_.emplace_back(vtable::method(nameItr->c_str(), argType.data(),
+                                            resultType.data(), method_handler));
+        return true;
+    }
+
+    static int get_handler(sd_bus *bus, const char *path, const char *interface,
+                           const char *property, sd_bus_message *reply,
+                           void *userdata, sd_bus_error *error)
+    {
+        dbus_interface *data = static_cast<dbus_interface *>(userdata);
+        auto func = data->callbacksGet_.find(property);
+        auto mesg = message::message(reply);
+        if (func != data->callbacksGet_.end())
+        {
+#ifdef __EXCEPTIONS
+            try
+            {
+#endif
+                return func->second->call(mesg);
+#ifdef __EXCEPTIONS
+            }
+
+            catch (sdbusplus::exception_t &e)
+            {
+                sd_bus_error_set_const(error, e.name(), e.description());
+                return -EINVAL;
+            }
+            catch (...)
+            {
+                // hit default error below
+            }
+#endif
+        }
+        sd_bus_error_set_const(error, SD_BUS_ERROR_INVALID_ARGS, NULL);
+        return -EINVAL;
+    }
+
+    static int set_handler(sd_bus *bus, const char *path, const char *interface,
+                           const char *property, sd_bus_message *value,
+                           void *userdata, sd_bus_error *error)
+    {
+        dbus_interface *data = static_cast<dbus_interface *>(userdata);
+        auto func = data->callbacksSet_.find(property);
+        auto mesg = message::message(value);
+        if (func != data->callbacksSet_.end())
+        {
+#ifdef __EXCEPTIONS
+            try
+            {
+#endif
+                int status = func->second->call(mesg);
+                if (status == 1)
+                {
+                    data->signal_property(property);
+                    return status;
+                }
+#ifdef __EXCEPTIONS
+            }
+
+            catch (sdbusplus::exception_t &e)
+            {
+                sd_bus_error_set_const(error, e.name(), e.description());
+                return -EINVAL;
+            }
+            catch (...)
+            {
+                // hit default error below
+            }
+#endif
+        }
+        sd_bus_error_set_const(error, SD_BUS_ERROR_INVALID_ARGS, NULL);
+        return -EINVAL;
+    }
+
+    static int method_handler(sd_bus_message *m, void *userdata,
+                              sd_bus_error *error)
+    {
+        dbus_interface *data = static_cast<dbus_interface *>(userdata);
+        auto mesg = message::message(m);
+        auto func = data->callbacksMethod_.find(mesg.get_member());
+        if (func != data->callbacksMethod_.end())
+        {
+#ifdef __EXCEPTIONS
+            try
+            {
+#endif
+                int status = func->second->call(mesg);
+                if (status == 1)
+                {
+                    return status;
+                }
+#ifdef __EXCEPTIONS
+            }
+
+            catch (sdbusplus::exception_t &e)
+            {
+                sd_bus_error_set_const(error, e.name(), e.description());
+                return -EINVAL;
+            }
+            catch (...)
+            {
+                // hit default error below
+            }
+#endif
+        }
+        sd_bus_error_set_const(error, SD_BUS_ERROR_INVALID_ARGS, NULL);
+        return -EINVAL;
+    }
+
+    bool initialize()
+    {
+        // can only register once
+        if (initialized_)
+        {
+            return false;
+        }
+        initialized_ = true;
+        vtable_.emplace_back(vtable::end());
+
+        interface_ = std::make_unique<sdbusplus::server::interface::interface>(
+            static_cast<sdbusplus::bus::bus &>(*conn_), path_.c_str(),
+            name_.c_str(), static_cast<const sd_bus_vtable *>(&vtable_[0]),
+            this);
+
+        for (const std::string &name : propertyNames_)
+        {
+            signal_property(name);
+        }
+        return true;
+    }
+    void signal_property(const std::string &name)
+    {
+        interface_->property_changed(name.c_str());
+    }
+
+    std::string get_object_path(void)
+    {
+        return path_;
+    }
+
+    std::string get_interface_name(void)
+    {
+        return name_;
+    }
+
+  private:
+    std::shared_ptr<sdbusplus::asio::connection> conn_;
+    std::string path_;
+    std::string name_;
+    std::list<std::string> propertyNames_;
+    std::list<std::string> methodNames_;
+    boost::container::flat_map<std::string, std::unique_ptr<callback>>
+        callbacksGet_;
+    boost::container::flat_map<std::string, std::unique_ptr<callback_set>>
+        callbacksSet_;
+    boost::container::flat_map<std::string, std::unique_ptr<callback>>
+        callbacksMethod_;
+    std::vector<sd_bus_vtable> vtable_;
+    std::unique_ptr<sdbusplus::server::interface::interface> interface_;
+
+    bool initialized_ = false;
+};
+
+class object_server
+{
+  public:
+    object_server(std::shared_ptr<sdbusplus::asio::connection> &conn) :
+        conn_(conn)
+    {
+        auto root = add_interface("/", "");
+        root->initialize();
+        add_manager("/");
+    }
+
+    std::shared_ptr<dbus_interface> add_interface(const std::string &path,
+                                                  const std::string &name)
+    {
+
+        auto dbusIface = std::make_shared<dbus_interface>(conn_, path, name);
+        interfaces_.emplace_back(dbusIface);
+        return dbusIface;
+    }
+
+    void add_manager(const std::string &path)
+    {
+        managers_.emplace_back(
+            std::make_unique<server::manager::manager>(server::manager::manager(
+                static_cast<sdbusplus::bus::bus &>(*conn_), path.c_str())));
+    }
+
+    bool remove_interface(std::shared_ptr<dbus_interface> &iface)
+    {
+        auto findIface =
+            std::find(interfaces_.begin(), interfaces_.end(), iface);
+        if (findIface != interfaces_.end())
+        {
+            interfaces_.erase(findIface);
+            return true;
+        }
+        return false;
+    }
+
+  private:
+    std::shared_ptr<sdbusplus::asio::connection> conn_;
+    std::vector<std::shared_ptr<dbus_interface>> interfaces_;
+    std::vector<std::unique_ptr<server::manager::manager>> managers_;
+};
+
+} // namespace asio
+} // namespace sdbusplus
diff --git a/sdbusplus/message/types.hpp b/sdbusplus/message/types.hpp
index 5d3be09..af7be35 100644
--- a/sdbusplus/message/types.hpp
+++ b/sdbusplus/message/types.hpp
@@ -238,6 +238,11 @@
 {
 };
 
+template <> struct type_id<void>
+{
+    constexpr static auto value = std::make_tuple('\0');
+};
+
 template <typename T> constexpr auto type_id_single()
 {
     static_assert(!std::is_base_of<undefined_type_id, type_id<T>>::value,
diff --git a/sdbusplus/utility/type_traits.hpp b/sdbusplus/utility/type_traits.hpp
index 3629d09..a88fcfa 100644
--- a/sdbusplus/utility/type_traits.hpp
+++ b/sdbusplus/utility/type_traits.hpp
@@ -43,6 +43,21 @@
     using type = std::tuple<typename std::decay<Args>::type...>;
 };
 
+// Small helper class for stripping off the first + last character of a char
+// array
+template <std::size_t N, std::size_t... Is>
+constexpr std::array<char, N - 2> strip_ends(const std::array<char, N>& s,
+                                             std::index_sequence<Is...>)
+{
+    return {(s[1 + Is])..., static_cast<char>(0)};
+};
+
+template <std::size_t N>
+constexpr std::array<char, N - 2> strip_ends(const std::array<char, N>& s)
+{
+    return strip_ends(s, std::make_index_sequence<N - 3>{});
+};
+
 } // namespace utility
 
 } // namespace sdbusplus