xref: /openbmc/pldm/requester/handler.hpp (revision 499a29dd)
1 #pragma once
2 
3 #include "config.h"
4 
5 #include "common/types.hpp"
6 #include "pldmd/instance_id.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] instanceIdDb - reference to an InstanceIdDb
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::InstanceIdDb& instanceIdDb,
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), instanceIdDb(instanceIdDb),
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         if (handlers.contains(key))
169         {
170             error("The eid:InstanceID {EID}:{IID} is using.", "EID",
171                   (unsigned)eid, "IID", (unsigned)instanceId);
172             return PLDM_ERROR;
173         }
174 
175         auto request = std::make_unique<RequestInterface>(
176             fd, eid, event, std::move(requestMsg), numRetries, responseTimeOut,
177             currentSendbuffSize, verbose);
178         auto timer = std::make_unique<phosphor::Timer>(
179             event.get(), instanceIdExpiryCallBack);
180 
181         auto rc = request->start();
182         if (rc)
183         {
184             instanceIdDb.free(eid, instanceId);
185             error("Failure to send the PLDM request message");
186             return rc;
187         }
188 
189         try
190         {
191             timer->start(duration_cast<std::chrono::microseconds>(
192                 instanceIdExpiryInterval));
193         }
194         catch (const std::runtime_error& e)
195         {
196             instanceIdDb.free(eid, instanceId);
197             error(
198                 "Failed to start the instance ID expiry timer. RC = {ERR_EXCEP}",
199                 "ERR_EXCEP", e.what());
200             return PLDM_ERROR;
201         }
202 
203         handlers.emplace(key, std::make_tuple(std::move(request),
204                                               std::move(responseHandler),
205                                               std::move(timer)));
206         return rc;
207     }
208 
209     /** @brief Handle PLDM response message
210      *
211      *  @param[in] eid - endpoint ID of the remote MCTP endpoint
212      *  @param[in] instanceId - instance ID to match request and response
213      *  @param[in] type - PLDM type
214      *  @param[in] command - PLDM command
215      *  @param[in] response - PLDM response message
216      *  @param[in] respMsgLen - length of the response message
217      */
218     void handleResponse(mctp_eid_t eid, uint8_t instanceId, uint8_t type,
219                         uint8_t command, const pldm_msg* response,
220                         size_t respMsgLen)
221     {
222         RequestKey key{eid, instanceId, type, command};
223         if (handlers.contains(key))
224         {
225             auto& [request, responseHandler, timerInstance] = handlers[key];
226             request->stop();
227             auto rc = timerInstance->stop();
228             if (rc)
229             {
230                 error("Failed to stop the instance ID expiry timer. RC = {RC}",
231                       "RC", static_cast<int>(rc));
232             }
233             responseHandler(eid, response, respMsgLen);
234             instanceIdDb.free(key.eid, key.instanceId);
235             handlers.erase(key);
236         }
237         else
238         {
239             // Got a response for a PLDM request message not registered with the
240             // request handler, so freeing up the instance ID, this can be other
241             // OpenBMC applications relying on PLDM D-Bus apis like
242             // openpower-occ-control and softoff
243             instanceIdDb.free(key.eid, key.instanceId);
244         }
245     }
246 
247   private:
248     int fd; //!< file descriptor of MCTP communications socket
249     sdeventplus::Event& event; //!< reference to PLDM daemon's main event loop
250     pldm::InstanceIdDb& instanceIdDb; //!< reference to an InstanceIdDb
251     int currentSendbuffSize;          //!< current Send Buffer size
252     bool verbose;                     //!< verbose tracing flag
253     std::chrono::seconds
254         instanceIdExpiryInterval;     //!< Instance ID expiration interval
255     uint8_t numRetries;               //!< number of request retries
256     std::chrono::milliseconds
257         responseTimeOut;              //!< time to wait between each retry
258 
259     /** @brief Container for storing the details of the PLDM request
260      *         message, handler for the corresponding PLDM response and the
261      *         timer object for the Instance ID expiration
262      */
263     using RequestValue =
264         std::tuple<std::unique_ptr<RequestInterface>, ResponseHandler,
265                    std::unique_ptr<phosphor::Timer>>;
266 
267     /** @brief Container for storing the PLDM request entries */
268     std::unordered_map<RequestKey, RequestValue, RequestKeyHasher> handlers;
269 
270     /** @brief Container to store information about the request entries to be
271      *         removed after the instance ID timer expires
272      */
273     std::unordered_map<RequestKey, std::unique_ptr<sdeventplus::source::Defer>,
274                        RequestKeyHasher>
275         removeRequestContainer;
276 
277     /** @brief Remove request entry for which the instance ID expired
278      *
279      *  @param[in] key - key for the Request
280      */
281     void removeRequestEntry(RequestKey key)
282     {
283         if (removeRequestContainer.contains(key))
284         {
285             removeRequestContainer[key].reset();
286             instanceIdDb.free(key.eid, key.instanceId);
287             handlers.erase(key);
288             removeRequestContainer.erase(key);
289         }
290     }
291 };
292 
293 } // namespace requester
294 
295 } // namespace pldm
296