Add OemRouter facility.

OemRouter adds a facility to register OEM Group Message handlers,
then dispatch matching messages to the registered handler.
Added as a core source so that any dynamic provider can register its
messages without requiring any specific load order.

Includes code fixes for x86 portability.

Change-Id: I47b8fe7873e3c7fdf35a00d3c8a7e17d30c398c4
Signed-off-by: Peter Hanson <peterh@google.com>
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/oemrouter.cpp b/oemrouter.cpp
new file mode 100644
index 0000000..273a679
--- /dev/null
+++ b/oemrouter.cpp
@@ -0,0 +1,149 @@
+#include <cstdio>
+#include <cstring>
+#include <map>
+#include <utility>
+
+#include "host-ipmid/oemrouter.hpp"
+
+namespace oem
+{
+
+using Key = std::pair<Number, ipmi_cmd_t>;
+
+// Private implementation of OemRouter Interface.
+class RouterImpl : public Router
+{
+    public:
+        RouterImpl() {}
+
+        // Implement OemRouter Interface.
+        void activate() override;
+        void registerHandler(Number oen, ipmi_cmd_t cmd,
+                             Handler handler) override;
+
+        // Actual message routing function.
+        ipmi_ret_t routeMsg(ipmi_cmd_t cmd, const uint8_t* reqBuf,
+                            uint8_t* replyBuf, size_t* dataLen);
+
+    private:
+        std::map<Key, Handler> handlers;
+};
+
+// Static global instance for simplicity.
+static RouterImpl* globalRouterImpl;
+
+// TODO Refactor ipmid to avoid need for singleton here.
+Router* mutableRouter()
+{
+    if (!globalRouterImpl)
+    {
+        globalRouterImpl = new RouterImpl;
+    }
+    return globalRouterImpl;
+}
+
+ipmi_ret_t RouterImpl::routeMsg(ipmi_cmd_t cmd, const uint8_t* reqBuf,
+                                uint8_t* replyBuf, size_t* dataLen)
+{
+    // Not entirely clear we can route reply without complete OEM group.
+    // TODO: consider adding a way to suppress malformed replies.
+    if (*dataLen < groupMagicSize)
+    {
+        fprintf(stderr, "NetFn:[0x2E], OEM:[%zu bytes?], Cmd:[%#04X]\n",
+                *dataLen, cmd);
+        (*dataLen) = 0;
+        return IPMI_CC_REQ_DATA_LEN_INVALID;
+    }
+
+    // Find registered handler or reject request.
+    auto number = toOemNumber(reqBuf);
+    auto cmdKey = std::make_pair(number, cmd);
+
+    auto iter = handlers.find(cmdKey);
+    if (iter == handlers.end())
+    {
+        auto wildKey = std::make_pair(number, IPMI_CMD_WILDCARD);
+        iter = handlers.find(wildKey);
+        if (iter == handlers.end())
+        {
+            fprintf(stderr, "No Registered handler for NetFn:[0x2E], "
+                    "OEM:[%#08X], Cmd:[%#04X]\n", number, cmd);
+            *dataLen = groupMagicSize;
+            return IPMI_CC_INVALID;
+        }
+#ifdef __IPMI_DEBUG__
+        fprintf(stderr, "Wildcard NetFn:[0x2E], OEM:[%#08X], Cmd:[%#04X]\n",
+                number, cmd);
+#endif
+    }
+    else
+    {
+#ifdef __IPMI_DEBUG__
+        fprintf(stderr, "Match NetFn:[0x2E], OEM:[%#08X], Cmd:[%#04X]\n",
+                number, cmd);
+#endif
+    }
+
+    // Copy OEMGroup here, by analogy to IPMI CC code at netfn router;
+    // OemHandler should deal only with optional following data bytes.
+    std::memcpy(replyBuf, reqBuf, groupMagicSize);
+
+    size_t oemDataLen = *dataLen - groupMagicSize;
+    Handler& handler = iter->second;
+
+    auto rc = handler(cmd, reqBuf + groupMagicSize,
+                      replyBuf + groupMagicSize, &oemDataLen);
+
+    // Add OEMGroup bytes to nominal reply.
+    *dataLen = oemDataLen + groupMagicSize;
+    return rc;
+}
+
+// Function suitable for use as ipmi_netfn_router() call-back.
+// Translates call-back pointer args to more specific types.
+ipmi_ret_t ipmi_oem_wildcard_handler(ipmi_netfn_t /* netfn */,
+                                     ipmi_cmd_t cmd, ipmi_request_t request,
+                                     ipmi_response_t response,
+                                     ipmi_data_len_t dataLen,
+                                     ipmi_context_t context)
+{
+    // View requests & responses as byte sequences.
+    const uint8_t* reqBuf = static_cast<uint8_t*>(request);
+    uint8_t* replyBuf = static_cast<uint8_t*>(response);
+
+    // View context as router object, defaulting nullptr to global object.
+    auto router = static_cast<RouterImpl*>(
+            context ? context : mutableRouter());
+
+    // Send message parameters to dispatcher.
+    return router->routeMsg(cmd, reqBuf, replyBuf, dataLen);
+}
+
+// Enable message routing to begin.
+void RouterImpl::activate()
+{
+    // Register netfn 0x2e OEM Group, any (wildcard) command.
+    printf("Registering NetFn:[0x%X], Cmd:[0x%X]\n",
+           NETFUN_OEM_GROUP, IPMI_CMD_WILDCARD);
+    ipmi_register_callback(NETFUN_OEM_GROUP, IPMI_CMD_WILDCARD, this,
+                           ipmi_oem_wildcard_handler, PRIVILEGE_OEM);
+}
+
+void RouterImpl::registerHandler(Number oen, ipmi_cmd_t cmd,
+                                 Handler handler)
+{
+    auto cmdKey = std::make_pair(oen, cmd);
+    auto iter = handlers.find(cmdKey);
+    if (iter == handlers.end())
+    {
+        // Add handler if key not already taken.
+        handlers.emplace(cmdKey, handler);
+    }
+    else
+    {
+        fprintf(stderr, "ERROR : Duplicate registration for NetFn:[0x2E], "
+                "OEM:[%#08X], Cmd:[%#04X]\n", oen, cmd);
+    }
+}
+
+}  // namespace oem