requester: PLDM handler for async request/response

PLDM request handler provides APIs to register PLDM request message,
handle retries and instance ID expiration. Sending the PLDM request
and handling response is handled in an async manner. On receiving
the response the corresponding response handler registered for the
request is invoked.

Tested: Ran unit tests

Signed-off-by: Tom Joseph <rushtotom@gmail.com>
Change-Id: I9f0a9dfcf0fbc9a84eefad375b92d40dd8b48d3d
diff --git a/common/types.hpp b/common/types.hpp
index 254c37a..213feeb 100644
--- a/common/types.hpp
+++ b/common/types.hpp
@@ -10,6 +10,9 @@
 namespace pldm

 {

 

+using Request = std::vector<uint8_t>;

+using Response = std::vector<uint8_t>;

+

 namespace dbus

 {

 

diff --git a/host-bmc/host_pdr_handler.cpp b/host-bmc/host_pdr_handler.cpp
index b4ef645..226fc23 100644
--- a/host-bmc/host_pdr_handler.cpp
+++ b/host-bmc/host_pdr_handler.cpp
@@ -21,16 +21,16 @@
 const Json emptyJson{};
 const std::vector<Json> emptyJsonList{};
 
-HostPDRHandler::HostPDRHandler(int mctp_fd, uint8_t mctp_eid,
-                               sdeventplus::Event& event, pldm_pdr* repo,
-                               const std::string& eventsJsonsDir,
-                               pldm_entity_association_tree* entityTree,
-                               pldm_entity_association_tree* bmcEntityTree,
-                               Requester& requester, bool verbose) :
+HostPDRHandler::HostPDRHandler(
+    int mctp_fd, uint8_t mctp_eid, sdeventplus::Event& event, pldm_pdr* repo,
+    const std::string& eventsJsonsDir, pldm_entity_association_tree* entityTree,
+    pldm_entity_association_tree* bmcEntityTree, Requester& requester,
+    pldm::requester::Handler<pldm::requester::Request>& handler, bool verbose) :
     mctp_fd(mctp_fd),
     mctp_eid(mctp_eid), event(event), repo(repo),
     stateSensorHandler(eventsJsonsDir), entityTree(entityTree),
-    bmcEntityTree(bmcEntityTree), requester(requester), verbose(verbose)
+    bmcEntityTree(bmcEntityTree), requester(requester), handler(handler),
+    verbose(verbose)
 {
     fs::path hostFruJson(fs::path(HOST_JSONS_DIR) / fruJson);
     if (fs::exists(hostFruJson))
@@ -382,32 +382,39 @@
         return;
     }
 
-    // Send up the event to host.
-    uint8_t* responseMsg = nullptr;
-    size_t responseMsgSize{};
-    auto requesterRc =
-        pldm_send_recv(mctp_eid, mctp_fd, requestMsg.data(), requestMsg.size(),
-                       &responseMsg, &responseMsgSize);
-    requester.markFree(mctp_eid, instanceId);
-    if (requesterRc != PLDM_REQUESTER_SUCCESS)
+    auto platformEventMessageResponseHandler = [](mctp_eid_t /*eid*/,
+                                                  const pldm_msg* response,
+                                                  size_t respMsgLen) {
+        if (response == nullptr || !respMsgLen)
+        {
+            std::cerr << "Failed to receive response for the PDR repository "
+                         "changed event"
+                      << "\n";
+            return;
+        }
+
+        uint8_t completionCode{};
+        uint8_t status{};
+        auto responsePtr = reinterpret_cast<const struct pldm_msg*>(response);
+        auto rc = decode_platform_event_message_resp(
+            responsePtr, respMsgLen - sizeof(pldm_msg_hdr), &completionCode,
+            &status);
+        if (rc != PLDM_SUCCESS || completionCode != PLDM_SUCCESS)
+        {
+            std::cerr << "Failed to decode_platform_event_message_resp: "
+                      << "rc=" << rc
+                      << ", cc=" << static_cast<unsigned>(completionCode)
+                      << std::endl;
+        }
+    };
+
+    rc = handler.registerRequest(
+        mctp_eid, instanceId, PLDM_PLATFORM, PLDM_PDR_REPOSITORY_CHG_EVENT,
+        std::move(requestMsg), std::move(platformEventMessageResponseHandler));
+    if (rc != PLDM_SUCCESS)
     {
-        std::cerr << "Failed to send msg to report pdrs, rc = " << requesterRc
-                  << std::endl;
-        return;
-    }
-    uint8_t completionCode{};
-    uint8_t status{};
-    auto responsePtr = reinterpret_cast<struct pldm_msg*>(responseMsg);
-    rc = decode_platform_event_message_resp(
-        responsePtr, responseMsgSize - sizeof(pldm_msg_hdr), &completionCode,
-        &status);
-    free(responseMsg);
-    if (rc != PLDM_SUCCESS || completionCode != PLDM_SUCCESS)
-    {
-        std::cerr << "Failed to decode_platform_event_message_resp: "
-                  << "rc=" << rc
-                  << ", cc=" << static_cast<unsigned>(completionCode)
-                  << std::endl;
+        std::cerr << "Failed to send the PDR repository changed event request"
+                  << "\n";
     }
 }
 
diff --git a/host-bmc/host_pdr_handler.hpp b/host-bmc/host_pdr_handler.hpp
index 8e898af..5ca8f91 100644
--- a/host-bmc/host_pdr_handler.hpp
+++ b/host-bmc/host_pdr_handler.hpp
@@ -8,6 +8,7 @@
 #include "libpldmresponder/event_parser.hpp"
 #include "libpldmresponder/pdr_utils.hpp"
 #include "pldmd/dbus_impl_requester.hpp"
+#include "requester/handler.hpp"
 
 #include <sdeventplus/event.hpp>
 #include <sdeventplus/source/event.hpp>
@@ -82,15 +83,18 @@
      *  @param[in] event - reference of main event loop of pldmd
      *  @param[in] repo - pointer to BMC's primary PDR repo
      *  @param[in] eventsJsonDir - directory path which has the config JSONs
-     *  @param[in] tree - pointer to BMC's entity association tree
+     *  @param[in] entityTree - Pointer to BMC and Host entity association tree
+     *  @param[in] bmcEntityTree - pointer to BMC's entity association tree
      *  @param[in] requester - reference to Requester object
+     *  @param[in] handler - PLDM request handler
      */
-    explicit HostPDRHandler(int mctp_fd, uint8_t mctp_eid,
-                            sdeventplus::Event& event, pldm_pdr* repo,
-                            const std::string& eventsJsonsDir,
-                            pldm_entity_association_tree* entityTree,
-                            pldm_entity_association_tree* bmcEntityTree,
-                            Requester& requester, bool verbose = false);
+    explicit HostPDRHandler(
+        int mctp_fd, uint8_t mctp_eid, sdeventplus::Event& event,
+        pldm_pdr* repo, const std::string& eventsJsonsDir,
+        pldm_entity_association_tree* entityTree,
+        pldm_entity_association_tree* bmcEntityTree, Requester& requester,
+        pldm::requester::Handler<pldm::requester::Request>& handler,
+        bool verbose = false);
 
     /** @brief fetch PDRs from host firmware. See @class.
      *  @param[in] recordHandles - list of record handles pointing to host's
@@ -184,6 +188,10 @@
      *  obtain PLDM instance id.
      */
     Requester& requester;
+
+    /** @brief PLDM request handler */
+    pldm::requester::Handler<pldm::requester::Request>& handler;
+
     /** @brief sdeventplus event source */
     std::unique_ptr<sdeventplus::source::Defer> pdrFetchEvent;
     /** @brief list of PDR record handles pointing to host's PDRs */
diff --git a/meson.build b/meson.build
index a9b52aa..c5ca30e 100644
--- a/meson.build
+++ b/meson.build
@@ -47,6 +47,9 @@
   add_project_arguments('-DOEM_IBM', language : 'cpp')
 endif
 conf_data.set('PLDM_VERBOSITY',get_option('verbosity'))
+conf_data.set('NUMBER_OF_REQUEST_RETRIES', get_option('number-of-request-retries'))
+conf_data.set('INSTANCE_ID_EXPIRATION_INTERVAL',get_option('instance-id-expiration-interval'))
+conf_data.set('RESPONSE_TIME_OUT',get_option('response-time-out'))
 configure_file(output: 'config.h',
   configuration: conf_data
 )
@@ -88,6 +91,17 @@
   )
 endif
 
+if cpp.has_header_symbol('function2/function2.hpp', 'fu2::unique_function')
+  function2_dep = declare_dependency()
+else
+  subproject('function2')
+  function2_dep = declare_dependency(
+    include_directories: [
+      'subprojects/function2/include/function2'
+    ]
+  )
+endif
+
 if get_option('oe-sdk').enabled()
   # Setup OE SYSROOT
   OECORE_TARGET_SYSROOT = run_command('sh', '-c', 'echo $OECORE_TARGET_SYSROOT').stdout().strip()
@@ -149,6 +163,7 @@
   link_with: libpldmutils)
 
 deps = [
+  function2_dep,
   libpldm_dep,
   libpldmutils,
   nlohmann_json,
@@ -213,6 +228,7 @@
 if get_option('tests').enabled()
   subdir('common/test')
   subdir('host-bmc/test')
+  subdir('requester/test')
   subdir('test')
 endif
 
diff --git a/meson_options.txt b/meson_options.txt
index 4b1d833..6df6dd9 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -10,3 +10,9 @@
 option('oem-ibm-dma-maxsize', type: 'integer', min:4096, max: 16773120, description: 'OEM-IBM: max DMA size', value: 8384512) #16MB - 4K
 option('softoff', type: 'feature', description: 'Build soft power off application', value: 'enabled')
 option('softoff-timeout-seconds', type: 'integer', description: 'softoff: Time to wait for host to gracefully shutdown', value: 7200)
+
+# Timing specifications for PLDM messages
+option('number-of-request-retries', type: 'integer', min: 2, max: 30, description: 'The number of times a requester is obligated to retry a request', value: 2)
+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)
diff --git a/pldmd/pldmd.cpp b/pldmd/pldmd.cpp
index 9684f0b..4e3bea3 100644
--- a/pldmd/pldmd.cpp
+++ b/pldmd/pldmd.cpp
@@ -6,6 +6,8 @@
 #include "common/utils.hpp"
 #include "dbus_impl_requester.hpp"
 #include "invoker.hpp"
+#include "requester/handler.hpp"
+#include "requester/request.hpp"
 
 #include <err.h>
 #include <getopt.h>
@@ -26,6 +28,7 @@
 #include <iostream>
 #include <iterator>
 #include <memory>
+#include <optional>
 #include <sstream>
 #include <stdexcept>
 #include <string>
@@ -57,11 +60,10 @@
 using namespace pldm::responder;
 using namespace pldm::utils;
 
-static Response processRxMsg(const std::vector<uint8_t>& requestMsg,
-                             Invoker& invoker, dbus_api::Requester& requester)
+static std::optional<Response>
+    processRxMsg(const std::vector<uint8_t>& requestMsg, Invoker& invoker,
+                 requester::Handler<requester::Request>& handler)
 {
-
-    Response response;
     uint8_t eid = requestMsg[0];
     uint8_t type = requestMsg[1];
     pldm_header_info hdrFields{};
@@ -70,11 +72,12 @@
     if (PLDM_SUCCESS != unpack_pldm_header(hdr, &hdrFields))
     {
         std::cerr << "Empty PLDM request header \n";
-        return response;
+        return std::nullopt;
     }
 
     if (PLDM_RESPONSE != hdrFields.msg_type)
     {
+        Response response;
         auto request = reinterpret_cast<const pldm_msg*>(hdr);
         size_t requestLen = requestMsg.size() - sizeof(struct pldm_msg_hdr) -
                             sizeof(eid) - sizeof(type);
@@ -96,15 +99,21 @@
             if (PLDM_SUCCESS != pack_pldm_header(&header, responseHdr))
             {
                 std::cerr << "Failed adding response header \n";
+                return std::nullopt;
             }
             response.insert(response.end(), completion_code);
         }
+        return response;
     }
-    else
+    else if (PLDM_RESPONSE == hdrFields.msg_type)
     {
-        requester.markFree(eid, hdr->instance_id);
+        auto response = reinterpret_cast<const pldm_msg*>(hdr);
+        size_t responseLen = requestMsg.size() - sizeof(struct pldm_msg_hdr) -
+                             sizeof(eid) - sizeof(type);
+        handler.handleResponse(eid, hdrFields.instance, hdrFields.pldm_type,
+                               hdrFields.command, response, responseLen);
     }
-    return response;
+    return std::nullopt;
 }
 
 void optionUsage(void)
@@ -159,6 +168,8 @@
     auto& bus = pldm::utils::DBusHandler::getBus();
     dbus_api::Requester dbusImplReq(bus, "/xyz/openbmc_project/pldm");
     Invoker invoker{};
+    requester::Handler<requester::Request> reqHandler(sockfd, event,
+                                                      dbusImplReq);
 
 #ifdef LIBPLDMRESPONDER
     using namespace pldm::state_sensor;
@@ -182,7 +193,8 @@
     {
         hostPDRHandler = std::make_unique<HostPDRHandler>(
             sockfd, hostEID, event, pdrRepo.get(), EVENTS_JSONS_DIR,
-            entityTree.get(), bmcEntityTree.get(), dbusImplReq, verbose);
+            entityTree.get(), bmcEntityTree.get(), dbusImplReq, reqHandler,
+            verbose);
         hostEffecterParser =
             std::make_unique<pldm::host_effecters::HostEffecterParser>(
                 &dbusImplReq, sockfd, pdrRepo.get(), dbusHandler.get(),
@@ -256,8 +268,8 @@
         exit(EXIT_FAILURE);
     }
 
-    auto callback = [verbose, &invoker, &dbusImplReq](IO& io, int fd,
-                                                      uint32_t revents) {
+    auto callback = [verbose, &invoker, &reqHandler](IO& io, int fd,
+                                                     uint32_t revents) {
         if (!(revents & EPOLLIN))
         {
             return;
@@ -309,20 +321,20 @@
                 {
                     // process message and send response
                     auto response =
-                        processRxMsg(requestMsg, invoker, dbusImplReq);
-                    if (!response.empty())
+                        processRxMsg(requestMsg, invoker, reqHandler);
+                    if (response.has_value())
                     {
                         if (verbose)
                         {
                             std::cout << "Sending Msg" << std::endl;
-                            printBuffer(response, verbose);
+                            printBuffer(*response, verbose);
                         }
 
                         iov[0].iov_base = &requestMsg[0];
                         iov[0].iov_len =
                             sizeof(requestMsg[0]) + sizeof(requestMsg[1]);
-                        iov[1].iov_base = response.data();
-                        iov[1].iov_len = response.size();
+                        iov[1].iov_base = (*response).data();
+                        iov[1].iov_len = (*response).size();
 
                         msg.msg_iov = iov;
                         msg.msg_iovlen = sizeof(iov) / sizeof(iov[0]);
diff --git a/requester/README.md b/requester/README.md
new file mode 100644
index 0000000..fd5ebd1
--- /dev/null
+++ b/requester/README.md
@@ -0,0 +1,43 @@
+## Overview
+
+PLDM requester infrastructure enables the requester code in PLDM daemon to
+meet the requirements of PLDM requesters. It provides the following features:
+
+- Register a PLDM request and the response handler to be invoked on receiving
+  the response.
+- The handling of the request and response is asynchronous. This means the PLDM
+  daemon is not blocked till the response is received for a request.
+- Multiple outstanding requests are supported.
+- Request retries based on the time-out waiting for a response.
+- Instance ID expiration and marking the instance ID free after expiration.
+
+Future enhancements:
+
+- A mechanism to queue multiple outstanding requests to the same responder.
+- Handle ERROR_NOT_READY completion code and retry the PLDM request after 250ms
+  interval.
+
+The requester code needs to use the `registerRequest` API to register the PLDM
+request. The destination endpoint ID, instance ID, PLDM type, PLDM command code,
+PLDM request message (PLDM header and payload) and response function handler are
+passed as parameters to the registerRequest API.
+
+```
+    int registerRequest(mctp_eid_t eid, uint8_t instanceId, uint8_t type,
+                        uint8_t command, pldm::Request&& requestMsg,
+                        ResponseHandler&& responseHandler)
+```
+
+The signature of the response function handler:
+```
+void handler(mctp_eid_t eid, const pldm_msg* response, size_t respMsgLen)
+```
+
+- If the response is received before instance ID expiration:
+  - If the response matches with an outstanding request then the response
+    handler is invoked.
+  - If the response does not match with the PLDM instance ID, PLDM type and PLDM
+    command code of an outstanding request, then no action is taken on the
+    response.
+- Once the instance ID is expired, then the response handler is invoked with
+  empty response, so that further action can be taken.
diff --git a/requester/handler.hpp b/requester/handler.hpp
new file mode 100644
index 0000000..2082f69
--- /dev/null
+++ b/requester/handler.hpp
@@ -0,0 +1,279 @@
+#pragma once
+
+#include "config.h"
+
+#include "libpldm/base.h"
+#include "libpldm/requester/pldm.h"
+
+#include "common/types.hpp"
+#include "pldmd/dbus_impl_requester.hpp"
+#include "request.hpp"
+
+#include <function2/function2.hpp>
+#include <sdbusplus/timer.hpp>
+#include <sdeventplus/event.hpp>
+#include <sdeventplus/source/event.hpp>
+
+#include <cassert>
+#include <chrono>
+#include <memory>
+#include <tuple>
+#include <unordered_map>
+
+namespace pldm
+{
+
+namespace requester
+{
+
+/** @struct RequestKey
+ *
+ *  RequestKey uniquely identifies the PLDM request message to match it with the
+ *  response and a combination of MCTP endpoint ID, PLDM instance ID, PLDM type
+ *  and PLDM command is the key.
+ */
+struct RequestKey
+{
+    mctp_eid_t eid;     //!< MCTP endpoint ID
+    uint8_t instanceId; //!< PLDM instance ID
+    uint8_t type;       //!< PLDM type
+    uint8_t command;    //!< PLDM command
+
+    bool operator==(const RequestKey& e) const
+    {
+        return ((eid == e.eid) && (instanceId == e.instanceId) &&
+                (type == e.type) && (command == e.command));
+    }
+};
+
+/** @struct RequestKeyHasher
+ *
+ *  This is a simple hash function, since the instance ID generator API
+ *  generates unique instance IDs for MCTP endpoint ID.
+ */
+struct RequestKeyHasher
+{
+    std::size_t operator()(const RequestKey& key) const
+    {
+        return (key.eid << 24 | key.instanceId << 16 | key.type << 8 |
+                key.command);
+    }
+};
+
+using ResponseHandler = fu2::unique_function<void(
+    mctp_eid_t eid, const pldm_msg* response, size_t respMsgLen)>;
+using namespace std::chrono;
+using namespace sdeventplus;
+using namespace sdeventplus::source;
+using namespace pldm::dbus_api;
+using namespace phosphor;
+
+/** @class Handler
+ *
+ *  This class handles the lifecycle of the PLDM request message based on the
+ *  instance ID expiration interval, number of request retries and the timeout
+ *  waiting for a response. The registered response handlers are invoked with
+ *  response once the PLDM responder sends the response. If no response is
+ *  received within the instance ID expiration interval or any other failure the
+ *  response handler is invoked with the empty response.
+ *
+ * @tparam RequestInterface - Request class type
+ */
+template <class RequestInterface>
+class Handler
+{
+
+  public:
+    Handler() = delete;
+    Handler(const Handler&) = delete;
+    Handler(Handler&&) = delete;
+    Handler& operator=(const Handler&) = delete;
+    Handler& operator=(Handler&&) = delete;
+    ~Handler() = default;
+
+    /** @brief Constructor
+     *
+     *  @param[in] fd - fd of MCTP communications socket
+     *  @param[in] event - reference to PLDM daemon's main event loop
+     *  @param[in] requester - reference to Requester object
+     *  @param[in] instanceIdExpiryInterval - instance ID expiration interval
+     *  @param[in] numRetries - number of request retries
+     *  @param[in] responseTimeOut - time to wait between each retry
+     */
+    explicit Handler(
+        int fd, Event& event, Requester& requester,
+        seconds instanceIdExpiryInterval =
+            seconds(INSTANCE_ID_EXPIRATION_INTERVAL),
+        uint8_t numRetries = static_cast<uint8_t>(NUMBER_OF_REQUEST_RETRIES),
+        milliseconds responseTimeOut = milliseconds(RESPONSE_TIME_OUT)) :
+        fd(fd),
+        event(event), requester(requester),
+        instanceIdExpiryInterval(instanceIdExpiryInterval),
+        numRetries(numRetries), responseTimeOut(responseTimeOut)
+    {}
+
+    /** @brief Register a PLDM request message
+     *
+     *  @param[in] eid - endpoint ID of the remote MCTP endpoint
+     *  @param[in] instanceId - instance ID to match request and response
+     *  @param[in] type - PLDM type
+     *  @param[in] command - PLDM command
+     *  @param[in] requestMsg - PLDM request message
+     *  @param[in] responseHandler - Response handler for this request
+     *
+     *  @return return PLDM_SUCCESS on success and PLDM_ERROR otherwise
+     */
+    int registerRequest(mctp_eid_t eid, uint8_t instanceId, uint8_t type,
+                        uint8_t command, pldm::Request&& requestMsg,
+                        ResponseHandler&& responseHandler)
+    {
+        RequestKey key{eid, instanceId, type, command};
+
+        auto instanceIdExpiryCallBack = [key, this](void) {
+            if (this->handlers.contains(key))
+            {
+                std::cerr << "Response not received for the request, instance "
+                             "ID expired."
+                          << " EID = " << key.eid
+                          << " INSTANCE_ID = " << key.instanceId
+                          << " TYPE = " << key.type
+                          << " COMMAND = " << key.command << "\n";
+                auto& [request, responseHandler, timerInstance] =
+                    this->handlers[key];
+                request->stop();
+                auto rc = timerInstance->stop();
+                if (rc)
+                {
+                    std::cerr
+                        << "Failed to stop the instance ID expiry timer. RC = "
+                        << rc << "\n";
+                }
+                // Call response handler with an empty response to indicate no
+                // response
+                responseHandler(key.eid, nullptr, 0);
+                this->removeRequestContainer.emplace(
+                    key, std::make_unique<sdeventplus::source::Defer>(
+                             event, std::bind(&Handler::removeRequestEntry,
+                                              this, key)));
+            }
+            else
+            {
+                // This condition is not possible, if a response is received
+                // before the instance ID expiry, then the response handler
+                // is executed and the entry will be removed.
+                assert(false);
+            }
+        };
+
+        auto request = std::make_unique<RequestInterface>(
+            fd, eid, event, std::move(requestMsg), numRetries, responseTimeOut);
+        auto timer = std::make_unique<phosphor::Timer>(
+            event.get(), instanceIdExpiryCallBack);
+
+        auto rc = request->start();
+        if (rc != PLDM_SUCCESS)
+        {
+            std::cerr << "Failure to send the PLDM request message"
+                      << "\n";
+            return rc;
+        }
+
+        try
+        {
+            timer->start(duration_cast<microseconds>(instanceIdExpiryInterval));
+        }
+        catch (const std::runtime_error& e)
+        {
+            std::cerr << "Failed to start the instance ID expiry timer. RC = "
+                      << e.what() << "\n";
+            return PLDM_ERROR;
+        }
+
+        handlers.emplace(key, std::make_tuple(std::move(request),
+                                              std::move(responseHandler),
+                                              std::move(timer)));
+        return rc;
+    }
+
+    /** @brief Handle PLDM response message
+     *
+     *  @param[in] eid - endpoint ID of the remote MCTP endpoint
+     *  @param[in] instanceId - instance ID to match request and response
+     *  @param[in] type - PLDM type
+     *  @param[in] command - PLDM command
+     *  @param[in] response - PLDM response message
+     *  @param[in] respMsgLen - length of the response message
+     */
+    void handleResponse(mctp_eid_t eid, uint8_t instanceId, uint8_t type,
+                        uint8_t command, const pldm_msg* response,
+                        size_t respMsgLen)
+    {
+        RequestKey key{eid, instanceId, type, command};
+        if (handlers.contains(key))
+        {
+            auto& [request, responseHandler, timerInstance] = handlers[key];
+            request->stop();
+            auto rc = timerInstance->stop();
+            if (rc)
+            {
+                std::cerr
+                    << "Failed to stop the instance ID expiry timer. RC = "
+                    << rc << "\n";
+            }
+            responseHandler(eid, response, respMsgLen);
+            requester.markFree(key.eid, key.instanceId);
+            handlers.erase(key);
+        }
+        else
+        {
+            // Got a response for a PLDM request message not registered with the
+            // request handler, so freeing up the instance ID, this can be other
+            // OpenBMC applications relying on PLDM D-Bus apis like
+            // openpower-occ-control and softoff
+            requester.markFree(key.eid, key.instanceId);
+        }
+    }
+
+  private:
+    int fd;               //!< file descriptor of MCTP communications socket
+    Event& event;         //!< reference to PLDM daemon's main event loop
+    Requester& requester; //!< reference to Requester object
+    seconds instanceIdExpiryInterval; //!< Instance ID expiration interval
+    uint8_t numRetries;               //!< number of request retries
+    milliseconds responseTimeOut;     //!< time to wait between each retry
+
+    /** @brief Container for storing the details of the PLDM request message,
+     *         handler for the corresponding PLDM response and the timer object
+     *         for the Instance ID expiration
+     */
+    using RequestValue = std::tuple<std::unique_ptr<RequestInterface>,
+                                    ResponseHandler, std::unique_ptr<Timer>>;
+
+    /** @brief Container for storing the PLDM request entries */
+    std::unordered_map<RequestKey, RequestValue, RequestKeyHasher> handlers;
+
+    /** @brief Container to store information about the request entries to be
+     *         removed after the instance ID timer expires
+     */
+    std::unordered_map<RequestKey, std::unique_ptr<Defer>, RequestKeyHasher>
+        removeRequestContainer;
+
+    /** @brief Remove request entry for which the instance ID expired
+     *
+     *  @param[in] key - key for the Request
+     */
+    void removeRequestEntry(RequestKey key)
+    {
+        if (removeRequestContainer.contains(key))
+        {
+            removeRequestContainer[key].reset();
+            requester.markFree(key.eid, key.instanceId);
+            handlers.erase(key);
+            removeRequestContainer.erase(key);
+        }
+    }
+};
+
+} // namespace requester
+
+} // namespace pldm
diff --git a/requester/request.hpp b/requester/request.hpp
new file mode 100644
index 0000000..4097e10
--- /dev/null
+++ b/requester/request.hpp
@@ -0,0 +1,179 @@
+#pragma once
+
+#include "libpldm/base.h"
+#include "libpldm/requester/pldm.h"
+
+#include "common/types.hpp"
+
+#include <sdbusplus/timer.hpp>
+#include <sdeventplus/event.hpp>
+
+#include <chrono>
+#include <functional>
+#include <iostream>
+
+namespace pldm
+{
+
+namespace requester
+{
+
+using namespace std::chrono;
+
+/** @class RequestRetryTimer
+ *
+ *  The abstract base class for implementing the PLDM request retry logic. This
+ *  class handles number of times the PLDM request needs to be retried if the
+ *  response is not received and the time to wait between each retry. It
+ *  provides APIs to start and stop the request flow.
+ */
+class RequestRetryTimer
+{
+  public:
+    RequestRetryTimer() = delete;
+    RequestRetryTimer(const RequestRetryTimer&) = delete;
+    RequestRetryTimer(RequestRetryTimer&&) = default;
+    RequestRetryTimer& operator=(const RequestRetryTimer&) = delete;
+    RequestRetryTimer& operator=(RequestRetryTimer&&) = default;
+    virtual ~RequestRetryTimer() = default;
+
+    /** @brief Constructor
+     *
+     *  @param[in] event - reference to PLDM daemon's main event loop
+     *  @param[in] numRetries - number of request retries
+     *  @param[in] timeout - time to wait between each retry in milliseconds
+     */
+    explicit RequestRetryTimer(sdeventplus::Event& event, uint8_t numRetries,
+                               milliseconds timeout) :
+
+        event(event),
+        numRetries(numRetries), timeout(timeout),
+        timer(event.get(), std::bind_front(&RequestRetryTimer::callback, this))
+    {}
+
+    /** @brief Starts the request flow and arms the timer for request retries
+     *
+     *  @return return PLDM_SUCCESS on success and PLDM_ERROR otherwise
+     */
+    int start()
+    {
+        auto rc = send();
+        if (rc != PLDM_SUCCESS)
+        {
+            return rc;
+        }
+
+        try
+        {
+            if (numRetries)
+            {
+                timer.start(duration_cast<microseconds>(timeout), true);
+            }
+        }
+        catch (const std::runtime_error& e)
+        {
+            std::cerr << "Failed to start the request timer. RC = " << e.what()
+                      << "\n";
+            return PLDM_ERROR;
+        }
+
+        return PLDM_SUCCESS;
+    }
+
+    /** @brief Stops the timer and no further request retries happen */
+    void stop()
+    {
+        auto rc = timer.stop();
+        if (rc)
+        {
+            std::cerr << "Failed to stop the request timer. RC = " << rc
+                      << "\n";
+        }
+    }
+
+  protected:
+    sdeventplus::Event& event; //!< reference to PLDM daemon's main event loop
+    uint8_t numRetries;        //!< number of request retries
+    milliseconds timeout;  //!< time to wait between each retry in milliseconds
+    phosphor::Timer timer; //!< manages starting timers and handling timeouts
+
+    /** @brief Sends the PLDM request message
+     *
+     *  @return return PLDM_SUCCESS on success and PLDM_ERROR otherwise
+     */
+    virtual int send() const = 0;
+
+    /** @brief Callback function invoked when the timeout happens */
+    void callback()
+    {
+        if (numRetries--)
+        {
+            send();
+        }
+        else
+        {
+            stop();
+        }
+    }
+};
+
+/** @class Request
+ *
+ *  The concrete implementation of RequestIntf. This class implements the send()
+ *  to send the PLDM request message over MCTP socket.
+ *  This class encapsulates the PLDM request message, the number of times the
+ *  request needs to retried if the response is not received and the amount of
+ *  time to wait between each retry. It provides APIs to start and stop the
+ *  request flow.
+ */
+class Request final : public RequestRetryTimer
+{
+  public:
+    Request() = delete;
+    Request(const Request&) = delete;
+    Request(Request&&) = default;
+    Request& operator=(const Request&) = delete;
+    Request& operator=(Request&&) = default;
+    ~Request() = default;
+
+    /** @brief Constructor
+     *
+     *  @param[in] fd - fd of the MCTP communication socket
+     *  @param[in] eid - endpoint ID of the remote MCTP endpoint
+     *  @param[in] event - reference to PLDM daemon's main event loop
+     *  @param[in] requestMsg - PLDM request message
+     *  @param[in] numRetries - number of request retries
+     *  @param[in] timeout - time to wait between each retry in milliseconds
+     */
+    explicit Request(int fd, mctp_eid_t eid, sdeventplus::Event& event,
+                     pldm::Request&& requestMsg, uint8_t numRetries,
+                     milliseconds timeout) :
+        RequestRetryTimer(event, numRetries, timeout),
+        fd(fd), eid(eid), requestMsg(std::move(requestMsg))
+    {}
+
+  private:
+    int fd;         //!< file descriptor of MCTP communications socket
+    mctp_eid_t eid; //!< endpoint ID of the remote MCTP endpoint
+    pldm::Request&& requestMsg; //!< PLDM request message
+
+    /** @brief Sends the PLDM request message on the socket
+     *
+     *  @return return PLDM_SUCCESS on success and PLDM_ERROR otherwise
+     */
+    int send() const
+    {
+        auto rc = pldm_send(eid, fd, requestMsg.data(), requestMsg.size());
+        if (rc < 0)
+        {
+            std::cerr << "Failed to send PLDM message. RC = " << rc
+                      << ", errno = " << errno << "\n";
+            return PLDM_ERROR;
+        }
+        return PLDM_SUCCESS;
+    }
+};
+
+} // namespace requester
+
+} // namespace pldm
\ No newline at end of file
diff --git a/requester/test/handler_test.cpp b/requester/test/handler_test.cpp
new file mode 100644
index 0000000..7230b08
--- /dev/null
+++ b/requester/test/handler_test.cpp
@@ -0,0 +1,149 @@
+#include "libpldm/base.h"
+
+#include "common/types.hpp"
+#include "common/utils.hpp"
+#include "mock_request.hpp"
+#include "pldmd/dbus_impl_requester.hpp"
+#include "requester/handler.hpp"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using namespace pldm::requester;
+using namespace std::chrono;
+
+using ::testing::AtLeast;
+using ::testing::Between;
+using ::testing::Exactly;
+using ::testing::NiceMock;
+using ::testing::Return;
+
+class HandlerTest : public testing::Test
+{
+  protected:
+    HandlerTest() :
+        event(sdeventplus::Event::get_default()),
+        dbusImplReq(pldm::utils::DBusHandler::getBus(),
+                    "/xyz/openbmc_project/pldm")
+    {}
+
+    int fd = 0;
+    mctp_eid_t eid = 0;
+    sdeventplus::Event event;
+    pldm::dbus_api::Requester dbusImplReq;
+
+    /** @brief This function runs the sd_event_run in a loop till all the events
+     *         in the testcase are dispatched and exits when there are no events
+     *         for the timeout time.
+     *
+     *  @param[in] timeout - maximum time to wait for an event
+     */
+    void waitEventExpiry(milliseconds timeout)
+    {
+        while (1)
+        {
+            auto sleepTime = duration_cast<microseconds>(timeout);
+            // Returns 0 on timeout
+            if (!sd_event_run(event.get(), sleepTime.count()))
+            {
+                break;
+            }
+        }
+    }
+
+  public:
+    bool nullResponse = false;
+    bool validResponse = false;
+    int callbackCount = 0;
+    bool response2 = false;
+
+    void pldmResponseCallBack(mctp_eid_t /*eid*/, const pldm_msg* response,
+                              size_t respMsgLen)
+    {
+        if (response == nullptr && respMsgLen == 0)
+        {
+            nullResponse = true;
+        }
+        else
+        {
+            validResponse = true;
+        }
+        callbackCount++;
+    }
+};
+
+TEST_F(HandlerTest, singleRequestResponseScenario)
+{
+    Handler<NiceMock<MockRequest>> reqHandler(fd, event, dbusImplReq,
+                                              seconds(1), 2, milliseconds(100));
+    pldm::Request request{};
+    auto instanceId = dbusImplReq.getInstanceId(eid);
+    reqHandler.registerRequest(
+        eid, instanceId, 0, 0, std::move(request),
+        std::move(std::bind_front(&HandlerTest::pldmResponseCallBack, this)));
+
+    pldm::Response response(sizeof(pldm_msg_hdr) + sizeof(uint8_t));
+    auto responsePtr = reinterpret_cast<const pldm_msg*>(response.data());
+    reqHandler.handleResponse(eid, instanceId, 0, 0, responsePtr,
+                              sizeof(response));
+
+    // handleResponse() will free the instance ID after calling the response
+    // handler, so the same instance ID is granted next as well
+    ASSERT_EQ(validResponse, true);
+    ASSERT_EQ(instanceId, dbusImplReq.getInstanceId(eid));
+}
+
+TEST_F(HandlerTest, singleRequestInstanceIdTimerExpired)
+{
+    Handler<NiceMock<MockRequest>> reqHandler(fd, event, dbusImplReq,
+                                              seconds(1), 2, milliseconds(100));
+    pldm::Request request{};
+    auto instanceId = dbusImplReq.getInstanceId(eid);
+    reqHandler.registerRequest(
+        eid, instanceId, 0, 0, std::move(request),
+        std::move(std::bind_front(&HandlerTest::pldmResponseCallBack, this)));
+
+    // Waiting for 500ms so that the instance ID expiry callback is invoked
+    waitEventExpiry(milliseconds(500));
+
+    // cleanup() will free the instance ID after calling the response
+    // handler will no response, so the same instance ID is granted next
+    ASSERT_EQ(instanceId, dbusImplReq.getInstanceId(eid));
+    ASSERT_EQ(nullResponse, true);
+}
+
+TEST_F(HandlerTest, multipleRequestResponseScenario)
+{
+    Handler<NiceMock<MockRequest>> reqHandler(fd, event, dbusImplReq,
+                                              seconds(2), 2, milliseconds(100));
+    pldm::Request request{};
+    auto instanceId = dbusImplReq.getInstanceId(eid);
+    reqHandler.registerRequest(
+        eid, instanceId, 0, 0, std::move(request),
+        std::move(std::bind_front(&HandlerTest::pldmResponseCallBack, this)));
+
+    pldm::Request requestNxt{};
+    auto instanceIdNxt = dbusImplReq.getInstanceId(eid);
+    reqHandler.registerRequest(
+        eid, instanceIdNxt, 0, 0, std::move(requestNxt),
+        std::move(std::bind_front(&HandlerTest::pldmResponseCallBack, this)));
+
+    pldm::Response response(sizeof(pldm_msg_hdr) + sizeof(uint8_t));
+    auto responsePtr = reinterpret_cast<const pldm_msg*>(response.data());
+    reqHandler.handleResponse(eid, instanceIdNxt, 0, 0, responsePtr,
+                              sizeof(response));
+    ASSERT_EQ(validResponse, true);
+    ASSERT_EQ(callbackCount, 1);
+    validResponse = false;
+
+    // Waiting for 500ms and handle the response for the first request, to
+    // simulate a delayed response for the first request
+    waitEventExpiry(milliseconds(500));
+
+    reqHandler.handleResponse(eid, instanceId, 0, 0, responsePtr,
+                              sizeof(response));
+
+    ASSERT_EQ(validResponse, true);
+    ASSERT_EQ(callbackCount, 2);
+    ASSERT_EQ(instanceId, dbusImplReq.getInstanceId(eid));
+}
\ No newline at end of file
diff --git a/requester/test/meson.build b/requester/test/meson.build
new file mode 100644
index 0000000..1f5fe17
--- /dev/null
+++ b/requester/test/meson.build
@@ -0,0 +1,25 @@
+test_src = declare_dependency(
+          sources: [
+            '../../pldmd/dbus_impl_requester.cpp',
+            '../../pldmd/instance_id.cpp'])
+
+tests = [
+  'handler_test',
+  'request_test',
+]
+
+foreach t : tests
+  test(t, executable(t.underscorify(), t + '.cpp',
+                     implicit_include_directories: false,
+                     link_args: dynamic_linker,
+                     build_rpath: get_option('oe-sdk').enabled() ? rpath : '',
+                     dependencies: [
+                         gtest,
+                         gmock,
+                         libpldm_dep,
+                         phosphor_dbus_interfaces,
+                         sdbusplus,
+                         sdeventplus,
+                         test_src]),
+       workdir: meson.current_source_dir())
+endforeach
diff --git a/requester/test/mock_request.hpp b/requester/test/mock_request.hpp
new file mode 100644
index 0000000..b8e9efb
--- /dev/null
+++ b/requester/test/mock_request.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+#include "requester/request.hpp"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace pldm
+{
+
+namespace requester
+{
+
+using namespace std::chrono;
+
+class MockRequest : public RequestRetryTimer
+{
+  public:
+    MockRequest(int /*fd*/, mctp_eid_t /*eid*/, sdeventplus::Event& event,
+                pldm::Request&& /*requestMsg*/, uint8_t numRetries,
+                milliseconds responseTimeOut) :
+        RequestRetryTimer(event, numRetries, responseTimeOut)
+    {}
+
+    MOCK_METHOD(int, send, (), (const, override));
+};
+
+} // namespace requester
+
+} // namespace pldm
\ No newline at end of file
diff --git a/requester/test/request_test.cpp b/requester/test/request_test.cpp
new file mode 100644
index 0000000..6471d9e
--- /dev/null
+++ b/requester/test/request_test.cpp
@@ -0,0 +1,101 @@
+#include "libpldm/base.h"
+
+#include "mock_request.hpp"
+
+#include <sdbusplus/timer.hpp>
+#include <sdeventplus/event.hpp>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using namespace pldm::requester;
+using namespace std::chrono;
+using ::testing::AtLeast;
+using ::testing::Between;
+using ::testing::Exactly;
+using ::testing::Return;
+
+class RequestIntfTest : public testing::Test
+{
+  protected:
+    RequestIntfTest() : event(sdeventplus::Event::get_default())
+    {}
+
+    /** @brief This function runs the sd_event_run in a loop till all the events
+     *         in the testcase are dispatched and exits when there are no events
+     *         for the timeout time.
+     *
+     *  @param[in] timeout - maximum time to wait for an event
+     */
+    void waitEventExpiry(milliseconds timeout)
+    {
+        while (1)
+        {
+            auto sleepTime = duration_cast<microseconds>(timeout);
+            // Returns 0 on timeout
+            if (!sd_event_run(event.get(), sleepTime.count()))
+            {
+                break;
+            }
+        }
+    }
+
+    int fd = 0;
+    mctp_eid_t eid = 0;
+    sdeventplus::Event event;
+    std::vector<uint8_t> requestMsg;
+};
+
+TEST_F(RequestIntfTest, 0Retries100msTimeout)
+{
+    MockRequest request(fd, eid, event, std::move(requestMsg), 0,
+                        milliseconds(100));
+    EXPECT_CALL(request, send())
+        .Times(Exactly(1))
+        .WillOnce(Return(PLDM_SUCCESS));
+    auto rc = request.start();
+    ASSERT_EQ(rc, PLDM_SUCCESS);
+}
+
+TEST_F(RequestIntfTest, 2Retries100msTimeout)
+{
+    MockRequest request(fd, eid, event, std::move(requestMsg), 2,
+                        milliseconds(100));
+    // send() is called a total of 3 times, the original plus two retries
+    EXPECT_CALL(request, send()).Times(3).WillRepeatedly(Return(PLDM_SUCCESS));
+    auto rc = request.start();
+    ASSERT_EQ(rc, PLDM_SUCCESS);
+    waitEventExpiry(milliseconds(500));
+}
+
+TEST_F(RequestIntfTest, 9Retries100msTimeoutRequestStoppedAfter1sec)
+{
+    MockRequest request(fd, eid, event, std::move(requestMsg), 9,
+                        milliseconds(100));
+    // send() will be called a total of 10 times, the original plus 9 retries.
+    // In a ideal scenario send() would have been called 10 times in 1 sec (when
+    // the timer is stopped) with a timeout of 100ms. Because there are delays
+    // in dispatch, the range is kept between 5 and 10. This recreates the
+    // situation where the Instance ID expires before the all the retries have
+    // been completed and the timer is stopped.
+    EXPECT_CALL(request, send())
+        .Times(Between(5, 10))
+        .WillRepeatedly(Return(PLDM_SUCCESS));
+    auto rc = request.start();
+    ASSERT_EQ(rc, PLDM_SUCCESS);
+
+    auto requestStopCallback = [&](void) { request.stop(); };
+    phosphor::Timer timer(event.get(), requestStopCallback);
+    timer.start(duration_cast<microseconds>(seconds(1)));
+
+    waitEventExpiry(milliseconds(500));
+}
+
+TEST_F(RequestIntfTest, 2Retries100msTimeoutsendReturnsError)
+{
+    MockRequest request(fd, eid, event, std::move(requestMsg), 2,
+                        milliseconds(100));
+    EXPECT_CALL(request, send()).Times(Exactly(1)).WillOnce(Return(PLDM_ERROR));
+    auto rc = request.start();
+    ASSERT_EQ(rc, PLDM_ERROR);
+}
diff --git a/subprojects/function2.wrap b/subprojects/function2.wrap
new file mode 100644
index 0000000..096a1d2
--- /dev/null
+++ b/subprojects/function2.wrap
@@ -0,0 +1,3 @@
+[wrap-git]
+url = https://github.com/Naios/function2
+revision = master