xref: /openbmc/pldm/requester/handler.hpp (revision b4ef4310)
1 #pragma once
2 
3 #include "common/instance_id.hpp"
4 #include "common/transport.hpp"
5 #include "common/types.hpp"
6 #include "request.hpp"
7 
8 #include <libpldm/base.h>
9 #include <sys/socket.h>
10 
11 #include <phosphor-logging/lg2.hpp>
12 #include <sdbusplus/timer.hpp>
13 #include <sdeventplus/event.hpp>
14 #include <sdeventplus/source/event.hpp>
15 
16 #include <cassert>
17 #include <chrono>
18 #include <deque>
19 #include <functional>
20 #include <memory>
21 #include <mutex>
22 #include <queue>
23 #include <tuple>
24 #include <unordered_map>
25 
26 PHOSPHOR_LOG2_USING;
27 
28 namespace pldm
29 {
30 namespace requester
31 {
32 /** @struct RequestKey
33  *
34  *  RequestKey uniquely identifies the PLDM request message to match it with the
35  *  response and a combination of MCTP endpoint ID, PLDM instance ID, PLDM type
36  *  and PLDM command is the key.
37  */
38 struct RequestKey
39 {
40     mctp_eid_t eid;     //!< MCTP endpoint ID
41     uint8_t instanceId; //!< PLDM instance ID
42     uint8_t type;       //!< PLDM type
43     uint8_t command;    //!< PLDM command
44 
45     bool operator==(const RequestKey& e) const
46     {
47         return ((eid == e.eid) && (instanceId == e.instanceId) &&
48                 (type == e.type) && (command == e.command));
49     }
50 };
51 
52 /** @struct RequestKeyHasher
53  *
54  *  This is a simple hash function, since the instance ID generator API
55  *  generates unique instance IDs for MCTP endpoint ID.
56  */
57 struct RequestKeyHasher
58 {
59     std::size_t operator()(const RequestKey& key) const
60     {
61         return (key.eid << 24 | key.instanceId << 16 | key.type << 8 |
62                 key.command);
63     }
64 };
65 
66 using ResponseHandler = std::function<void(
67     mctp_eid_t eid, const pldm_msg* response, size_t respMsgLen)>;
68 
69 /** @struct RegisteredRequest
70  *
71  *  This struct is used to store the registered request to one endpoint.
72  */
73 struct RegisteredRequest
74 {
75     RequestKey key;                  //!< Responder MCTP endpoint ID
76     std::vector<uint8_t> reqMsg;     //!< Request messages queue
77     ResponseHandler responseHandler; //!< Waiting for response flag
78 };
79 
80 /** @struct EndpointMessageQueue
81  *
82  *  This struct is used to save the list of request messages of one endpoint and
83  *  the existing of the request message to the endpoint with its' EID.
84  */
85 struct EndpointMessageQueue
86 {
87     mctp_eid_t eid; //!< Responder MCTP endpoint ID
88     std::deque<std::shared_ptr<RegisteredRequest>> requestQueue; //!< Queue
89     bool activeRequest; //!< Waiting for response flag
90 
91     bool operator==(const mctp_eid_t& mctpEid) const
92     {
93         return (eid == mctpEid);
94     }
95 };
96 
97 /** @class Handler
98  *
99  *  This class handles the lifecycle of the PLDM request message based on the
100  *  instance ID expiration interval, number of request retries and the timeout
101  *  waiting for a response. The registered response handlers are invoked with
102  *  response once the PLDM responder sends the response. If no response is
103  *  received within the instance ID expiration interval or any other failure the
104  *  response handler is invoked with the empty response.
105  *
106  * @tparam RequestInterface - Request class type
107  */
108 template <class RequestInterface>
109 class Handler
110 {
111   public:
112     Handler() = delete;
113     Handler(const Handler&) = delete;
114     Handler(Handler&&) = delete;
115     Handler& operator=(const Handler&) = delete;
116     Handler& operator=(Handler&&) = delete;
117     ~Handler() = default;
118 
119     /** @brief Constructor
120      *
121      *  @param[in] pldm_transport - PLDM requester
122      *  @param[in] event - reference to PLDM daemon's main event loop
123      *  @param[in] instanceIdDb - reference to an InstanceIdDb
124      *  @param[in] verbose - verbose tracing flag
125      *  @param[in] instanceIdExpiryInterval - instance ID expiration interval
126      *  @param[in] numRetries - number of request retries
127      *  @param[in] responseTimeOut - time to wait between each retry
128      */
129     explicit Handler(
130         PldmTransport* pldmTransport, sdeventplus::Event& event,
131         pldm::InstanceIdDb& instanceIdDb, bool verbose,
132         std::chrono::seconds instanceIdExpiryInterval =
133             std::chrono::seconds(INSTANCE_ID_EXPIRATION_INTERVAL),
134         uint8_t numRetries = static_cast<uint8_t>(NUMBER_OF_REQUEST_RETRIES),
135         std::chrono::milliseconds responseTimeOut =
136             std::chrono::milliseconds(RESPONSE_TIME_OUT)) :
137         pldmTransport(pldmTransport),
138         event(event), instanceIdDb(instanceIdDb), verbose(verbose),
139         instanceIdExpiryInterval(instanceIdExpiryInterval),
140         numRetries(numRetries), responseTimeOut(responseTimeOut)
141     {}
142 
143     void instanceIdExpiryCallBack(RequestKey key)
144     {
145         auto eid = key.eid;
146         if (this->handlers.contains(key))
147         {
148             error("The eid:InstanceID {EID}:{IID} is using.", "EID",
149                   (unsigned)key.eid, "IID", (unsigned)key.instanceId);
150             auto& [request, responseHandler,
151                    timerInstance] = this->handlers[key];
152             request->stop();
153             auto rc = timerInstance->stop();
154             if (rc)
155             {
156                 error("Failed to stop the instance ID expiry timer. RC = {RC}",
157                       "RC", static_cast<int>(rc));
158             }
159             // Call response handler with an empty response to indicate no
160             // response
161             responseHandler(eid, nullptr, 0);
162             this->removeRequestContainer.emplace(
163                 key,
164                 std::make_unique<sdeventplus::source::Defer>(
165                     event, std::bind(&Handler::removeRequestEntry, this, key)));
166             endpointMessageQueues[eid]->activeRequest = false;
167 
168             /* try to send new request if the endpoint is free */
169             pollEndpointQueue(eid);
170         }
171         else
172         {
173             // This condition is not possible, if a response is received
174             // before the instance ID expiry, then the response handler
175             // is executed and the entry will be removed.
176             assert(false);
177         }
178     }
179 
180     /** @brief Send the remaining PLDM request messages in endpoint queue
181      *
182      *  @param[in] eid - endpoint ID of the remote MCTP endpoint
183      */
184     int pollEndpointQueue(mctp_eid_t eid)
185     {
186         if (endpointMessageQueues[eid]->activeRequest ||
187             endpointMessageQueues[eid]->requestQueue.empty())
188         {
189             return PLDM_SUCCESS;
190         }
191 
192         endpointMessageQueues[eid]->activeRequest = true;
193         auto requestMsg = endpointMessageQueues[eid]->requestQueue.front();
194         endpointMessageQueues[eid]->requestQueue.pop_front();
195 
196         auto request = std::make_unique<RequestInterface>(
197             pldmTransport, requestMsg->key.eid, event,
198             std::move(requestMsg->reqMsg), numRetries, responseTimeOut,
199             verbose);
200         auto timer = std::make_unique<sdbusplus::Timer>(
201             event.get(), std::bind(&Handler::instanceIdExpiryCallBack, this,
202                                    requestMsg->key));
203 
204         auto rc = request->start();
205         if (rc)
206         {
207             instanceIdDb.free(requestMsg->key.eid, requestMsg->key.instanceId);
208             error("Failure to send the PLDM request message");
209             endpointMessageQueues[eid]->activeRequest = false;
210             return rc;
211         }
212 
213         try
214         {
215             timer->start(duration_cast<std::chrono::microseconds>(
216                 instanceIdExpiryInterval));
217         }
218         catch (const std::runtime_error& e)
219         {
220             instanceIdDb.free(requestMsg->key.eid, requestMsg->key.instanceId);
221             error(
222                 "Failed to start the instance ID expiry timer. RC = {ERR_EXCEP}",
223                 "ERR_EXCEP", e.what());
224             endpointMessageQueues[eid]->activeRequest = false;
225             return PLDM_ERROR;
226         }
227 
228         handlers.emplace(requestMsg->key,
229                          std::make_tuple(std::move(request),
230                                          std::move(requestMsg->responseHandler),
231                                          std::move(timer)));
232         return PLDM_SUCCESS;
233     }
234 
235     /** @brief Register a PLDM request message
236      *
237      *  @param[in] eid - endpoint ID of the remote MCTP endpoint
238      *  @param[in] instanceId - instance ID to match request and response
239      *  @param[in] type - PLDM type
240      *  @param[in] command - PLDM command
241      *  @param[in] requestMsg - PLDM request message
242      *  @param[in] responseHandler - Response handler for this request
243      *
244      *  @return return PLDM_SUCCESS on success and PLDM_ERROR otherwise
245      */
246     int registerRequest(mctp_eid_t eid, uint8_t instanceId, uint8_t type,
247                         uint8_t command, pldm::Request&& requestMsg,
248                         ResponseHandler&& responseHandler)
249     {
250         RequestKey key{eid, instanceId, type, command};
251 
252         if (handlers.contains(key))
253         {
254             error("The eid:InstanceID {EID}:{IID} is using.", "EID",
255                   (unsigned)eid, "IID", (unsigned)instanceId);
256             return PLDM_ERROR;
257         }
258 
259         auto inputRequest = std::make_shared<RegisteredRequest>(
260             key, std::move(requestMsg), std::move(responseHandler));
261         if (endpointMessageQueues.contains(eid))
262         {
263             endpointMessageQueues[eid]->requestQueue.push_back(inputRequest);
264         }
265         else
266         {
267             std::deque<std::shared_ptr<RegisteredRequest>> reqQueue;
268             reqQueue.push_back(inputRequest);
269             endpointMessageQueues[eid] =
270                 std::make_shared<EndpointMessageQueue>(eid, reqQueue, false);
271         }
272 
273         /* try to send new request if the endpoint is free */
274         pollEndpointQueue(eid);
275 
276         return PLDM_SUCCESS;
277     }
278 
279     /** @brief Handle PLDM response message
280      *
281      *  @param[in] eid - endpoint ID of the remote MCTP endpoint
282      *  @param[in] instanceId - instance ID to match request and response
283      *  @param[in] type - PLDM type
284      *  @param[in] command - PLDM command
285      *  @param[in] response - PLDM response message
286      *  @param[in] respMsgLen - length of the response message
287      */
288     void handleResponse(mctp_eid_t eid, uint8_t instanceId, uint8_t type,
289                         uint8_t command, const pldm_msg* response,
290                         size_t respMsgLen)
291     {
292         RequestKey key{eid, instanceId, type, command};
293         if (handlers.contains(key))
294         {
295             auto& [request, responseHandler, timerInstance] = handlers[key];
296             request->stop();
297             auto rc = timerInstance->stop();
298             if (rc)
299             {
300                 error("Failed to stop the instance ID expiry timer. RC = {RC}",
301                       "RC", static_cast<int>(rc));
302             }
303             responseHandler(eid, response, respMsgLen);
304             instanceIdDb.free(key.eid, key.instanceId);
305             handlers.erase(key);
306 
307             endpointMessageQueues[eid]->activeRequest = false;
308             /* try to send new request if the endpoint is free */
309             pollEndpointQueue(eid);
310         }
311         else
312         {
313             // Got a response for a PLDM request message not registered with the
314             // request handler, so freeing up the instance ID, this can be other
315             // OpenBMC applications relying on PLDM D-Bus apis like
316             // openpower-occ-control and softoff
317             instanceIdDb.free(key.eid, key.instanceId);
318         }
319     }
320 
321   private:
322     PldmTransport* pldmTransport; //!< PLDM transport object
323     sdeventplus::Event& event; //!< reference to PLDM daemon's main event loop
324     pldm::InstanceIdDb& instanceIdDb; //!< reference to an InstanceIdDb
325     bool verbose;                     //!< verbose tracing flag
326     std::chrono::seconds
327         instanceIdExpiryInterval;     //!< Instance ID expiration interval
328     uint8_t numRetries;               //!< number of request retries
329     std::chrono::milliseconds
330         responseTimeOut;              //!< time to wait between each retry
331 
332     /** @brief Container for storing the details of the PLDM request
333      *         message, handler for the corresponding PLDM response and the
334      *         timer object for the Instance ID expiration
335      */
336     using RequestValue =
337         std::tuple<std::unique_ptr<RequestInterface>, ResponseHandler,
338                    std::unique_ptr<sdbusplus::Timer>>;
339 
340     // Manage the requests of responders base on MCTP EID
341     std::map<mctp_eid_t, std::shared_ptr<EndpointMessageQueue>>
342         endpointMessageQueues;
343 
344     /** @brief Container for storing the PLDM request entries */
345     std::unordered_map<RequestKey, RequestValue, RequestKeyHasher> handlers;
346 
347     /** @brief Container to store information about the request entries to be
348      *         removed after the instance ID timer expires
349      */
350     std::unordered_map<RequestKey, std::unique_ptr<sdeventplus::source::Defer>,
351                        RequestKeyHasher>
352         removeRequestContainer;
353 
354     /** @brief Remove request entry for which the instance ID expired
355      *
356      *  @param[in] key - key for the Request
357      */
358     void removeRequestEntry(RequestKey key)
359     {
360         if (removeRequestContainer.contains(key))
361         {
362             removeRequestContainer[key].reset();
363             instanceIdDb.free(key.eid, key.instanceId);
364             handlers.erase(key);
365             removeRequestContainer.erase(key);
366         }
367     }
368 };
369 
370 } // namespace requester
371 
372 } // namespace pldm
373