netipmid: move sol timers to asio

The IPMI SOL console was using sd_event-based timers directly (without
any abstraction). This moves to a much higher level abstraction that is
very easy to use: asio timers.

Change-Id: Id5df76a1918cdfae420e01884d664234810b7abd
Signed-off-by: Vernon Mauery <vernon.mauery@linux.intel.com>
diff --git a/sd_event_loop.cpp b/sd_event_loop.cpp
index 8d24d23..5be974a 100644
--- a/sd_event_loop.cpp
+++ b/sd_event_loop.cpp
@@ -61,77 +61,6 @@
                           });
 }
 
-static int charAccTimerHandler(sd_event_source* s, uint64_t usec,
-                               void* userdata)
-{
-    auto bufferSize = std::get<sol::Manager&>(singletonPool).dataBuffer.size();
-
-    try
-    {
-        // The instance is hardcoded to 1, in the case of supporting multiple
-        // payload instances we would need to populate it from userdata
-        uint8_t instance = 1;
-
-        if (bufferSize > 0)
-        {
-            auto& context =
-                std::get<sol::Manager&>(singletonPool).getContext(instance);
-
-            int rc = context.sendOutboundPayload();
-
-            if (rc == 0)
-            {
-                return 0;
-            }
-        }
-
-        std::get<eventloop::EventLoop&>(singletonPool)
-            .switchTimer(instance, Timers::ACCUMULATE, true);
-    }
-    catch (std::exception& e)
-    {
-        log<level::ERR>(e.what());
-    }
-
-    return 0;
-}
-
-static int retryTimerHandler(sd_event_source* s, uint64_t usec, void* userdata)
-{
-    try
-    {
-        // The instance is hardcoded to 1, in the case of supporting multiple
-        // payload instances we would need to populate it from userdata
-        uint8_t instance = 1;
-
-        auto& context =
-            std::get<sol::Manager&>(singletonPool).getContext(instance);
-
-        if (context.retryCounter)
-        {
-            --context.retryCounter;
-            std::get<eventloop::EventLoop&>(singletonPool)
-                .switchTimer(instance, Timers::RETRY, true);
-            context.resendPayload(sol::Context::noClear);
-        }
-        else
-        {
-            context.retryCounter = context.maxRetryCount;
-            context.resendPayload(sol::Context::clear);
-            std::get<eventloop::EventLoop&>(singletonPool)
-                .switchTimer(instance, Timers::RETRY, false);
-            std::get<eventloop::EventLoop&>(singletonPool)
-                .switchTimer(instance, Timers::ACCUMULATE, true);
-        }
-    }
-    catch (std::exception& e)
-    {
-        log<level::ERR>(e.what());
-    }
-
-    return 0;
-}
-
 int EventLoop::startEventLoop()
 {
     sdbusplus::asio::sd_event_wrapper sdEvents(*io);
@@ -179,163 +108,4 @@
     return EXIT_SUCCESS;
 }
 
-void EventLoop::startSOLPayloadInstance(uint8_t payloadInst,
-                                        IntervalType accumulateInterval,
-                                        IntervalType retryInterval)
-{
-    auto instance = payloadInst;
-    sd_event_source* accTimerSource = nullptr;
-    sd_event_source* retryTimerSource = nullptr;
-    int rc = 0;
-    uint64_t currentTime = 0;
-
-    rc = sd_event_now(event, CLOCK_MONOTONIC, &currentTime);
-    if (rc < 0)
-    {
-        log<level::ERR>("Failed to get the current timestamp",
-                        entry("RC=%d", rc));
-        throw std::runtime_error("Failed to get current timestamp");
-    }
-
-    // Create character accumulate timer
-    rc = sd_event_add_time(event, &accTimerSource, CLOCK_MONOTONIC,
-                           currentTime + accumulateInterval.count(), 0,
-                           charAccTimerHandler, static_cast<void*>(&instance));
-    if (rc < 0)
-    {
-        log<level::ERR>("Failed to setup the accumulate timer",
-                        entry("RC=%d", rc));
-        throw std::runtime_error("Failed to setup accumulate timer");
-    }
-
-    // Create retry interval timer and add to the event loop
-    rc = sd_event_add_time(event, &retryTimerSource, CLOCK_MONOTONIC,
-                           currentTime + retryInterval.count(), 0,
-                           retryTimerHandler, static_cast<void*>(&instance));
-    if (rc < 0)
-    {
-        log<level::ERR>("Failed to setup the retry timer", entry("RC=%d", rc));
-        throw std::runtime_error("Failed to setup retry timer");
-    }
-
-    // Enable the Character Accumulate Timer
-    rc = sd_event_source_set_enabled(accTimerSource, SD_EVENT_ONESHOT);
-    if (rc < 0)
-    {
-        log<level::ERR>("Failed to enable the accumulate timer",
-                        entry("RC=%d", rc));
-        throw std::runtime_error("Failed to enable accumulate timer");
-    }
-
-    // Disable the Retry Interval Timer
-    rc = sd_event_source_set_enabled(retryTimerSource, SD_EVENT_OFF);
-    if (rc < 0)
-    {
-        log<level::ERR>("Failed to disable the retry timer",
-                        entry("RC=%d", rc));
-        throw std::runtime_error("Failed to disable retry timer");
-    }
-
-    EventSource accEventSource(accTimerSource);
-    EventSource retryEventSource(retryTimerSource);
-    accTimerSource = nullptr;
-    retryTimerSource = nullptr;
-
-    TimerMap timer;
-    timer.emplace(Timers::ACCUMULATE, std::make_tuple(std::move(accEventSource),
-                                                      accumulateInterval));
-    timer.emplace(Timers::RETRY,
-                  std::make_tuple(std::move(retryEventSource), retryInterval));
-    payloadInfo.emplace(instance, std::move(timer));
-}
-
-void EventLoop::stopSOLPayloadInstance(uint8_t payloadInst)
-{
-    auto iter = payloadInfo.find(payloadInst);
-    if (iter == payloadInfo.end())
-    {
-        log<level::ERR>("SOL Payload instance not found",
-                        entry("PAYLOADINST=%d", payloadInst));
-        throw std::runtime_error("SOL payload instance not found");
-    }
-
-    int rc = 0;
-
-    /* Destroy the character accumulate timer event source */
-    rc = sd_event_source_set_enabled(
-        (std::get<0>(iter->second.at(Timers::ACCUMULATE))).get(), SD_EVENT_OFF);
-    if (rc < 0)
-    {
-        log<level::ERR>("Failed to disable the character accumulate timer",
-                        entry("RC=%d", rc));
-        payloadInfo.erase(payloadInst);
-        throw std::runtime_error("Failed to disable accumulate timer");
-    }
-
-    /* Destroy the retry interval timer event source */
-    rc = sd_event_source_set_enabled(
-        (std::get<0>(iter->second.at(Timers::RETRY))).get(), SD_EVENT_OFF);
-    if (rc < 0)
-    {
-        log<level::ERR>("Failed to disable the retry timer",
-                        entry("RC=%d", rc));
-        payloadInfo.erase(payloadInst);
-        throw std::runtime_error("Failed to disable retry timer");
-    }
-
-    payloadInfo.erase(payloadInst);
-}
-
-void EventLoop::switchTimer(uint8_t payloadInst, Timers type, bool status)
-{
-    auto iter = payloadInfo.find(payloadInst);
-    if (iter == payloadInfo.end())
-    {
-        log<level::ERR>("SOL Payload instance not found",
-                        entry("PAYLOADINST=%d", payloadInst));
-        throw std::runtime_error("SOL Payload instance not found");
-    }
-
-    int rc = 0;
-    auto source = (std::get<0>(iter->second.at(type))).get();
-    auto interval = std::get<1>(iter->second.at(type));
-
-    // Turn OFF the timer
-    if (!status)
-    {
-        rc = sd_event_source_set_enabled(source, SD_EVENT_OFF);
-        if (rc < 0)
-        {
-            log<level::ERR>("Failed to disable the timer", entry("RC=%d", rc));
-            throw std::runtime_error("Failed to disable timer");
-        }
-        return;
-    }
-
-    // Turn ON the timer
-    uint64_t currentTime = 0;
-    rc = sd_event_now(event, CLOCK_MONOTONIC, &currentTime);
-    if (rc < 0)
-    {
-        log<level::ERR>("Failed to get the current timestamp",
-                        entry("RC=%d", rc));
-        throw std::runtime_error("Failed to get current timestamp");
-    }
-
-    rc = sd_event_source_set_time(source, currentTime + interval.count());
-    if (rc < 0)
-    {
-        log<level::ERR>("sd_event_source_set_time function failed",
-                        entry("RC=%d", rc));
-        throw std::runtime_error("sd_event_source_set_time function failed");
-    }
-
-    rc = sd_event_source_set_enabled(source, SD_EVENT_ONESHOT);
-    if (rc < 0)
-    {
-        log<level::ERR>("Failed to enable the timer", entry("RC=%d", rc));
-        throw std::runtime_error("Failed to enable timer");
-    }
-}
-
 } // namespace eventloop
diff --git a/sd_event_loop.hpp b/sd_event_loop.hpp
index 0d9c268..10b24ed 100644
--- a/sd_event_loop.hpp
+++ b/sd_event_loop.hpp
@@ -11,35 +11,6 @@
 namespace eventloop
 {
 
-/** @struct EventSourceDeleter
- *
- *  Custom deleter for the sd_event_source*.
- */
-struct EventSourceDeleter
-{
-    void operator()(sd_event_source* event) const
-    {
-        event = sd_event_source_unref(event);
-    }
-};
-
-using EventSource = std::unique_ptr<sd_event_source, EventSourceDeleter>;
-using IntervalType = std::chrono::microseconds;
-
-/** @enum Timers
- *
- *  For SOL functioning, there are two timers involved. The character accumulate
- *  interval timer is the amount of time that the BMC will wait before
- *  transmitting a partial SOL packet. The retry interval timer is the time that
- *  BMC will wait before the first retry and the time between retries when
- *  sending SOL packets to the remote console.
- */
-enum class Timers
-{
-    ACCUMULATE, /**< Character Accumulate Timer */
-    RETRY,      /**< Retry Interval Timer */
-};
-
 class EventLoop
 {
   public:
@@ -53,22 +24,6 @@
     EventLoop(EventLoop&&) = delete;
     EventLoop& operator=(EventLoop&&) = delete;
 
-    /** @brief Timer Map
-     *
-     *  The key for the timer map is the timer type. There are two types of
-     *  timers, character accumulate timer and retry interval timer. The
-     *  entries in the values is the event source for the timer and the
-     *  interval.
-     */
-    using TimerMap = std::map<Timers, std::tuple<EventSource, IntervalType>>;
-
-    /** @brief SOL Payload Map.
-     *
-     *  The key for the payload map is the payload instance, the entries in
-     *  the value are a map of timers.
-     */
-    using PayloadMap = std::map<uint8_t, TimerMap>;
-
     /** @brief Initialise the event loop and add the handler for incoming
      *         IPMI packets.
      *
@@ -76,56 +31,6 @@
      */
     int startEventLoop();
 
-    /** @brief Initialize the timers for the SOL payload instance
-     *
-     *  This API would add the Character accumulate interval timer event
-     *  source and the retry interval timer event source for the SOL payload
-     *  instance to the event loop.
-     *
-     *  @param[in] payloadInst - SOL payload instance.
-     *  @param[in] accumulateInterval - Character accumulate interval.
-     *  @param[in] retryInterval - Retry interval.
-     */
-    void startSOLPayloadInstance(uint8_t payloadInst,
-                                 IntervalType accumulateInterval,
-                                 IntervalType retryInterval);
-
-    /** @brief Stop the timers for the SOL payload instance.
-     *
-     *  This would remove the character accumulate interval timer event
-     *  source and the retry interval timer event source from the event
-     *  loop.
-     *
-     *  @param[in] payloadInst - SOL payload instance
-     */
-    void stopSOLPayloadInstance(uint8_t payloadInst);
-
-    /** @brief Modify the timer event source to enable/disable.
-     *
-     *  When the timer is enabled, the timer it set to fire again at
-     *  timer's interval for the instance added to the event loop iteration
-     *  timestamp. When the timer is disabled, the event source for the
-     *  timer is disabled.
-     *
-     *  @param[in] payloadInst - SOL payload instance.
-     *  @param[in] type -  Timer type.
-     *  @param[in] status - on/off the event source.
-     */
-    void switchTimer(uint8_t payloadInst, Timers type, bool status);
-
-    /** @brief Modify the retry interval timer event source to enable/
-     *         disable
-     *
-     *  When the timer is enabled, the timer it set to fire again at
-     *  retry interval for the instance added to the event loop iteration
-     *  timestamp. When the timer is disabled the event source for the
-     *  retry interval timer is disabled.
-     *
-     *  @param[in] payloadInst - SOL payload instance.
-     *  @param[in] status - on/off the event source.
-     */
-    void switchRetryTimer(uint8_t payloadInst, bool status);
-
     /** @brief Event loop object. */
     sd_event* event = nullptr;
 
@@ -143,11 +48,6 @@
     /** @brief boost::asio udp socket
      */
     std::shared_ptr<boost::asio::ip::udp::socket> udpSocket = nullptr;
-
-    /** @brief Map to keep information regarding IPMI payload instance and
-     *         timers for character accumulate interval and retry interval.
-     */
-    PayloadMap payloadInfo;
 };
 
 } // namespace eventloop
diff --git a/sol/sol_context.cpp b/sol/sol_context.cpp
index fffb66f..215f725 100644
--- a/sol/sol_context.cpp
+++ b/sol/sol_context.cpp
@@ -8,9 +8,62 @@
 
 namespace sol
 {
-
 using namespace phosphor::logging;
 
+Context::Context(std::shared_ptr<boost::asio::io_context> io,
+                 uint8_t maxRetryCount, uint8_t sendThreshold, uint8_t instance,
+                 session::SessionID sessionID) :
+    accumulateTimer(*io),
+    retryTimer(*io), maxRetryCount(maxRetryCount), retryCounter(maxRetryCount),
+    sendThreshold(sendThreshold), payloadInstance(instance),
+    sessionID(sessionID)
+{
+    session = std::get<session::Manager&>(singletonPool).getSession(sessionID);
+    enableAccumulateTimer(true);
+}
+
+void Context::enableAccumulateTimer(bool enable)
+{
+    // fetch the timeout from the SOL manager
+    std::chrono::microseconds interval =
+        std::get<sol::Manager&>(singletonPool).accumulateInterval;
+    if (enable)
+    {
+        accumulateTimer.expires_after(interval);
+        accumulateTimer.async_wait([this](const boost::system::error_code& ec) {
+            if (!ec)
+            {
+                charAccTimerHandler();
+            }
+        });
+    }
+    else
+    {
+        accumulateTimer.cancel();
+    }
+}
+
+void Context::enableRetryTimer(bool enable)
+{
+    if (enable)
+    {
+        // fetch the timeout from the SOL manager
+        std::chrono::microseconds interval =
+            std::get<sol::Manager&>(singletonPool).retryInterval;
+        retryTimer.expires_after(interval);
+        retryTimer.async_wait([this](const boost::system::error_code& ec) {
+            if (!ec)
+            {
+                retryTimerHandler();
+            }
+        });
+    }
+    else
+    {
+        retryTimer.cancel();
+    }
+}
+
 void Context::processInboundPayload(uint8_t seqNum, uint8_t ackSeqNum,
                                     uint8_t count, bool status,
                                     const std::vector<uint8_t>& input)
@@ -54,10 +107,8 @@
     if (status || ((count != expectedCharCount) && ackSeqNum))
     {
         resendPayload(noClear);
-        std::get<eventloop::EventLoop&>(singletonPool)
-            .switchTimer(payloadInstance, eventloop::Timers::RETRY, false);
-        std::get<eventloop::EventLoop&>(singletonPool)
-            .switchTimer(payloadInstance, eventloop::Timers::RETRY, true);
+        enableRetryTimer(false);
+        enableRetryTimer(true);
         return;
     }
     /*
@@ -70,8 +121,7 @@
         std::get<sol::Manager&>(singletonPool).dataBuffer.erase(count);
 
         // Once it is acknowledged stop the retry interval timer
-        std::get<eventloop::EventLoop&>(singletonPool)
-            .switchTimer(payloadInstance, eventloop::Timers::RETRY, false);
+        enableRetryTimer(false);
 
         retryCounter = maxRetryCount;
         expectedCharCount = 0;
@@ -111,8 +161,7 @@
     }
     else
     {
-        std::get<eventloop::EventLoop&>(singletonPool)
-            .switchTimer(payloadInstance, eventloop::Timers::ACCUMULATE, true);
+        enableAccumulateTimer(true);
     }
 }
 
@@ -123,8 +172,7 @@
     /* Sent a ACK only response */
     if (payloadCache.size() != 0 || (bufferSize < sendThreshold))
     {
-        std::get<eventloop::EventLoop&>(singletonPool)
-            .switchTimer(payloadInstance, eventloop::Timers::ACCUMULATE, true);
+        enableAccumulateTimer(true);
 
         std::vector<uint8_t> outPayload(sizeof(Payload));
         auto response = reinterpret_cast<Payload*>(outPayload.data());
@@ -148,10 +196,8 @@
     std::copy_n(handle, readSize, payloadCache.data() + sizeof(Payload));
     expectedCharCount = readSize;
 
-    std::get<eventloop::EventLoop&>(singletonPool)
-        .switchTimer(payloadInstance, eventloop::Timers::RETRY, true);
-    std::get<eventloop::EventLoop&>(singletonPool)
-        .switchTimer(payloadInstance, eventloop::Timers::ACCUMULATE, false);
+    enableRetryTimer(true);
+    enableAccumulateTimer(false);
 
     sendPayload(payloadCache);
 }
@@ -160,8 +206,7 @@
 {
     if (payloadCache.size() != 0)
     {
-        std::get<eventloop::EventLoop&>(singletonPool)
-            .switchTimer(payloadInstance, eventloop::Timers::ACCUMULATE, true);
+        enableAccumulateTimer(true);
         return -1;
     }
 
@@ -179,10 +224,8 @@
     std::copy_n(handle, readSize, payloadCache.data() + sizeof(Payload));
     expectedCharCount = readSize;
 
-    std::get<eventloop::EventLoop&>(singletonPool)
-        .switchTimer(payloadInstance, eventloop::Timers::RETRY, true);
-    std::get<eventloop::EventLoop&>(singletonPool)
-        .switchTimer(payloadInstance, eventloop::Timers::ACCUMULATE, false);
+    enableRetryTimer(true);
+    enableAccumulateTimer(false);
 
     sendPayload(payloadCache);
 
@@ -204,12 +247,54 @@
 
 void Context::sendPayload(const std::vector<uint8_t>& out) const
 {
-    auto session =
-        std::get<session::Manager&>(singletonPool).getSession(sessionID);
-
     message::Handler msgHandler(session->channelPtr, sessionID);
 
     msgHandler.sendSOLPayload(out);
 }
 
+void Context::charAccTimerHandler()
+{
+    auto bufferSize = std::get<sol::Manager&>(singletonPool).dataBuffer.size();
+
+    try
+    {
+        if (bufferSize > 0)
+        {
+            int rc = sendOutboundPayload();
+            if (rc == 0)
+            {
+                return;
+            }
+        }
+        enableAccumulateTimer(true);
+    }
+    catch (std::exception& e)
+    {
+        log<level::ERR>(e.what());
+    }
+}
+
+void Context::retryTimerHandler()
+{
+    try
+    {
+        if (retryCounter)
+        {
+            --retryCounter;
+            enableRetryTimer(true);
+            resendPayload(sol::Context::noClear);
+        }
+        else
+        {
+            retryCounter = maxRetryCount;
+            resendPayload(sol::Context::clear);
+            enableRetryTimer(false);
+            enableAccumulateTimer(true);
+        }
+    }
+    catch (std::exception& e)
+    {
+        log<level::ERR>(e.what());
+    }
+}
 } // namespace sol
diff --git a/sol/sol_context.hpp b/sol/sol_context.hpp
index 220040b..8d0fc87 100644
--- a/sol/sol_context.hpp
+++ b/sol/sol_context.hpp
@@ -3,6 +3,9 @@
 #include "console_buffer.hpp"
 #include "session.hpp"
 
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/steady_timer.hpp>
+
 namespace sol
 {
 
@@ -149,34 +152,37 @@
 class Context
 {
   public:
-    Context() = default;
+    Context() = delete;
     ~Context() = default;
     Context(const Context&) = delete;
     Context& operator=(const Context&) = delete;
-    Context(Context&&) = default;
-    Context& operator=(Context&&) = default;
+    Context(Context&&) = delete;
+    Context& operator=(Context&&) = delete;
 
     /** @brief Context Constructor.
      *
      *  This is issued by the SOL Manager when a SOL payload instance is
      *  started for the activate payload command.
      *
+     *  @param[in] io  - boost::asio io context for event scheduling.
      *  @param[in] maxRetryCount  - Retry count max value.
      *  @param[in] sendThreshold - Character send threshold.
      *  @param[in] instance - SOL payload instance.
      *  @param[in] sessionID - BMC session ID.
      */
-    Context(uint8_t maxRetryCount, uint8_t sendThreshold, uint8_t instance,
-            session::SessionID sessionID) :
-        maxRetryCount(maxRetryCount),
-        retryCounter(maxRetryCount), sendThreshold(sendThreshold),
-        payloadInstance(instance), sessionID(sessionID)
-    {
-    }
+    Context(std::shared_ptr<boost::asio::io_context> io, uint8_t maxRetryCount,
+            uint8_t sendThreshold, uint8_t instance,
+            session::SessionID sessionID);
 
     static constexpr auto clear = true;
     static constexpr auto noClear = false;
 
+    /** @brief accumulate timer */
+    boost::asio::steady_timer accumulateTimer;
+
+    /** @brief retry timer */
+    boost::asio::steady_timer retryTimer;
+
     /** @brief Retry count max value. */
     const uint8_t maxRetryCount = 0;
 
@@ -192,6 +198,28 @@
     /** @brief Session ID. */
     const session::SessionID sessionID = 0;
 
+    /** @brief session pointer
+     */
+    std::shared_ptr<session::Session> session;
+
+    /** @brief enable/disable accumulate timer
+     *
+     *  The timeout interval is managed by the SOL Manager;
+     *  this function only enables or disable the timer
+     *
+     *  @param[in] enable - enable(true) or disable(false) accumulation timer
+     */
+    void enableAccumulateTimer(bool enable);
+
+    /** @brief enable/disable retry timer
+     *
+     *  The timeout interval is managed by the SOL Manager;
+     *  this function only enables or disable the timer
+     *
+     *  @param[in] enable - enable(true) or disable(false) retry timer
+     */
+    void enableRetryTimer(bool enable);
+
     /** @brief Process the Inbound SOL payload.
      *
      *  The SOL payload from the remote console is processed and the
@@ -253,6 +281,12 @@
      *  @param[in] out - buffer containing the SOL payload.
      */
     void sendPayload(const std::vector<uint8_t>& out) const;
+
+    /** @brief accumlate timer handler called by timer */
+    void charAccTimerHandler();
+
+    /** @brief retry timer handler called by timer */
+    void retryTimerHandler();
 };
 
 } // namespace sol
diff --git a/sol/sol_manager.cpp b/sol/sol_manager.cpp
index fc0efe9..2046fe4 100644
--- a/sol/sol_manager.cpp
+++ b/sol/sol_manager.cpp
@@ -102,16 +102,9 @@
     }
 
     // Create the SOL Context data for payload instance
-    auto context = std::make_unique<Context>(retryCount, sendThreshold,
+    auto context = std::make_unique<Context>(io, retryCount, sendThreshold,
                                              payloadInstance, sessionID);
 
-    std::get<eventloop::EventLoop&>(singletonPool)
-        .startSOLPayloadInstance(
-            payloadInstance,
-            std::chrono::duration_cast<eventloop::IntervalType>(
-                accumulateInterval),
-            std::chrono::duration_cast<eventloop::IntervalType>(retryInterval));
-
     payloadMap.emplace(payloadInstance, std::move(context));
 }
 
@@ -125,9 +118,6 @@
 
     payloadMap.erase(iter);
 
-    std::get<eventloop::EventLoop&>(singletonPool)
-        .stopSOLPayloadInstance(payloadInstance);
-
     if (payloadMap.empty())
     {
         stopHostConsole();