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