ipmid: Rewrite ipmid to use the new architecture

New architecture highlights:
* The new registration detects handler type for argument unpacking.
* Upon completion the response is automatically packed.
* Handlers can make use of the new async/yield sdbusplus mechanism.
* The queue exports a new dbus interface for method-based IPMI calls.
* The legacy handler registration is still supported for now.
* The legacy dbus interface is still supported for now.

Change-Id: Iae8342d9771ccebd3a0834e35597c14be4cc39cf
Signed-off-by: Vernon Mauery <vernon.mauery@linux.intel.com>
diff --git a/Makefile.am b/Makefile.am
index 890b9f6..ce96af7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -10,11 +10,11 @@
 	ipmid
 
 ipmid_SOURCES = \
-	ipmid.cpp \
+	ipmid-new.cpp \
 	settings.cpp \
 	host-cmd-manager.cpp \
-	utils.cpp \
-	oemrouter.cpp
+	utils.cpp
+
 nodist_ipmid_SOURCES = ipmiwhitelist.cpp
 
 libipmi20_BUILT_LIST = \
@@ -46,6 +46,7 @@
 ipmid_CXXFLAGS = $(COMMON_CXX)
 ipmid_LDADD = \
 	libipmid/libipmid.la \
+	user_channel/libchannellayer.la \
 	libipmid-host/libipmid-host.la
 ipmid_LDFLAGS = \
 	$(SYSTEMD_LIBS) \
@@ -54,8 +55,8 @@
 	$(PHOSPHOR_LOGGING_LIBS) \
 	$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
 	$(CRYPTO_LIBS) \
+	-lboost_coroutine \
 	-lstdc++fs \
-	-pthread \
 	-export-dynamic
 
 # TODO: Rather than use -export-dynamic, we should use -export-symbol to have a
@@ -116,6 +117,7 @@
 	$(PHOSPHOR_LOGGING_LIBS) \
 	$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
 	-lstdc++fs \
+	-lboost_coroutine \
 	-version-info 0:0:0 -shared
 libipmi20_la_CXXFLAGS = $(COMMON_CXX)
 
diff --git a/include/ipmid-host/cmd.hpp b/include/ipmid-host/cmd.hpp
index adea960..e46c485 100644
--- a/include/ipmid-host/cmd.hpp
+++ b/include/ipmid-host/cmd.hpp
@@ -2,9 +2,5 @@
 #include <memory>
 #include <sdbusplus/bus.hpp>
 
-// Need this to use new sdbusplus compatible interfaces
-using sdbusPtr = std::unique_ptr<sdbusplus::bus::bus>;
-extern sdbusPtr& ipmid_get_sdbus_plus_handler();
-
 // Global Host Bound Command manager
 extern void ipmid_send_cmd_to_host(phosphor::host::command::CommandHandler&&);
diff --git a/ipmid-new.cpp b/ipmid-new.cpp
new file mode 100644
index 0000000..fb57666
--- /dev/null
+++ b/ipmid-new.cpp
@@ -0,0 +1,459 @@
+/**
+ * Copyright © 2018 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#include "settings.hpp"
+
+#include <dlfcn.h>
+
+#include <algorithm>
+#include <any>
+#include <exception>
+#include <forward_list>
+#include <host-cmd-manager.hpp>
+#include <ipmid-host/cmd.hpp>
+#include <ipmid/api.hpp>
+#include <ipmid/handler.hpp>
+#include <ipmid/message.hpp>
+#include <ipmid/oemrouter.hpp>
+#include <ipmid/registration.hpp>
+#include <map>
+#include <memory>
+#include <optional>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/asio/sd_event.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/timer.hpp>
+#include <tuple>
+#include <types.hpp>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#if __has_include(<filesystem>)
+#include <filesystem>
+#elif __has_include(<experimental/filesystem>)
+#include <experimental/filesystem>
+namespace std
+{
+// splice experimental::filesystem into std
+namespace filesystem = std::experimental::filesystem;
+} // namespace std
+#else
+#error filesystem not available
+#endif
+
+namespace fs = std::filesystem;
+
+using namespace phosphor::logging;
+
+// Global timer for network changes
+std::unique_ptr<phosphor::Timer> networkTimer = nullptr;
+
+// IPMI Spec, shared Reservation ID.
+static unsigned short selReservationID = 0xFFFF;
+static bool selReservationValid = false;
+
+unsigned short reserveSel(void)
+{
+    // IPMI spec, Reservation ID, the value simply increases against each
+    // execution of the Reserve SEL command.
+    if (++selReservationID == 0)
+    {
+        selReservationID = 1;
+    }
+    selReservationValid = true;
+    return selReservationID;
+}
+
+bool checkSELReservation(unsigned short id)
+{
+    return (selReservationValid && selReservationID == id);
+}
+
+void cancelSELReservation(void)
+{
+    selReservationValid = false;
+}
+
+EInterfaceIndex getInterfaceIndex(void)
+{
+    return interfaceKCS;
+}
+
+sd_bus* bus;
+sd_event* events = nullptr;
+sd_event* ipmid_get_sd_event_connection(void)
+{
+    return events;
+}
+sd_bus* ipmid_get_sd_bus_connection(void)
+{
+    return bus;
+}
+
+namespace ipmi
+{
+
+static inline unsigned int makeCmdKey(unsigned int cluster, unsigned int cmd)
+{
+    return (cluster << 8) | cmd;
+}
+
+using HandlerTuple = std::tuple<int,                        /* prio */
+                                Privilege, HandlerBase::ptr /* handler */
+                                >;
+
+/* map to handle standard registered commands */
+static std::unordered_map<unsigned int, /* key is NetFn/Cmd */
+                          HandlerTuple>
+    handlerMap;
+
+namespace impl
+{
+/* common function to register all standard IPMI handlers */
+bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv,
+                     HandlerBase::ptr handler)
+{
+    // check for valid NetFn: even; 00-0Ch, 30-3Eh
+    if (netFn & 1 || (netFn > netFnTransport && netFn < netFnGroup) ||
+        netFn > netFnOemEight)
+    {
+        return false;
+    }
+
+    // create key and value for this handler
+    unsigned int netFnCmd = makeCmdKey(netFn, cmd);
+    HandlerTuple item(prio, priv, handler);
+
+    // consult the handler map and look for a match
+    auto& mapCmd = handlerMap[netFnCmd];
+    if (!std::get<HandlerBase::ptr>(mapCmd) || std::get<int>(mapCmd) <= prio)
+    {
+        mapCmd = item;
+        return true;
+    }
+    return false;
+}
+
+} // namespace impl
+
+message::Response::ptr executeIpmiCommandCommon(
+    std::unordered_map<unsigned int, HandlerTuple>& handlers,
+    unsigned int keyCommon, message::Request::ptr request)
+{
+    Cmd cmd = request->ctx->cmd;
+    unsigned int key = makeCmdKey(keyCommon, cmd);
+    auto cmdIter = handlers.find(key);
+    if (cmdIter != handlers.end())
+    {
+        HandlerTuple& chosen = cmdIter->second;
+        if (request->ctx->priv < std::get<Privilege>(chosen))
+        {
+            return errorResponse(request, ccInsufficientPrivilege);
+        }
+        return std::get<HandlerBase::ptr>(chosen)->call(request);
+    }
+    else
+    {
+        unsigned int wildcard = makeCmdKey(keyCommon, cmdWildcard);
+        cmdIter = handlers.find(wildcard);
+        if (cmdIter != handlers.end())
+        {
+            HandlerTuple& chosen = cmdIter->second;
+            if (request->ctx->priv < std::get<Privilege>(chosen))
+            {
+                return errorResponse(request, ccInsufficientPrivilege);
+            }
+            return std::get<HandlerBase::ptr>(chosen)->call(request);
+        }
+    }
+    return errorResponse(request, ccInvalidCommand);
+}
+
+message::Response::ptr executeIpmiCommand(message::Request::ptr request)
+{
+    NetFn netFn = request->ctx->netFn;
+    // TODO: handler OEM and group OEM commands here
+    return executeIpmiCommandCommon(handlerMap, netFn, request);
+}
+
+/* called from sdbus async server context */
+auto executionEntry(boost::asio::yield_context yield, NetFn netFn, uint8_t lun,
+                    Cmd cmd, std::vector<uint8_t>& data,
+                    std::map<std::string, ipmi::Value>& options)
+{
+    auto ctx = std::make_shared<ipmi::Context>(netFn, cmd, 0, 0,
+                                               ipmi::Privilege::Admin, &yield);
+    auto request = std::make_shared<ipmi::message::Request>(
+        ctx, std::forward<std::vector<uint8_t>>(data));
+    message::Response::ptr response = executeIpmiCommand(request);
+
+    // Responses in IPMI require a bit set.  So there ya go...
+    netFn |= 0x01;
+    return std::make_tuple(netFn, lun, cmd, response->cc,
+                           response->payload.raw);
+}
+
+/** @struct IpmiProvider
+ *
+ *  RAII wrapper for dlopen so that dlclose gets called on exit
+ */
+struct IpmiProvider
+{
+  public:
+    /** @brief address of the opened library */
+    void* addr;
+    std::string name;
+
+    IpmiProvider() = delete;
+    IpmiProvider(const IpmiProvider&) = delete;
+    IpmiProvider& operator=(const IpmiProvider&) = delete;
+    IpmiProvider(IpmiProvider&&) = delete;
+    IpmiProvider& operator=(IpmiProvider&&) = delete;
+
+    /** @brief dlopen a shared object file by path
+     *  @param[in]  filename - path of shared object to open
+     */
+    explicit IpmiProvider(const char* fname) : addr(nullptr), name(fname)
+    {
+        log<level::DEBUG>("Open IPMI provider library",
+                          entry("PROVIDER=%s", name.c_str()));
+        try
+        {
+            addr = dlopen(name.c_str(), RTLD_NOW);
+        }
+        catch (std::exception& e)
+        {
+            log<level::ERR>("ERROR opening IPMI provider",
+                            entry("PROVIDER=%s", name.c_str()),
+                            entry("ERROR=%s", e.what()));
+        }
+        catch (...)
+        {
+            std::exception_ptr eptr = std::current_exception();
+            try
+            {
+                std::rethrow_exception(eptr);
+            }
+            catch (std::exception& e)
+            {
+                log<level::ERR>("ERROR opening IPMI provider",
+                                entry("PROVIDER=%s", name.c_str()),
+                                entry("ERROR=%s", e.what()));
+            }
+        }
+        if (!isOpen())
+        {
+            log<level::ERR>("ERROR opening IPMI provider",
+                            entry("PROVIDER=%s", name.c_str()),
+                            entry("ERROR=%s", dlerror()));
+        }
+    }
+
+    ~IpmiProvider()
+    {
+        if (isOpen())
+        {
+            dlclose(addr);
+        }
+    }
+    bool isOpen() const
+    {
+        return (nullptr != addr);
+    }
+};
+
+// Plugin libraries need to contain .so either at the end or in the middle
+constexpr const char ipmiPluginExtn[] = ".so";
+
+/* return a list of self-closing library handles */
+std::forward_list<IpmiProvider> loadProviders(const fs::path& ipmiLibsPath)
+{
+    std::vector<fs::path> libs;
+    for (const auto& libPath : fs::directory_iterator(ipmiLibsPath))
+    {
+        fs::path fname = libPath.path();
+        while (fname.has_extension())
+        {
+            fs::path extn = fname.extension();
+            if (extn == ipmiPluginExtn)
+            {
+                libs.push_back(libPath.path());
+                break;
+            }
+            fname.replace_extension();
+        }
+    }
+    std::sort(libs.begin(), libs.end());
+
+    std::forward_list<IpmiProvider> handles;
+    for (auto& lib : libs)
+    {
+#ifdef __IPMI_DEBUG__
+        log<level::DEBUG>("Registering handler",
+                          entry("HANDLER=%s", lib.c_str()));
+#endif
+        handles.emplace_front(lib.c_str());
+    }
+    return handles;
+}
+
+} // namespace ipmi
+
+static std::shared_ptr<boost::asio::io_service> io;
+std::shared_ptr<boost::asio::io_service> getIoService()
+{
+    return io;
+}
+
+static std::shared_ptr<sdbusplus::asio::connection> sdbusp;
+std::shared_ptr<sdbusplus::asio::connection> getSdBus()
+{
+    return sdbusp;
+}
+
+#ifdef ALLOW_DEPRECATED_API
+/* legacy registration */
+void ipmi_register_callback(ipmi_netfn_t netFn, ipmi_cmd_t cmd,
+                            ipmi_context_t context, ipmid_callback_t handler,
+                            ipmi_cmd_privilege_t priv)
+{
+    auto h = ipmi::makeLegacyHandler(handler);
+    // translate priv from deprecated enum to current
+    ipmi::Privilege realPriv;
+    switch (priv)
+    {
+        case PRIVILEGE_CALLBACK:
+            realPriv = ipmi::Privilege::Callback;
+            break;
+        case PRIVILEGE_USER:
+            realPriv = ipmi::Privilege::User;
+            break;
+        case PRIVILEGE_OPERATOR:
+            realPriv = ipmi::Privilege::Operator;
+            break;
+        case PRIVILEGE_ADMIN:
+            realPriv = ipmi::Privilege::Admin;
+            break;
+        case PRIVILEGE_OEM:
+            realPriv = ipmi::Privilege::Oem;
+            break;
+        case SYSTEM_INTERFACE:
+            realPriv = ipmi::Privilege::Admin;
+            break;
+        default:
+            realPriv = ipmi::Privilege::Admin;
+            break;
+    }
+    ipmi::impl::registerHandler(ipmi::prioOpenBmcBase, netFn, cmd, realPriv, h);
+}
+
+/* legacy alternative to executionEntry */
+void handleLegacyIpmiCommand(sdbusplus::message::message& m)
+{
+    unsigned char seq, netFn, lun, cmd;
+    std::vector<uint8_t> data;
+
+    m.read(seq, netFn, lun, cmd, data);
+
+    auto ctx = std::make_shared<ipmi::Context>(netFn, cmd, 0, 0,
+                                               ipmi::Privilege::Admin);
+    auto request = std::make_shared<ipmi::message::Request>(
+        ctx, std::forward<std::vector<uint8_t>>(data));
+    ipmi::message::Response::ptr response = ipmi::executeIpmiCommand(request);
+
+    // Responses in IPMI require a bit set.  So there ya go...
+    netFn |= 0x01;
+
+    const char *dest, *path;
+    constexpr const char* DBUS_INTF = "org.openbmc.HostIpmi";
+
+    dest = m.get_sender();
+    path = m.get_path();
+    sdbusp->async_method_call([](boost::system::error_code ec) {}, dest, path,
+                              DBUS_INTF, "sendMessage", seq, netFn, lun, cmd,
+                              response->cc, response->payload.raw);
+}
+
+#endif /* ALLOW_DEPRECATED_API */
+
+// Calls host command manager to do the right thing for the command
+using CommandHandler = phosphor::host::command::CommandHandler;
+std::unique_ptr<phosphor::host::command::Manager> cmdManager;
+void ipmid_send_cmd_to_host(CommandHandler&& cmd)
+{
+    return cmdManager->execute(std::move(cmd));
+}
+
+std::unique_ptr<phosphor::host::command::Manager>& ipmid_get_host_cmd_manager()
+{
+    return cmdManager;
+}
+
+int main(int argc, char* argv[])
+{
+    // Connect to system bus
+    io = std::make_shared<boost::asio::io_service>();
+    if (argc > 1 && std::string(argv[1]) == "-session")
+    {
+        sd_bus_default_user(&bus);
+    }
+    else
+    {
+        sd_bus_default_system(&bus);
+    }
+    sdbusp = std::make_shared<sdbusplus::asio::connection>(*io, bus);
+    sdbusp->request_name("xyz.openbmc_project.Ipmi.Host");
+
+    // TODO: Hack to keep the sdEvents running.... Not sure why the sd_event
+    //       queue stops running if we don't have a timer that keeps re-arming
+    phosphor::Timer t2([]() { ; });
+    t2.start(std::chrono::microseconds(500000), true);
+
+    // TODO: Remove all vestiges of sd_event from phosphor-host-ipmid
+    //       until that is done, add the sd_event wrapper to the io object
+    sdbusplus::asio::sd_event_wrapper sdEvents(*io);
+
+    cmdManager = std::make_unique<phosphor::host::command::Manager>(*sdbusp);
+
+    // Register all command providers and filters
+    auto handles = ipmi::loadProviders(HOST_IPMI_LIB_PATH);
+
+    // Add bindings for inbound IPMI requests
+    auto server = sdbusplus::asio::object_server(sdbusp);
+    auto iface = server.add_interface("/xyz/openbmc_project/Ipmi",
+                                      "xyz.openbmc_project.Ipmi.Server");
+    iface->register_method("execute", ipmi::executionEntry);
+    iface->initialize();
+
+#ifdef ALLOW_DEPRECATED_API
+    // listen on deprecated signal interface for kcs/bt commands
+    constexpr const char* FILTER = "type='signal',interface='org.openbmc."
+                                   "HostIpmi',member='ReceivedMessage'";
+    sdbusplus::bus::match::match oldIpmiInterface(*sdbusp, FILTER,
+                                                  handleLegacyIpmiCommand);
+#endif /* ALLOW_DEPRECATED_API */
+
+    io->run();
+
+    // This avoids a warning about unused variables
+    handles.clear();
+    return 0;
+}
diff --git a/sensorhandler.cpp b/sensorhandler.cpp
index 9d0364d..051c4fd 100644
--- a/sensorhandler.cpp
+++ b/sensorhandler.cpp
@@ -139,11 +139,6 @@
     sd_bus_error error = SD_BUS_ERROR_NULL;
     sd_bus_message* m = NULL;
 
-    std::fprintf(ipmidbus,
-                 "Attempting to set a dbus Variant Sensor 0x%02x via %s with a "
-                 "value of %s\n",
-                 number, method, value);
-
     r = find_openbmc_path(number, &a);
 
     if (r < 0)
@@ -190,11 +185,6 @@
     sd_bus_error error = SD_BUS_ERROR_NULL;
     sd_bus_message* m = NULL;
 
-    std::fprintf(ipmidbus,
-                 "Attempting to set a dbus Variant Sensor 0x%02x via %s with a "
-                 "value of 0x%02x\n",
-                 number, method, value);
-
     r = find_openbmc_path(number, &a);
 
     if (r < 0)
diff --git a/systemintfcmds.cpp b/systemintfcmds.cpp
index d044030..564cc38 100644
--- a/systemintfcmds.cpp
+++ b/systemintfcmds.cpp
@@ -5,10 +5,10 @@
 #include "host-cmd-manager.hpp"
 #include "host-interface.hpp"
 
-#include <ipmid/api.h>
-
 #include <cstring>
 #include <ipmid-host/cmd.hpp>
+#include <ipmid/api.hpp>
+#include <ipmid/registration.hpp>
 
 void register_netfn_app_functions() __attribute__((constructor));
 
@@ -109,6 +109,8 @@
     __attribute__((init_priority(101)));
 std::unique_ptr<sdbusplus::server::manager::manager> objManager
     __attribute__((init_priority(101)));
+std::unique_ptr<sdbusplus::asio::connection> sdbusp
+    __attribute__((init_priority(101)));
 } // namespace
 
 #include <unistd.h>
@@ -130,14 +132,23 @@
     // Create new xyz.openbmc_project.host object on the bus
     auto objPath = std::string{CONTROL_HOST_OBJ_MGR} + '/' + HOST_NAME + '0';
 
-    // Add sdbusplus ObjectManager.
-    auto& sdbusPlusHandler = ipmid_get_sdbus_plus_handler();
-    objManager = std::make_unique<sdbusplus::server::manager::manager>(
-        *sdbusPlusHandler, CONTROL_HOST_OBJ_MGR);
+    // Create a new sdbus connection so it can have a well-known name
+    sd_bus* bus = nullptr;
+    sd_bus_open_system(&bus);
+    if (!bus)
+    {
+        return;
+    }
+    auto io = getIoService();
+    sdbusp = std::make_unique<sdbusplus::asio::connection>(*io, bus);
 
-    host = std::make_unique<phosphor::host::command::Host>(*sdbusPlusHandler,
+    // Add sdbusplus ObjectManager.
+    objManager = std::make_unique<sdbusplus::server::manager::manager>(
+        *sdbusp, CONTROL_HOST_OBJ_MGR);
+
+    host = std::make_unique<phosphor::host::command::Host>(*sdbusp,
                                                            objPath.c_str());
-    sdbusPlusHandler->request_name(CONTROL_HOST_BUSNAME);
+    sdbusp->request_name(CONTROL_HOST_BUSNAME);
 
     return;
 }