ipmi: add zone controller interface

Implementations of this interface can make the library mode testable and
work in environments other than dbus.

The main entry point to the IPMI handler now expects to receive a
handler pointer.  The handler itself is implemented to process IPMI
requests and responses in the expected way.  The default object receives
a dbus implementation for the zone controls.

Signed-off-by: Patrick Venture <venture@google.com>
Change-Id: I6d7be871bdc5cce3c3cc53c16d80b501aaaafc7d
diff --git a/ipmi/control.hpp b/ipmi/control.hpp
new file mode 100644
index 0000000..1cd1612
--- /dev/null
+++ b/ipmi/control.hpp
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <cstdint>
+#include <string>
+
+namespace pid_control
+{
+namespace ipmi
+{
+
+// Implement this interface to control a zone's mode or read back its status.
+class ZoneControlInterface
+{
+  public:
+    // Reads the fan control property (either manual or failsafe) and returns an
+    // IPMI code based on success or failure of this.
+    virtual uint8_t getFanCtrlProperty(uint8_t zoneId, bool* value,
+                                       const std::string& property) = 0;
+
+    // Sets the fan control property (only manual mode is settable presently)
+    // and returns an IPMI code based on success or failure of this.
+    virtual uint8_t setFanCtrlProperty(uint8_t zoneId, bool value,
+                                       const std::string& property) = 0;
+};
+
+} // namespace ipmi
+} // namespace pid_control
diff --git a/ipmi/dbus_mode.cpp b/ipmi/dbus_mode.cpp
index 76b4eba..7ea7fd4 100644
--- a/ipmi/dbus_mode.cpp
+++ b/ipmi/dbus_mode.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "dbus_mode.hpp"
+
 #include <ipmid/api.h>
 
 #include <sdbusplus/bus.hpp>
@@ -21,6 +23,7 @@
 
 #include <cstdint>
 #include <string>
+#include <variant>
 
 namespace pid_control
 {
@@ -44,8 +47,8 @@
     return std::string(objectPath) + std::to_string(zone);
 }
 
-uint8_t getFanCtrlProperty(uint8_t zoneId, bool* value,
-                           const std::string& property)
+uint8_t DbusZoneControl::getFanCtrlProperty(uint8_t zoneId, bool* value,
+                                            const std::string& property)
 {
     std::string path = getControlPath(zoneId);
 
@@ -73,5 +76,33 @@
     return IPMI_CC_OK;
 }
 
+uint8_t DbusZoneControl::setFanCtrlProperty(uint8_t zoneId, bool value,
+                                            const std::string& property)
+{
+    using Value = std::variant<bool>;
+    Value v{value};
+
+    std::string path = getControlPath(zoneId);
+
+    auto PropertyWriteBus = sdbusplus::bus::new_system();
+    auto pimMsg = PropertyWriteBus.new_method_call(busName, path.c_str(),
+                                                   propertiesintf, "Set");
+    pimMsg.append(intf);
+    pimMsg.append(property);
+    pimMsg.append(v);
+
+    try
+    {
+        PropertyWriteBus.call_noreply(pimMsg);
+    }
+    catch (const sdbusplus::exception::SdBusError& ex)
+    {
+        return IPMI_CC_INVALID;
+    }
+
+    /* TODO(venture): Should sanity check the result. */
+    return IPMI_CC_OK;
+}
+
 } // namespace ipmi
 } // namespace pid_control
diff --git a/ipmi/dbus_mode.hpp b/ipmi/dbus_mode.hpp
index 05e9734..b063ac4 100644
--- a/ipmi/dbus_mode.hpp
+++ b/ipmi/dbus_mode.hpp
@@ -1,5 +1,7 @@
 #pragma once
 
+#include "control.hpp"
+
 #include <cstdint>
 #include <string>
 
@@ -8,21 +10,31 @@
 namespace ipmi
 {
 
-/*
- * busctl call xyz.openbmc_project.State.FanCtrl \
- *     /xyz/openbmc_project/settings/fanctrl/zone1 \
- *     org.freedesktop.DBus.Properties \
- *     GetAll \
- *     s \
- *     xyz.openbmc_project.Control.Mode
- * a{sv} 2 "Manual" b false "FailSafe" b false
- *
- * This returns an IPMI code as a uint8_t (which will always be sufficient to
- * hold the result). NOTE: This does not return the typedef value to avoid
- * including a header with conflicting types.
- */
-uint8_t getFanCtrlProperty(uint8_t zoneId, bool* value,
-                           const std::string& property);
+class DbusZoneControl : public ZoneControlInterface
+{
+  public:
+    DbusZoneControl() = default;
+    ~DbusZoneControl() = default;
+
+    /*
+     * busctl call xyz.openbmc_project.State.FanCtrl \
+     *     /xyz/openbmc_project/settings/fanctrl/zone1 \
+     *     org.freedesktop.DBus.Properties \
+     *     GetAll \
+     *     s \
+     *     xyz.openbmc_project.Control.Mode
+     * a{sv} 2 "Manual" b false "FailSafe" b false
+     *
+     * This returns an IPMI code as a uint8_t (which will always be sufficient
+     * to hold the result). NOTE: This does not return the typedef value to
+     * avoid including a header with conflicting types.
+     */
+    uint8_t getFanCtrlProperty(uint8_t zoneId, bool* value,
+                               const std::string& property) override;
+
+    uint8_t setFanCtrlProperty(uint8_t zoneId, bool value,
+                               const std::string& property) override;
+};
 
 } // namespace ipmi
 } // namespace pid_control
diff --git a/ipmi/main_ipmi.cpp b/ipmi/main_ipmi.cpp
index 6056464..8e58b25 100644
--- a/ipmi/main_ipmi.cpp
+++ b/ipmi/main_ipmi.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "control.hpp"
+#include "dbus_mode.hpp"
 #include "manualcmds.hpp"
 
 #include <ipmid/iana.hpp>
@@ -21,6 +23,18 @@
 #include <ipmid/oemrouter.hpp>
 
 #include <cstdio>
+#include <functional>
+#include <memory>
+
+namespace pid_control
+{
+namespace ipmi
+{
+
+ZoneControlIpmiHandler handler(std::make_unique<DbusZoneControl>());
+
+}
+} // namespace pid_control
 
 void setupGlobalOemFanControl() __attribute__((constructor));
 
@@ -33,6 +47,9 @@
         "Registering OEM:[%#08X], Cmd:[%#04X] for Manual Zone Control\n",
         oem::obmcOemNumber, oem::Cmd::fanManualCmd);
 
+    using namespace std::placeholders;
     router->registerHandler(oem::obmcOemNumber, oem::Cmd::fanManualCmd,
-                            pid_control::ipmi::manualModeControl);
+                            std::bind(pid_control::ipmi::manualModeControl,
+                                      &pid_control::ipmi::handler, _1, _2, _3,
+                                      _4));
 }
diff --git a/ipmi/manualcmds.cpp b/ipmi/manualcmds.cpp
index 5dcac09..0ff0c0a 100644
--- a/ipmi/manualcmds.cpp
+++ b/ipmi/manualcmds.cpp
@@ -16,6 +16,7 @@
 
 #include "manualcmds.hpp"
 
+#include "control.hpp"
 #include "dbus_mode.hpp"
 #include "manual_messages.hpp"
 
@@ -25,6 +26,7 @@
 #include <sdbusplus/message.hpp>
 
 #include <map>
+#include <memory>
 #include <string>
 #include <tuple>
 #include <variant>
@@ -34,25 +36,12 @@
 namespace ipmi
 {
 
-static constexpr auto objectPath = "/xyz/openbmc_project/settings/fanctrl/zone";
-static constexpr auto busName = "xyz.openbmc_project.State.FanCtrl";
-static constexpr auto intf = "xyz.openbmc_project.Control.Mode";
 static constexpr auto manualProperty = "Manual";
 static constexpr auto failsafeProperty = "FailSafe";
-static constexpr auto propertiesintf = "org.freedesktop.DBus.Properties";
 
-using Property = std::string;
-using Value = std::variant<bool>;
-using PropertyMap = std::map<Property, Value>;
-
-/* The following was copied directly from my manual thread handler. */
-static std::string getControlPath(int8_t zone)
-{
-    return std::string(objectPath) + std::to_string(zone);
-}
-
-static ipmi_ret_t getFailsafeModeState(const uint8_t* reqBuf, uint8_t* replyBuf,
-                                       size_t* dataLen)
+ipmi_ret_t ZoneControlIpmiHandler::getFailsafeModeState(const uint8_t* reqBuf,
+                                                        uint8_t* replyBuf,
+                                                        size_t* dataLen)
 {
     bool current;
 
@@ -65,7 +54,7 @@
         reinterpret_cast<const struct FanCtrlRequest*>(&reqBuf[0]);
 
     ipmi_ret_t rc =
-        getFanCtrlProperty(request->zone, &current, failsafeProperty);
+        _control->getFanCtrlProperty(request->zone, &current, failsafeProperty);
     if (rc)
     {
         return rc;
@@ -82,8 +71,9 @@
  *   <arg name="properties" direction="out" type="a{sv}"/>
  * </method>
  */
-static ipmi_ret_t getManualModeState(const uint8_t* reqBuf, uint8_t* replyBuf,
-                                     size_t* dataLen)
+ipmi_ret_t ZoneControlIpmiHandler::getManualModeState(const uint8_t* reqBuf,
+                                                      uint8_t* replyBuf,
+                                                      size_t* dataLen)
 {
     bool current;
 
@@ -95,7 +85,8 @@
     const auto request =
         reinterpret_cast<const struct FanCtrlRequest*>(&reqBuf[0]);
 
-    ipmi_ret_t rc = getFanCtrlProperty(request->zone, &current, manualProperty);
+    ipmi_ret_t rc =
+        _control->getFanCtrlProperty(request->zone, &current, manualProperty);
     if (rc)
     {
         return rc;
@@ -113,51 +104,28 @@
  *   <arg name="value" direction="in" type="v"/>
  * </method>
  */
-static ipmi_ret_t setManualModeState(const uint8_t* reqBuf, uint8_t* replyBuf,
-                                     size_t* dataLen)
+ipmi_ret_t ZoneControlIpmiHandler::setManualModeState(const uint8_t* reqBuf,
+                                                      uint8_t* replyBuf,
+                                                      size_t* dataLen)
 {
     if (*dataLen < sizeof(struct FanCtrlRequestSet))
     {
         return IPMI_CC_INVALID;
     }
 
-    using Value = std::variant<bool>;
-
     const auto request =
         reinterpret_cast<const struct FanCtrlRequestSet*>(&reqBuf[0]);
 
     /* 0 is false, 1 is true */
-    bool setValue = static_cast<bool>(request->value);
-    Value v{setValue};
-
-    auto PropertyWriteBus = sdbusplus::bus::new_system();
-
-    std::string path = getControlPath(request->zone);
-
-    auto pimMsg = PropertyWriteBus.new_method_call(busName, path.c_str(),
-                                                   propertiesintf, "Set");
-    pimMsg.append(intf);
-    pimMsg.append(manualProperty);
-    pimMsg.append(v);
-
-    ipmi_ret_t rc = IPMI_CC_OK;
-
-    try
-    {
-        PropertyWriteBus.call_noreply(pimMsg);
-    }
-    catch (const sdbusplus::exception::SdBusError& ex)
-    {
-        rc = IPMI_CC_INVALID;
-    }
-    /* TODO(venture): Should sanity check the result. */
-
+    ipmi_ret_t rc = _control->setFanCtrlProperty(
+        request->zone, static_cast<bool>(request->value), manualProperty);
     return rc;
 }
 
 /* Three command packages: get, set true, set false */
-ipmi_ret_t manualModeControl(ipmi_cmd_t cmd, const uint8_t* reqBuf,
-                             uint8_t* replyCmdBuf, size_t* dataLen)
+ipmi_ret_t manualModeControl(ZoneControlIpmiHandler* handler, ipmi_cmd_t cmd,
+                             const uint8_t* reqBuf, uint8_t* replyCmdBuf,
+                             size_t* dataLen)
 {
     // FanCtrlRequest is the smaller of the requests, so it's at a minimum.
     if (*dataLen < sizeof(struct FanCtrlRequest))
@@ -173,11 +141,11 @@
     switch (request->command)
     {
         case getControlState:
-            return getManualModeState(reqBuf, replyCmdBuf, dataLen);
+            return handler->getManualModeState(reqBuf, replyCmdBuf, dataLen);
         case setControlState:
-            return setManualModeState(reqBuf, replyCmdBuf, dataLen);
+            return handler->setManualModeState(reqBuf, replyCmdBuf, dataLen);
         case getFailsafeState:
-            return getFailsafeModeState(reqBuf, replyCmdBuf, dataLen);
+            return handler->getFailsafeModeState(reqBuf, replyCmdBuf, dataLen);
         default:
             rc = IPMI_CC_INVALID;
     }
diff --git a/ipmi/manualcmds.hpp b/ipmi/manualcmds.hpp
index ac1513d..4e11662 100644
--- a/ipmi/manualcmds.hpp
+++ b/ipmi/manualcmds.hpp
@@ -1,16 +1,42 @@
 #pragma once
 
+#include "control.hpp"
+
 #include <ipmid/api.h>
 
 #include <cstdint>
+#include <memory>
 
 namespace pid_control
 {
 namespace ipmi
 {
 
-ipmi_ret_t manualModeControl(ipmi_cmd_t cmd, const uint8_t* reqBuf,
-                             uint8_t* replyCmdBuf, size_t* dataLen);
+// Implements validation of IPMI commands and handles sending back the
+// responses.
+class ZoneControlIpmiHandler
+{
+  public:
+    ZoneControlIpmiHandler(std::unique_ptr<ZoneControlInterface> control) :
+        _control(std::move(control))
+    {}
+
+    ipmi_ret_t getFailsafeModeState(const uint8_t* reqBuf, uint8_t* replyBuf,
+                                    size_t* dataLen);
+
+    ipmi_ret_t getManualModeState(const uint8_t* reqBuf, uint8_t* replyBuf,
+                                  size_t* dataLen);
+
+    ipmi_ret_t setManualModeState(const uint8_t* reqBuf, uint8_t* replyBuf,
+                                  size_t* dataLen);
+
+  private:
+    std::unique_ptr<ZoneControlInterface> _control;
+};
+
+ipmi_ret_t manualModeControl(ZoneControlIpmiHandler* handler, ipmi_cmd_t cmd,
+                             const uint8_t* reqBuf, uint8_t* replyCmdBuf,
+                             size_t* dataLen);
 
 } // namespace ipmi
 } // namespace pid_control