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