sdbus++: create server support functions

Reduce the complexity of the generated bindings by taking the
common patterns and moving them to support template functions.
This has the side-effect of a small space savings a library
of generated bindings.

Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: I55d6de0aa8773a68f1a23b1301056ea3435dbbc9
diff --git a/include/sdbusplus/sdbuspp_support/server.hpp b/include/sdbusplus/sdbuspp_support/server.hpp
new file mode 100644
index 0000000..0044e0b
--- /dev/null
+++ b/include/sdbusplus/sdbuspp_support/server.hpp
@@ -0,0 +1,140 @@
+#pragma once
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/server.hpp>
+
+#include <type_traits>
+
+/** This file contains support functions for sdbus++-generated server
+ *  bindings, which result in space savings by having common definitions.
+ */
+
+namespace sdbusplus
+{
+namespace sdbuspp
+{
+
+/** Handle common parts of a get/set property callback.
+ *
+ *  @param[in] msg The message to read to / write from.
+ *  @param[in] intf The SdBusInterface type.
+ *  @param[in] error The error object for any errors.
+ *  @param[in] f The interface function for the get/set.
+ *
+ *  This function will unpack a message's contents, redirect them to the
+ *  function 'f', and then pack them into the response message.
+ */
+template <typename... Args, typename Return>
+bool property_callback(sd_bus_message* msg, sdbusplus::SdBusInterface* intf,
+                       sd_bus_error* error, std::function<Return(Args&&...)> f)
+{
+    try
+    {
+        // Refcount the message.
+        auto m = message::message(msg, intf);
+
+        // Set up the transaction.
+        auto tbus = m.get_bus();
+        sdbusplus::server::transaction::Transaction t(tbus, m);
+        sdbusplus::server::transaction::set_id(
+            std::hash<sdbusplus::server::transaction::Transaction>{}(t));
+
+        // Read arguments from the message.
+        std::tuple<Args...> arg{};
+        std::apply([&](Args&... a) { (m.read(a), ...); }, arg);
+
+        // Call the function with the arguments.
+        if constexpr (!std::is_same_v<void, Return>)
+        {
+            // Pack results back into message.
+            m.append(std::apply(f, std::move(arg)));
+        }
+        else
+        {
+            // No return, just direct call.
+            std::apply(f, std::move(arg));
+        }
+    }
+    catch (sdbusplus::internal_exception_t& e)
+    {
+        return intf->sd_bus_error_set(error, e.name(), e.description());
+    }
+
+    return true;
+}
+
+/** Handle common parts of a method callback.
+ *
+ *  @tparam multi_return Set to true if the function returns multiple results.
+ *                       Otherwise, this function is unable to differentiate
+ *                       between a tuple that should be returned as a tuple or
+ *                       a tuple that contains the multiple results and should
+ *                       be returned without the tuple wrapper.
+ *
+ *  @param[in] msg The message to read to / write from.
+ *  @param[in] intf The SdBusInterface type.
+ *  @param[in] error The error object for any errors.
+ *  @param[in] f The interface function for the get/set.
+ *
+ *  @return a negative return-code on error.
+ *
+ *  This function will unpack a message's contents, redirect them to the
+ *  function 'f', and then pack them into the response message.
+ */
+template <bool multi_return = false, typename... Args, typename Return>
+int method_callback(sd_bus_message* msg, sdbusplus::SdBusInterface* intf,
+                    sd_bus_error* error, std::function<Return(Args&&...)> f)
+{
+    try
+    {
+        // Refcount the message.
+        auto m = message::message(msg, intf);
+
+        // Set up the transaction.
+        auto tbus = m.get_bus();
+        sdbusplus::server::transaction::Transaction t(tbus, m);
+        sdbusplus::server::transaction::set_id(
+            std::hash<sdbusplus::server::transaction::Transaction>{}(t));
+
+        // Read arguments from the message.
+        std::tuple<Args...> arg{};
+        std::apply([&](Args&... a) { (m.read(a), ...); }, arg);
+
+        // Get the reply message.
+        auto reply = m.new_method_return();
+
+        // Call the function with the arguments.
+        if constexpr (std::is_same_v<void, Return>)
+        {
+            // No return value, direct call.
+            std::apply(f, std::move(arg));
+        }
+        else if constexpr (!multi_return)
+        {
+            // Single return value; call and append to reply message.
+            reply.append(std::apply(f, std::move(arg)));
+        }
+        else
+        {
+            // Multi return values; call and append to reply message.
+            // Step 1: call 'f' with args from message.
+            //      - std::apply(f, std::move(arg))
+            // Step 2: append each return from f into the reply message one
+            //         at a time.
+            //      - Apply on return from 'f' into lambda that does an append.
+            std::apply([&](auto&&... v) { (reply.append(std::move(v)), ...); },
+                       std::apply(f, std::move(arg)));
+        }
+
+        // Indicate reply complete.
+        reply.method_return();
+    }
+    catch (sdbusplus::internal_exception_t& e)
+    {
+        return intf->sd_bus_error_set(error, e.name(), e.description());
+    }
+
+    return 0;
+}
+
+} // namespace sdbuspp
+} // namespace sdbusplus
diff --git a/tools/sdbusplus/method.py b/tools/sdbusplus/method.py
index 3535612..7dbb421 100644
--- a/tools/sdbusplus/method.py
+++ b/tools/sdbusplus/method.py
@@ -47,10 +47,6 @@
             [self.parameter(interface, p, defaultValue)
                 for p in self.parameters])
 
-    def parameters_as_local(self, interface):
-        return "{};\n    ".join([self.parameter(interface, p)
-                                for p in self.parameters])
-
     def or_cpp_flags(self, flags):
         """Return the corresponding ORed cpp flags."""
         flags_dict = {"deprecated": "vtable::common_::deprecated",
diff --git a/tools/sdbusplus/templates/interface.server.cpp.mako b/tools/sdbusplus/templates/interface.server.cpp.mako
index 6a12968..ddb7537 100644
--- a/tools/sdbusplus/templates/interface.server.cpp.mako
+++ b/tools/sdbusplus/templates/interface.server.cpp.mako
@@ -2,6 +2,7 @@
 #include <map>
 #include <sdbusplus/exception.hpp>
 #include <sdbusplus/sdbus.hpp>
+#include <sdbusplus/sdbuspp_support/server.hpp>
 #include <sdbusplus/server.hpp>
 #include <string>
 #include <tuple>
diff --git a/tools/sdbusplus/templates/method.prototype.hpp.mako b/tools/sdbusplus/templates/method.prototype.hpp.mako
index 17c2019..640e74f 100644
--- a/tools/sdbusplus/templates/method.prototype.hpp.mako
+++ b/tools/sdbusplus/templates/method.prototype.hpp.mako
@@ -1,4 +1,6 @@
 <%
+    def parameters_as_arg_list():
+        return ", ".join([ parameter(p, ref="&&") for p in method.parameters ])
 
     def parameters_as_list(transform=lambda p: p.camelCase):
         return ", ".join([ transform(p) for p in method.parameters ])
@@ -7,10 +9,17 @@
         return ", ".join([ p.cppTypeParam(interface.name, full=True)
                 for p in method.parameters ])
 
-    def returns_as_tuple_index(tuple, pre="", post=""):
-        return ", ".join([ "%sstd::move(std::get<%d>(%s))%s" %\
-                (pre,i,tuple,post) \
-                for i in range(len(method.returns))])
+    def parameter(p, defaultValue=False, ref=""):
+        r = "%s%s %s" % (p.cppTypeParam(interface.name), ref, p.camelCase)
+        if defaultValue:
+            r += default_value(p)
+        return r
+
+    def default_value(p):
+        if p.defaultValue != None:
+            return " = " + str(p.defaultValue)
+        else:
+            return ""
 
     def interface_name():
         return interface.name.split('.').pop()
@@ -82,54 +91,34 @@
 int ${interface_name()}::_callback_${ method.CamelCase }(
         sd_bus_message* msg, void* context, sd_bus_error* error)
 {
+    auto o = static_cast<${interface_name()}*>(context);
+
+    % if method.errors:
     try
-    {
-        ### Need to add a ref to msg since we attached it to an
-        ### sdbusplus::message.
-        auto m = message::message(msg);
-        {
-            auto tbus = m.get_bus();
-            sdbusplus::server::transaction::Transaction t(tbus, m);
-            sdbusplus::server::transaction::set_id
-                (std::hash<sdbusplus::server::transaction::Transaction>{}(t));
-        }
-
-    % if len(method.parameters) != 0:
-        ${method.parameters_as_local(interface)}{};
-
-        m.read(${parameters_as_list()});
     % endif
-
-        auto o = static_cast<${interface_name()}*>(context);
-    % if len(method.returns) != 0:
-        auto r = \
-    %endif
-        o->${ method.camelCase }(${parameters_as_list()});
-
-        auto reply = m.new_method_return();
-    % if len(method.returns) == 0:
-        // No data to append on reply.
-    % elif len(method.returns) == 1:
-        reply.append(std::move(r));
-    % else:
-        reply.append(\
-${returns_as_tuple_index("r")});
-    % endif
-
-        reply.method_return();
-    }
-    catch(sdbusplus::internal_exception_t& e)
     {
-        return sd_bus_error_set(error, e.name(), e.description());
+        return sdbusplus::sdbuspp::method_callback\
+    % if len(method.returns) > 1:
+<true>\
+    % endif
+(
+                msg, o->_intf, error,
+                std::function(
+                    [=](${parameters_as_arg_list()})
+                    {
+                        return o->${ method.camelCase }(
+                                ${parameters_as_list()});
+                    }
+                ));
     }
     % for e in method.errors:
     catch(sdbusplus::${error_namespace(e)}::${error_name(e)}& e)
     {
-        return sd_bus_error_set(error, e.name(), e.description());
+        return o->_intf->sd_bus_error_set(error, e.name(), e.description());
     }
     % endfor
 
-    return true;
+    return 0;
 }
 
 namespace details
diff --git a/tools/sdbusplus/templates/property.prototype.hpp.mako b/tools/sdbusplus/templates/property.prototype.hpp.mako
index 425dd63..e99eb7d 100644
--- a/tools/sdbusplus/templates/property.prototype.hpp.mako
+++ b/tools/sdbusplus/templates/property.prototype.hpp.mako
@@ -34,21 +34,18 @@
 {
     auto o = static_cast<${classname}*>(context);
 
+    % if property.errors:
     try
+    % endif
     {
-        auto m = message::message(reply, o->_intf);
-        {
-            auto tbus = m.get_bus();
-            sdbusplus::server::transaction::Transaction t(tbus, m);
-            sdbusplus::server::transaction::set_id
-                (std::hash<sdbusplus::server::transaction::Transaction>{}(t));
-        }
-
-        m.append(o->${property.camelCase}());
-    }
-    catch(sdbusplus::internal_exception_t& e)
-    {
-        return o->_intf->sd_bus_error_set(error, e.name(), e.description());
+        return sdbusplus::sdbuspp::property_callback(
+                reply, o->_intf, error,
+                std::function(
+                    [=]()
+                    {
+                        return o->${property.camelCase}();
+                    }
+                ));
     }
     % for e in property.errors:
     catch(sdbusplus::${error_namespace(e)}::${error_name(e)}& e)
@@ -56,8 +53,6 @@
         return o->_intf->sd_bus_error_set(error, e.name(), e.description());
     }
     % endfor
-
-    return true;
 }
 
 auto ${classname}::${property.camelCase}(${property.cppTypeParam(interface.name)} value,
@@ -90,23 +85,18 @@
 {
     auto o = static_cast<${classname}*>(context);
 
+    % if property.errors:
     try
+    % endif
     {
-        auto m = message::message(value, o->_intf);
-        {
-            auto tbus = m.get_bus();
-            sdbusplus::server::transaction::Transaction t(tbus, m);
-            sdbusplus::server::transaction::set_id
-                (std::hash<sdbusplus::server::transaction::Transaction>{}(t));
-        }
-
-        ${property.cppTypeParam(interface.name)} v{};
-        m.read(v);
-        o->${property.camelCase}(v);
-    }
-    catch(sdbusplus::internal_exception_t& e)
-    {
-        return o->_intf->sd_bus_error_set(error, e.name(), e.description());
+        return sdbusplus::sdbuspp::property_callback(
+                value, o->_intf, error,
+                std::function(
+                    [=](${property.cppTypeParam(interface.name)}&& arg)
+                    {
+                        o->${property.camelCase}(std::move(arg));
+                    }
+                ));
     }
     % for e in property.errors:
     catch(sdbusplus::${error_namespace(e)}::${error_name(e)}& e)