xref: /openbmc/pldm/requester/handler.hpp (revision b3dbb6e8eb5a05a77c572bedce89b8ce331726b2)
1 #pragma once
2 
3 #include "config.h"
4 
5 #include "libpldm/base.h"
6 #include "libpldm/requester/pldm.h"
7 
8 #include "common/types.hpp"
9 #include "pldmd/dbus_impl_requester.hpp"
10 #include "request.hpp"
11 
12 #include <function2/function2.hpp>
13 #include <sdbusplus/timer.hpp>
14 #include <sdeventplus/event.hpp>
15 #include <sdeventplus/source/event.hpp>
16 
17 #include <cassert>
18 #include <chrono>
19 #include <memory>
20 #include <tuple>
21 #include <unordered_map>
22 
23 namespace pldm
24 {
25 
26 namespace requester
27 {
28 
29 /** @struct RequestKey
30  *
31  *  RequestKey uniquely identifies the PLDM request message to match it with the
32  *  response and a combination of MCTP endpoint ID, PLDM instance ID, PLDM type
33  *  and PLDM command is the key.
34  */
35 struct RequestKey
36 {
37     mctp_eid_t eid;     //!< MCTP endpoint ID
38     uint8_t instanceId; //!< PLDM instance ID
39     uint8_t type;       //!< PLDM type
40     uint8_t command;    //!< PLDM command
41 
42     bool operator==(const RequestKey& e) const
43     {
44         return ((eid == e.eid) && (instanceId == e.instanceId) &&
45                 (type == e.type) && (command == e.command));
46     }
47 };
48 
49 /** @struct RequestKeyHasher
50  *
51  *  This is a simple hash function, since the instance ID generator API
52  *  generates unique instance IDs for MCTP endpoint ID.
53  */
54 struct RequestKeyHasher
55 {
56     std::size_t operator()(const RequestKey& key) const
57     {
58         return (key.eid << 24 | key.instanceId << 16 | key.type << 8 |
59                 key.command);
60     }
61 };
62 
63 using ResponseHandler = fu2::unique_function<void(
64     mctp_eid_t eid, const pldm_msg* response, size_t respMsgLen)>;
65 using namespace std::chrono;
66 using namespace sdeventplus;
67 using namespace sdeventplus::source;
68 using namespace pldm::dbus_api;
69 using namespace phosphor;
70 
71 /** @class Handler
72  *
73  *  This class handles the lifecycle of the PLDM request message based on the
74  *  instance ID expiration interval, number of request retries and the timeout
75  *  waiting for a response. The registered response handlers are invoked with
76  *  response once the PLDM responder sends the response. If no response is
77  *  received within the instance ID expiration interval or any other failure the
78  *  response handler is invoked with the empty response.
79  *
80  * @tparam RequestInterface - Request class type
81  */
82 template <class RequestInterface>
83 class Handler
84 {
85 
86   public:
87     Handler() = delete;
88     Handler(const Handler&) = delete;
89     Handler(Handler&&) = delete;
90     Handler& operator=(const Handler&) = delete;
91     Handler& operator=(Handler&&) = delete;
92     ~Handler() = default;
93 
94     /** @brief Constructor
95      *
96      *  @param[in] fd - fd of MCTP communications socket
97      *  @param[in] event - reference to PLDM daemon's main event loop
98      *  @param[in] requester - reference to Requester object
99      *  @param[in] instanceIdExpiryInterval - instance ID expiration interval
100      *  @param[in] numRetries - number of request retries
101      *  @param[in] responseTimeOut - time to wait between each retry
102      */
103     explicit Handler(
104         int fd, Event& event, Requester& requester,
105         seconds instanceIdExpiryInterval =
106             seconds(INSTANCE_ID_EXPIRATION_INTERVAL),
107         uint8_t numRetries = static_cast<uint8_t>(NUMBER_OF_REQUEST_RETRIES),
108         milliseconds responseTimeOut = milliseconds(RESPONSE_TIME_OUT)) :
109         fd(fd),
110         event(event), requester(requester),
111         instanceIdExpiryInterval(instanceIdExpiryInterval),
112         numRetries(numRetries), responseTimeOut(responseTimeOut)
113     {}
114 
115     /** @brief Register a PLDM request message
116      *
117      *  @param[in] eid - endpoint ID of the remote MCTP endpoint
118      *  @param[in] instanceId - instance ID to match request and response
119      *  @param[in] type - PLDM type
120      *  @param[in] command - PLDM command
121      *  @param[in] requestMsg - PLDM request message
122      *  @param[in] responseHandler - Response handler for this request
123      *
124      *  @return return PLDM_SUCCESS on success and PLDM_ERROR otherwise
125      */
126     int registerRequest(mctp_eid_t eid, uint8_t instanceId, uint8_t type,
127                         uint8_t command, pldm::Request&& requestMsg,
128                         ResponseHandler&& responseHandler)
129     {
130         RequestKey key{eid, instanceId, type, command};
131 
132         auto instanceIdExpiryCallBack = [key, this](void) {
133             if (this->handlers.contains(key))
134             {
135                 std::cerr << "Response not received for the request, instance "
136                              "ID expired."
137                           << " EID = " << key.eid
138                           << " INSTANCE_ID = " << key.instanceId
139                           << " TYPE = " << key.type
140                           << " COMMAND = " << key.command << "\n";
141                 auto& [request, responseHandler, timerInstance] =
142                     this->handlers[key];
143                 request->stop();
144                 auto rc = timerInstance->stop();
145                 if (rc)
146                 {
147                     std::cerr
148                         << "Failed to stop the instance ID expiry timer. RC = "
149                         << rc << "\n";
150                 }
151                 // Call response handler with an empty response to indicate no
152                 // response
153                 responseHandler(key.eid, nullptr, 0);
154                 this->removeRequestContainer.emplace(
155                     key, std::make_unique<sdeventplus::source::Defer>(
156                              event, std::bind(&Handler::removeRequestEntry,
157                                               this, key)));
158             }
159             else
160             {
161                 // This condition is not possible, if a response is received
162                 // before the instance ID expiry, then the response handler
163                 // is executed and the entry will be removed.
164                 assert(false);
165             }
166         };
167 
168         auto request = std::make_unique<RequestInterface>(
169             fd, eid, event, std::move(requestMsg), numRetries, responseTimeOut);
170         auto timer = std::make_unique<phosphor::Timer>(
171             event.get(), instanceIdExpiryCallBack);
172 
173         auto rc = request->start();
174         if (rc != PLDM_SUCCESS)
175         {
176             std::cerr << "Failure to send the PLDM request message"
177                       << "\n";
178             return rc;
179         }
180 
181         try
182         {
183             timer->start(duration_cast<microseconds>(instanceIdExpiryInterval));
184         }
185         catch (const std::runtime_error& e)
186         {
187             std::cerr << "Failed to start the instance ID expiry timer. RC = "
188                       << e.what() << "\n";
189             return PLDM_ERROR;
190         }
191 
192         handlers.emplace(key, std::make_tuple(std::move(request),
193                                               std::move(responseHandler),
194                                               std::move(timer)));
195         return rc;
196     }
197 
198     /** @brief Handle PLDM response message
199      *
200      *  @param[in] eid - endpoint ID of the remote MCTP endpoint
201      *  @param[in] instanceId - instance ID to match request and response
202      *  @param[in] type - PLDM type
203      *  @param[in] command - PLDM command
204      *  @param[in] response - PLDM response message
205      *  @param[in] respMsgLen - length of the response message
206      */
207     void handleResponse(mctp_eid_t eid, uint8_t instanceId, uint8_t type,
208                         uint8_t command, const pldm_msg* response,
209                         size_t respMsgLen)
210     {
211         RequestKey key{eid, instanceId, type, command};
212         if (handlers.contains(key))
213         {
214             auto& [request, responseHandler, timerInstance] = handlers[key];
215             request->stop();
216             auto rc = timerInstance->stop();
217             if (rc)
218             {
219                 std::cerr
220                     << "Failed to stop the instance ID expiry timer. RC = "
221                     << rc << "\n";
222             }
223             responseHandler(eid, response, respMsgLen);
224             requester.markFree(key.eid, key.instanceId);
225             handlers.erase(key);
226         }
227         else
228         {
229             // Got a response for a PLDM request message not registered with the
230             // request handler, so freeing up the instance ID, this can be other
231             // OpenBMC applications relying on PLDM D-Bus apis like
232             // openpower-occ-control and softoff
233             requester.markFree(key.eid, key.instanceId);
234         }
235     }
236 
237   private:
238     int fd;               //!< file descriptor of MCTP communications socket
239     Event& event;         //!< reference to PLDM daemon's main event loop
240     Requester& requester; //!< reference to Requester object
241     seconds instanceIdExpiryInterval; //!< Instance ID expiration interval
242     uint8_t numRetries;               //!< number of request retries
243     milliseconds responseTimeOut;     //!< time to wait between each retry
244 
245     /** @brief Container for storing the details of the PLDM request message,
246      *         handler for the corresponding PLDM response and the timer object
247      *         for the Instance ID expiration
248      */
249     using RequestValue = std::tuple<std::unique_ptr<RequestInterface>,
250                                     ResponseHandler, std::unique_ptr<Timer>>;
251 
252     /** @brief Container for storing the PLDM request entries */
253     std::unordered_map<RequestKey, RequestValue, RequestKeyHasher> handlers;
254 
255     /** @brief Container to store information about the request entries to be
256      *         removed after the instance ID timer expires
257      */
258     std::unordered_map<RequestKey, std::unique_ptr<Defer>, RequestKeyHasher>
259         removeRequestContainer;
260 
261     /** @brief Remove request entry for which the instance ID expired
262      *
263      *  @param[in] key - key for the Request
264      */
265     void removeRequestEntry(RequestKey key)
266     {
267         if (removeRequestContainer.contains(key))
268         {
269             removeRequestContainer[key].reset();
270             requester.markFree(key.eid, key.instanceId);
271             handlers.erase(key);
272             removeRequestContainer.erase(key);
273         }
274     }
275 };
276 
277 } // namespace requester
278 
279 } // namespace pldm
280