PLDM: implement surveillance between Host and bmc

This commit is to implement surveillance between host
and bmc, wherein host monitors if bmc is up and
running through constant pings(by sending Platform
EventMessages) sent from host to BMC. And if BMC
fails to respond to the pings, then BMC will be
reset using the KCS interface.

1. Host->BMC - GetTID
2. BMC->Host - Respond to GetTID, SetEventReceiver
3. Host->BMC - Respond to SetEventReceiver
4. BMC->Host -  Send PlatformEventMessage after the
   elapsed time interval(specified with
   SetEventReceiver command)
4. Host->BMC - If BMC fails to send respond to host
   within specified interval, Host resets BMC via
   the KCS interface

Tested with PLDMTOOL:
SetEventReceiver command:
root@rain127bmc:/tmp# ./pldmtool base GetTID -m 8
Received Msg
08 01 81 00 02
Sending Msg
01 00 02 00 01
{
    "Response": 1
}

platformEventMessage command(which will be received
by host):
root@rain118bmc:/tmp# ./pldmtool raw -d 0x80 0x02
0x0A 0x01 0x01 0x06 0x01 0x01
Request Message:
08 01 80 02 0a 01 01 06 01 01
Received Msg
08 01 80 02 0a 01 01 06 01 01
eventClass Checking
Sending Msg
00 02 0a 00 00
Response Message:
08 01 00 02 0a 00 00
Received Msg
08 01 00 02 0a 00 00

Signed-off-by: Sagar Srinivas <sagar.srinivas@ibm.com>
Change-Id: Iac90b2233a873a54504ffa649d324d30525b7ce3
diff --git a/libpldmresponder/base.cpp b/libpldmresponder/base.cpp
index 395529b..7195b80 100644
--- a/libpldmresponder/base.cpp
+++ b/libpldmresponder/base.cpp
@@ -1,13 +1,19 @@
+#include "config.h"
+
 #include "libpldm/base.h"
 
 #include "libpldm/bios.h"
 #include "libpldm/fru.h"
 #include "libpldm/platform.h"
+#include "libpldm/requester/pldm.h"
 
 #include "base.hpp"
+#include "common/utils.hpp"
+#include "libpldmresponder/pdr.hpp"
 
 #include <array>
 #include <cstring>
+#include <iostream>
 #include <map>
 #include <stdexcept>
 #include <vector>
@@ -170,6 +176,64 @@
     return response;
 }
 
+void Handler::processSetEventReceiver(
+    sdeventplus::source::EventBase& /*source */)
+{
+    survEvent.reset();
+    std::vector<uint8_t> requestMsg(sizeof(pldm_msg_hdr) +
+                                    PLDM_SET_EVENT_RECEIVER_REQ_BYTES);
+    auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
+    auto instanceId = requester.getInstanceId(eid);
+    uint8_t eventMessageGlobalEnable =
+        PLDM_EVENT_MESSAGE_GLOBAL_ENABLE_ASYNC_KEEP_ALIVE;
+    uint8_t transportProtocolType = PLDM_TRANSPORT_PROTOCOL_TYPE_MCTP;
+    uint8_t eventReceiverAddressInfo = pldm::responder::pdr::BmcMctpEid;
+    uint16_t heartbeatTimer = HEARTBEAT_TIMEOUT;
+
+    auto rc = encode_set_event_receiver_req(
+        instanceId, eventMessageGlobalEnable, transportProtocolType,
+        eventReceiverAddressInfo, heartbeatTimer, request);
+    if (rc != PLDM_SUCCESS)
+    {
+        requester.markFree(eid, instanceId);
+        std::cerr << "Failed to encode_set_event_receiver_req, rc = "
+                  << std::hex << std::showbase << rc << std::endl;
+        return;
+    }
+
+    auto processSetEventReceiverResponse = [](mctp_eid_t /*eid*/,
+                                              const pldm_msg* response,
+                                              size_t respMsgLen) {
+        if (response == nullptr || !respMsgLen)
+        {
+            std::cerr << "Failed to receive response for "
+                         "setEventReceiver command \n";
+            return;
+        }
+
+        uint8_t completionCode{};
+        auto rc = decode_set_event_receiver_resp(response, respMsgLen,
+                                                 &completionCode);
+        if (rc || completionCode)
+        {
+            std::cerr << "Failed to decode setEventReceiver command response,"
+                      << " rc=" << rc << "cc=" << (uint8_t)completionCode
+                      << "\n";
+            pldm::utils::reportError(
+                "xyz.openbmc_project.bmc.pldm.InternalFailure");
+        }
+    };
+    rc = handler->registerRequest(
+        eid, instanceId, PLDM_PLATFORM, PLDM_SET_EVENT_RECEIVER,
+        std::move(requestMsg), std::move(processSetEventReceiverResponse));
+
+    if (rc != PLDM_SUCCESS)
+    {
+        std::cerr << "Failed to send the setEventReceiver request"
+                  << "\n";
+    }
+}
+
 Response Handler::getTID(const pldm_msg* request, size_t /*payloadLength*/)
 {
     // assigned 1 to the bmc as the PLDM terminus
@@ -184,6 +248,9 @@
         return ccOnlyResponse(request, rc);
     }
 
+    survEvent = std::make_unique<sdeventplus::source::Defer>(
+        event, std::bind_front(&Handler::processSetEventReceiver, this));
+
     return response;
 }
 
diff --git a/libpldmresponder/base.hpp b/libpldmresponder/base.hpp
index 6e5b653..195e221 100644
--- a/libpldmresponder/base.hpp
+++ b/libpldmresponder/base.hpp
@@ -1,13 +1,21 @@
 #pragma once
 
 #include "libpldm/base.h"
+#include "libpldm/platform.h"
 
+#include "pldmd/dbus_impl_requester.hpp"
 #include "pldmd/handler.hpp"
+#include "requester/handler.hpp"
 
 #include <stdint.h>
 
+#include <sdeventplus/source/event.hpp>
+
 #include <vector>
 
+using namespace pldm::dbus_api;
+using namespace pldm::responder;
+
 namespace pldm
 {
 namespace responder
@@ -18,7 +26,10 @@
 class Handler : public CmdHandler
 {
   public:
-    Handler()
+    Handler(uint8_t eid, Requester& requester, sdeventplus::Event& event,
+            pldm::requester::Handler<pldm::requester::Request>* handler) :
+        eid(eid),
+        requester(requester), event(event), handler(handler)
     {
         handlers.emplace(PLDM_GET_PLDM_TYPES,
                          [this](const pldm_msg* request, size_t payloadLength) {
@@ -62,6 +73,14 @@
      */
     Response getPLDMVersion(const pldm_msg* request, size_t payloadLength);
 
+    /** @brief _processSetEventReceiver does the actual work that needs
+     *  to be carried out for setEventReceiver command. This is deferred
+     *  after sending response for getTID command to the host
+     *
+     *  @param[in] source - sdeventplus event source
+     */
+    void processSetEventReceiver(sdeventplus::source::EventBase& source);
+
     /** @brief Handler for getTID
      *
      *  @param[in] request - Request message payload
@@ -69,6 +88,26 @@
      *  @param[return] Response - PLDM Response message
      */
     Response getTID(const pldm_msg* request, size_t payloadLength);
+
+  private:
+    /** @brief MCTP EID of host firmware */
+    uint8_t eid;
+
+    /** @brief reference to Requester object, primarily used to access API to
+     *  obtain PLDM instance id.
+     */
+    Requester& requester;
+
+    /** @brief reference of main event loop of pldmd, primarily used to schedule
+     *  work
+     */
+    sdeventplus::Event& event;
+
+    /** @brief PLDM request handler */
+    pldm::requester::Handler<pldm::requester::Request>* handler;
+
+    /** @brief sdeventplus event source */
+    std::unique_ptr<sdeventplus::source::Defer> survEvent;
 };
 
 } // namespace base
diff --git a/libpldmresponder/platform.cpp b/libpldmresponder/platform.cpp
index dd7851c..73274ac 100644
--- a/libpldmresponder/platform.cpp
+++ b/libpldmresponder/platform.cpp
@@ -313,24 +313,30 @@
         return CmdHandler::ccOnlyResponse(request, rc);
     }
 
-    try
+    if (eventClass == PLDM_HEARTBEAT_TIMER_ELAPSED_EVENT)
     {
-        const auto& handlers = eventHandlers.at(eventClass);
-        for (const auto& handler : handlers)
+        rc = PLDM_SUCCESS;
+    }
+    else
+    {
+        try
         {
-            auto rc =
-                handler(request, payloadLength, formatVersion, tid, offset);
-            if (rc != PLDM_SUCCESS)
+            const auto& handlers = eventHandlers.at(eventClass);
+            for (const auto& handler : handlers)
             {
-                return CmdHandler::ccOnlyResponse(request, rc);
+                auto rc =
+                    handler(request, payloadLength, formatVersion, tid, offset);
+                if (rc != PLDM_SUCCESS)
+                {
+                    return CmdHandler::ccOnlyResponse(request, rc);
+                }
             }
         }
+        catch (const std::out_of_range& e)
+        {
+            return CmdHandler::ccOnlyResponse(request, PLDM_ERROR_INVALID_DATA);
+        }
     }
-    catch (const std::out_of_range& e)
-    {
-        return CmdHandler::ccOnlyResponse(request, PLDM_ERROR_INVALID_DATA);
-    }
-
     Response response(
         sizeof(pldm_msg_hdr) + PLDM_PLATFORM_EVENT_MESSAGE_RESP_BYTES, 0);
     auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
diff --git a/libpldmresponder/test/libpldmresponder_base_test.cpp b/libpldmresponder/test/libpldmresponder_base_test.cpp
index ac6e681..19b870b 100644
--- a/libpldmresponder/test/libpldmresponder_base_test.cpp
+++ b/libpldmresponder/test/libpldmresponder_base_test.cpp
@@ -1,22 +1,38 @@
 #include "libpldm/base.h"
 
+#include "common/utils.hpp"
 #include "libpldmresponder/base.hpp"
 
 #include <string.h>
 
+#include <sdeventplus/event.hpp>
+
 #include <array>
 
 #include <gtest/gtest.h>
 
 using namespace pldm::responder;
 
-TEST(GetPLDMTypes, testGoodRequest)
+class TestBaseCommands : public testing::Test
+{
+  protected:
+    TestBaseCommands() :
+        requester(pldm::utils::DBusHandler::getBus(), "/abc/def"),
+        event(sdeventplus::Event::get_default())
+    {}
+
+    uint8_t mctpEid = 0;
+    Requester requester;
+    sdeventplus::Event event;
+};
+
+TEST_F(TestBaseCommands, testPLDMTypesGoodRequest)
 {
     std::array<uint8_t, sizeof(pldm_msg_hdr)> requestPayload{};
     auto request = reinterpret_cast<pldm_msg*>(requestPayload.data());
     // payload length will be 0 in this case
     size_t requestPayloadLength = 0;
-    base::Handler handler;
+    base::Handler handler(mctpEid, requester, event, nullptr);
     auto response = handler.getPLDMTypes(request, requestPayloadLength);
     // Need to support OEM type.
     auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
@@ -26,14 +42,14 @@
     ASSERT_EQ(payload_ptr[2], 0);
 }
 
-TEST(GetPLDMCommands, testGoodRequest)
+TEST_F(TestBaseCommands, testGetPLDMCommandsGoodRequest)
 {
     // Need to support OEM type commands.
     std::array<uint8_t, sizeof(pldm_msg_hdr) + PLDM_GET_COMMANDS_REQ_BYTES>
         requestPayload{};
     auto request = reinterpret_cast<pldm_msg*>(requestPayload.data());
     size_t requestPayloadLength = requestPayload.size() - sizeof(pldm_msg_hdr);
-    base::Handler handler;
+    base::Handler handler(mctpEid, requester, event, nullptr);
     auto response = handler.getPLDMCommands(request, requestPayloadLength);
     auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
     uint8_t* payload_ptr = responsePtr->payload;
@@ -42,7 +58,7 @@
     ASSERT_EQ(payload_ptr[2], 0);
 }
 
-TEST(GetPLDMCommands, testBadRequest)
+TEST_F(TestBaseCommands, testGetPLDMCommandsBadRequest)
 {
     std::array<uint8_t, sizeof(pldm_msg_hdr) + PLDM_GET_COMMANDS_REQ_BYTES>
         requestPayload{};
@@ -50,13 +66,14 @@
 
     request->payload[0] = 0xFF;
     size_t requestPayloadLength = requestPayload.size() - sizeof(pldm_msg_hdr);
-    base::Handler handler;
+    base::Handler handler(mctpEid, requester, event, nullptr);
     auto response = handler.getPLDMCommands(request, requestPayloadLength);
     auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
     uint8_t* payload_ptr = responsePtr->payload;
     ASSERT_EQ(payload_ptr[0], PLDM_ERROR_INVALID_PLDM_TYPE);
 }
-TEST(GetPLDMVersion, testGoodRequest)
+
+TEST_F(TestBaseCommands, testGetPLDMVersionGoodRequest)
 {
     std::array<uint8_t, sizeof(pldm_msg_hdr) + PLDM_GET_VERSION_REQ_BYTES>
         requestPayload{};
@@ -74,7 +91,7 @@
 
     ASSERT_EQ(0, rc);
 
-    base::Handler handler;
+    base::Handler handler(mctpEid, requester, event, nullptr);
     auto response = handler.getPLDMVersion(request, requestPayloadLength);
     auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
 
@@ -88,7 +105,8 @@
                             sizeof(transferHandle) + sizeof(flag),
                         &version, sizeof(version)));
 }
-TEST(GetPLDMVersion, testBadRequest)
+
+TEST_F(TestBaseCommands, testGetPLDMVersionBadRequest)
 {
     std::array<uint8_t, sizeof(pldm_msg_hdr) + PLDM_GET_VERSION_REQ_BYTES>
         requestPayload{};
@@ -104,7 +122,7 @@
 
     ASSERT_EQ(0, rc);
 
-    base::Handler handler;
+    base::Handler handler(mctpEid, requester, event, nullptr);
     auto response = handler.getPLDMVersion(request, requestPayloadLength - 1);
     auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
 
@@ -123,13 +141,13 @@
     ASSERT_EQ(responsePtr->payload[0], PLDM_ERROR_INVALID_PLDM_TYPE);
 }
 
-TEST(GetTID, testGoodRequest)
+TEST_F(TestBaseCommands, testGetTIDGoodRequest)
 {
     std::array<uint8_t, sizeof(pldm_msg_hdr)> requestPayload{};
     auto request = reinterpret_cast<pldm_msg*>(requestPayload.data());
     size_t requestPayloadLength = 0;
 
-    base::Handler handler;
+    base::Handler handler(mctpEid, requester, event, nullptr);
     auto response = handler.getTID(request, requestPayloadLength);
 
     auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
diff --git a/meson.build b/meson.build
index 14b67d5..b06a082 100644
--- a/meson.build
+++ b/meson.build
@@ -33,6 +33,7 @@
 conf_data.set_quoted('FRU_MASTER_JSON', join_paths(package_datadir, 'fru_master.json'))
 conf_data.set_quoted('HOST_JSONS_DIR', join_paths(package_datadir, 'host'))
 conf_data.set_quoted('EVENTS_JSONS_DIR', join_paths(package_datadir, 'events'))
+conf_data.set('HEARTBEAT_TIMEOUT', get_option('heartbeat-timeout-seconds'))
 add_project_arguments('-DLIBPLDMRESPONDER', language : ['c','cpp'])
 endif
 if get_option('softoff').enabled()
diff --git a/meson_options.txt b/meson_options.txt
index 9943e82..5462403 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -18,3 +18,5 @@
 option('instance-id-expiration-interval', type: 'integer', min: 5, max: 6, description: 'Instance ID expiration interval in seconds', value: 5)
 # Default response-time-out set to 2 seconds to facilitate a minimum retry of the request of 2.
 option('response-time-out', type: 'integer', min: 300, max: 4800, description: 'The amount of time a requester has to wait for a response message in milliseconds', value: 2000)
+
+option('heartbeat-timeout-seconds', type: 'integer', description: ' The amount of time host waits for BMC to respond to pings from host, as part of host-bmc surveillance', value: 120)
diff --git a/pldmd/pldmd.cpp b/pldmd/pldmd.cpp
index 9114eaf..b1d2904 100644
--- a/pldmd/pldmd.cpp
+++ b/pldmd/pldmd.cpp
@@ -222,7 +222,9 @@
                                           oemPlatformHandler.get(), sockfd,
                                           hostEID, &dbusImplReq, &reqHandler));
 #endif
-    invoker.registerHandler(PLDM_BASE, std::make_unique<base::Handler>());
+    invoker.registerHandler(
+        PLDM_BASE, std::make_unique<base::Handler>(hostEID, dbusImplReq, event,
+                                                   &reqHandler));
     invoker.registerHandler(
         PLDM_BIOS, std::make_unique<bios::Handler>(sockfd, hostEID,
                                                    &dbusImplReq, &reqHandler));