xref: /openbmc/pldm/requester/handler.hpp (revision 7d852862)
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/async.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 <deque>
20 #include <functional>
21 #include <memory>
22 #include <mutex>
23 #include <queue>
24 #include <tuple>
25 #include <unordered_map>
26 
27 PHOSPHOR_LOG2_USING;
28 
29 namespace pldm
30 {
31 namespace requester
32 {
33 /** @struct RequestKey
34  *
35  *  RequestKey uniquely identifies the PLDM request message to match it with the
36  *  response and a combination of MCTP endpoint ID, PLDM instance ID, PLDM type
37  *  and PLDM command is the key.
38  */
39 struct RequestKey
40 {
41     mctp_eid_t eid;     //!< MCTP endpoint ID
42     uint8_t instanceId; //!< PLDM instance ID
43     uint8_t type;       //!< PLDM type
44     uint8_t command;    //!< PLDM command
45 
46     bool operator==(const RequestKey& e) const
47     {
48         return ((eid == e.eid) && (instanceId == e.instanceId) &&
49                 (type == e.type) && (command == e.command));
50     }
51 };
52 
53 /** @struct RequestKeyHasher
54  *
55  *  This is a simple hash function, since the instance ID generator API
56  *  generates unique instance IDs for MCTP endpoint ID.
57  */
58 struct RequestKeyHasher
59 {
60     std::size_t operator()(const RequestKey& key) const
61     {
62         return (key.eid << 24 | key.instanceId << 16 | key.type << 8 |
63                 key.command);
64     }
65 };
66 
67 using ResponseHandler = std::function<void(
68     mctp_eid_t eid, const pldm_msg* response, size_t respMsgLen)>;
69 
70 /** @brief The response from SendRecvMsg with coroutine API
71  *
72  *  The response when registers PLDM request message using the SendRecvMsg
73  *  with coroutine API.
74  *  Responded tuple includes <CompleteCode, ResponseMgs, ResponseMsgLength>
75  *  Value: [PLDM_ERROR, _, _] if registerRequest fails.
76  *         [PLDM_ERROR_NOT_READY, nullptr, 0] if timed out.
77  *         [PLDM_SUCCESS, ResponseMsg, ResponseMsgLength] if succeeded
78  */
79 using SendRecvCoResp = std::tuple<int, const pldm_msg*, size_t>;
80 
81 /** @struct RegisteredRequest
82  *
83  *  This struct is used to store the registered request to one endpoint.
84  */
85 struct RegisteredRequest
86 {
87     RequestKey key;                  //!< Responder MCTP endpoint ID
88     std::vector<uint8_t> reqMsg;     //!< Request messages queue
89     ResponseHandler responseHandler; //!< Waiting for response flag
90 };
91 
92 /** @struct EndpointMessageQueue
93  *
94  *  This struct is used to save the list of request messages of one endpoint and
95  *  the existing of the request message to the endpoint with its' EID.
96  */
97 struct EndpointMessageQueue
98 {
99     mctp_eid_t eid; //!< Responder MCTP endpoint ID
100     std::deque<std::shared_ptr<RegisteredRequest>> requestQueue; //!< Queue
101     bool activeRequest; //!< Waiting for response flag
102 
103     bool operator==(const mctp_eid_t& mctpEid) const
104     {
105         return (eid == mctpEid);
106     }
107 };
108 
109 /** @class Handler
110  *
111  *  This class handles the lifecycle of the PLDM request message based on the
112  *  instance ID expiration interval, number of request retries and the timeout
113  *  waiting for a response. The registered response handlers are invoked with
114  *  response once the PLDM responder sends the response. If no response is
115  *  received within the instance ID expiration interval or any other failure the
116  *  response handler is invoked with the empty response.
117  *
118  * @tparam RequestInterface - Request class type
119  */
120 template <class RequestInterface>
121 class Handler
122 {
123   public:
124     Handler() = delete;
125     Handler(const Handler&) = delete;
126     Handler(Handler&&) = delete;
127     Handler& operator=(const Handler&) = delete;
128     Handler& operator=(Handler&&) = delete;
129     ~Handler() = default;
130 
131     /** @brief Constructor
132      *
133      *  @param[in] pldm_transport - PLDM requester
134      *  @param[in] event - reference to PLDM daemon's main event loop
135      *  @param[in] instanceIdDb - reference to an InstanceIdDb
136      *  @param[in] verbose - verbose tracing flag
137      *  @param[in] instanceIdExpiryInterval - instance ID expiration interval
138      *  @param[in] numRetries - number of request retries
139      *  @param[in] responseTimeOut - time to wait between each retry
140      */
141     explicit Handler(
142         PldmTransport* pldmTransport, sdeventplus::Event& event,
143         pldm::InstanceIdDb& instanceIdDb, bool verbose,
144         std::chrono::seconds instanceIdExpiryInterval =
145             std::chrono::seconds(INSTANCE_ID_EXPIRATION_INTERVAL),
146         uint8_t numRetries = static_cast<uint8_t>(NUMBER_OF_REQUEST_RETRIES),
147         std::chrono::milliseconds responseTimeOut =
148             std::chrono::milliseconds(RESPONSE_TIME_OUT)) :
149         pldmTransport(pldmTransport),
150         event(event), instanceIdDb(instanceIdDb), verbose(verbose),
151         instanceIdExpiryInterval(instanceIdExpiryInterval),
152         numRetries(numRetries), responseTimeOut(responseTimeOut)
153     {}
154 
155     void instanceIdExpiryCallBack(RequestKey key)
156     {
157         auto eid = key.eid;
158         if (this->handlers.contains(key))
159         {
160             info(
161                 "Instance ID expiry for EID '{EID}' using InstanceID '{INSTANCEID}'",
162                 "EID", key.eid, "INSTANCEID", key.instanceId);
163             auto& [request, responseHandler,
164                    timerInstance] = this->handlers[key];
165             request->stop();
166             auto rc = timerInstance->stop();
167             if (rc)
168             {
169                 error(
170                     "Failed to stop the instance ID expiry timer, response code '{RC}'",
171                     "RC", rc);
172             }
173             // Call response handler with an empty response to indicate no
174             // response
175             responseHandler(eid, nullptr, 0);
176             this->removeRequestContainer.emplace(
177                 key,
178                 std::make_unique<sdeventplus::source::Defer>(
179                     event, std::bind(&Handler::removeRequestEntry, this, key)));
180             endpointMessageQueues[eid]->activeRequest = false;
181 
182             /* try to send new request if the endpoint is free */
183             pollEndpointQueue(eid);
184         }
185         else
186         {
187             // This condition is not possible, if a response is received
188             // before the instance ID expiry, then the response handler
189             // is executed and the entry will be removed.
190             assert(false);
191         }
192     }
193 
194     /** @brief Send the remaining PLDM request messages in endpoint queue
195      *
196      *  @param[in] eid - endpoint ID of the remote MCTP endpoint
197      */
198     int pollEndpointQueue(mctp_eid_t eid)
199     {
200         if (endpointMessageQueues[eid]->activeRequest ||
201             endpointMessageQueues[eid]->requestQueue.empty())
202         {
203             return PLDM_SUCCESS;
204         }
205 
206         endpointMessageQueues[eid]->activeRequest = true;
207         auto requestMsg = endpointMessageQueues[eid]->requestQueue.front();
208         endpointMessageQueues[eid]->requestQueue.pop_front();
209 
210         auto request = std::make_unique<RequestInterface>(
211             pldmTransport, requestMsg->key.eid, event,
212             std::move(requestMsg->reqMsg), numRetries, responseTimeOut,
213             verbose);
214         auto timer = std::make_unique<sdbusplus::Timer>(
215             event.get(), std::bind(&Handler::instanceIdExpiryCallBack, this,
216                                    requestMsg->key));
217 
218         auto rc = request->start();
219         if (rc)
220         {
221             instanceIdDb.free(requestMsg->key.eid, requestMsg->key.instanceId);
222             error(
223                 "Failure to send the PLDM request message for polling endpoint queue, response code '{RC}'",
224                 "RC", rc);
225             endpointMessageQueues[eid]->activeRequest = false;
226             return rc;
227         }
228 
229         try
230         {
231             timer->start(duration_cast<std::chrono::microseconds>(
232                 instanceIdExpiryInterval));
233         }
234         catch (const std::runtime_error& e)
235         {
236             instanceIdDb.free(requestMsg->key.eid, requestMsg->key.instanceId);
237             error(
238                 "Failed to start the instance ID expiry timer, error - {ERROR}",
239                 "ERROR", e);
240             endpointMessageQueues[eid]->activeRequest = false;
241             return PLDM_ERROR;
242         }
243 
244         handlers.emplace(requestMsg->key,
245                          std::make_tuple(std::move(request),
246                                          std::move(requestMsg->responseHandler),
247                                          std::move(timer)));
248         return PLDM_SUCCESS;
249     }
250 
251     /** @brief Register a PLDM request message
252      *
253      *  @param[in] eid - endpoint ID of the remote MCTP endpoint
254      *  @param[in] instanceId - instance ID to match request and response
255      *  @param[in] type - PLDM type
256      *  @param[in] command - PLDM command
257      *  @param[in] requestMsg - PLDM request message
258      *  @param[in] responseHandler - Response handler for this request
259      *
260      *  @return return PLDM_SUCCESS on success and PLDM_ERROR otherwise
261      */
262     int registerRequest(mctp_eid_t eid, uint8_t instanceId, uint8_t type,
263                         uint8_t command, pldm::Request&& requestMsg,
264                         ResponseHandler&& responseHandler)
265     {
266         RequestKey key{eid, instanceId, type, command};
267 
268         if (handlers.contains(key))
269         {
270             error(
271                 "Register request for EID '{EID}' is using InstanceID '{INSTANCEID}'",
272                 "EID", eid, "INSTANCEID", instanceId);
273             return PLDM_ERROR;
274         }
275 
276         auto inputRequest = std::make_shared<RegisteredRequest>(
277             key, std::move(requestMsg), std::move(responseHandler));
278         if (endpointMessageQueues.contains(eid))
279         {
280             endpointMessageQueues[eid]->requestQueue.push_back(inputRequest);
281         }
282         else
283         {
284             std::deque<std::shared_ptr<RegisteredRequest>> reqQueue;
285             reqQueue.push_back(inputRequest);
286             endpointMessageQueues[eid] =
287                 std::make_shared<EndpointMessageQueue>(eid, reqQueue, false);
288         }
289 
290         /* try to send new request if the endpoint is free */
291         pollEndpointQueue(eid);
292 
293         return PLDM_SUCCESS;
294     }
295 
296     /** @brief Unregister a PLDM request message
297      *
298      *  @param[in] eid - endpoint ID of the remote MCTP endpoint
299      *  @param[in] instanceId - instance ID to match request and response
300      *  @param[in] type - PLDM type
301      *  @param[in] command - PLDM command
302      *
303      *  @return return PLDM_SUCCESS on success and PLDM_ERROR otherwise
304      */
305     int unregisterRequest(mctp_eid_t eid, uint8_t instanceId, uint8_t type,
306                           uint8_t command)
307     {
308         RequestKey key{eid, instanceId, type, command};
309 
310         /* handlers only contain key when the message is already sent */
311         if (handlers.contains(key))
312         {
313             auto& [request, responseHandler, timerInstance] = handlers[key];
314             request->stop();
315             auto rc = timerInstance->stop();
316             if (rc)
317             {
318                 error(
319                     "Failed to stop the instance ID expiry timer, response code '{RC}'",
320                     "RC", static_cast<int>(rc));
321             }
322 
323             instanceIdDb.free(key.eid, key.instanceId);
324             handlers.erase(key);
325             endpointMessageQueues[eid]->activeRequest = false;
326             /* try to send new request if the endpoint is free */
327             pollEndpointQueue(eid);
328 
329             return PLDM_SUCCESS;
330         }
331         else
332         {
333             if (!endpointMessageQueues.contains(eid))
334             {
335                 error(
336                     "Can't find request for EID '{EID}' is using InstanceID '{INSTANCEID}' in Endpoint message Queue",
337                     "EID", (unsigned)eid, "INSTANCEID", (unsigned)instanceId);
338                 return PLDM_ERROR;
339             }
340             auto requestMsg = endpointMessageQueues[eid]->requestQueue;
341             /* Find the registered request in the requestQueue */
342             for (auto it = requestMsg.begin(); it != requestMsg.end();)
343             {
344                 auto msg = *it;
345                 if (msg->key == key)
346                 {
347                     // erase and get the next valid iterator
348                     it = endpointMessageQueues[eid]->requestQueue.erase(it);
349                     instanceIdDb.free(key.eid, key.instanceId);
350                     return PLDM_SUCCESS;
351                 }
352                 else
353                 {
354                     ++it; // increment iterator only if not erasing
355                 }
356             }
357         }
358 
359         return PLDM_ERROR;
360     }
361 
362     /** @brief Handle PLDM response message
363      *
364      *  @param[in] eid - endpoint ID of the remote MCTP endpoint
365      *  @param[in] instanceId - instance ID to match request and response
366      *  @param[in] type - PLDM type
367      *  @param[in] command - PLDM command
368      *  @param[in] response - PLDM response message
369      *  @param[in] respMsgLen - length of the response message
370      */
371     void handleResponse(mctp_eid_t eid, uint8_t instanceId, uint8_t type,
372                         uint8_t command, const pldm_msg* response,
373                         size_t respMsgLen)
374     {
375         RequestKey key{eid, instanceId, type, command};
376         if (handlers.contains(key))
377         {
378             auto& [request, responseHandler, timerInstance] = handlers[key];
379             request->stop();
380             auto rc = timerInstance->stop();
381             if (rc)
382             {
383                 error(
384                     "Failed to stop the instance ID expiry timer, response code '{RC}'",
385                     "RC", rc);
386             }
387             responseHandler(eid, response, respMsgLen);
388             instanceIdDb.free(key.eid, key.instanceId);
389             handlers.erase(key);
390 
391             endpointMessageQueues[eid]->activeRequest = false;
392             /* try to send new request if the endpoint is free */
393             pollEndpointQueue(eid);
394         }
395         else
396         {
397             // Got a response for a PLDM request message not registered with the
398             // request handler, so freeing up the instance ID, this can be other
399             // OpenBMC applications relying on PLDM D-Bus apis like
400             // openpower-occ-control and softoff
401             instanceIdDb.free(key.eid, key.instanceId);
402         }
403     }
404 
405     /** @brief Wrap registerRequest with coroutine API.
406      *
407      *  @return Return [PLDM_ERROR, _, _] if registerRequest fails.
408      *          Return [PLDM_ERROR_NOT_READY, nullptr, 0] if timed out.
409      *          Return [PLDM_SUCCESS, resp, len] if succeeded
410      */
411     stdexec::sender_of<stdexec::set_value_t(SendRecvCoResp)> auto
412         sendRecvMsg(mctp_eid_t eid, pldm::Request&& request);
413 
414   private:
415     PldmTransport* pldmTransport; //!< PLDM transport object
416     sdeventplus::Event& event; //!< reference to PLDM daemon's main event loop
417     pldm::InstanceIdDb& instanceIdDb; //!< reference to an InstanceIdDb
418     bool verbose;                     //!< verbose tracing flag
419     std::chrono::seconds
420         instanceIdExpiryInterval;     //!< Instance ID expiration interval
421     uint8_t numRetries;               //!< number of request retries
422     std::chrono::milliseconds
423         responseTimeOut;              //!< time to wait between each retry
424 
425     /** @brief Container for storing the details of the PLDM request
426      *         message, handler for the corresponding PLDM response and the
427      *         timer object for the Instance ID expiration
428      */
429     using RequestValue =
430         std::tuple<std::unique_ptr<RequestInterface>, ResponseHandler,
431                    std::unique_ptr<sdbusplus::Timer>>;
432 
433     // Manage the requests of responders base on MCTP EID
434     std::map<mctp_eid_t, std::shared_ptr<EndpointMessageQueue>>
435         endpointMessageQueues;
436 
437     /** @brief Container for storing the PLDM request entries */
438     std::unordered_map<RequestKey, RequestValue, RequestKeyHasher> handlers;
439 
440     /** @brief Container to store information about the request entries to be
441      *         removed after the instance ID timer expires
442      */
443     std::unordered_map<RequestKey, std::unique_ptr<sdeventplus::source::Defer>,
444                        RequestKeyHasher>
445         removeRequestContainer;
446 
447     /** @brief Remove request entry for which the instance ID expired
448      *
449      *  @param[in] key - key for the Request
450      */
451     void removeRequestEntry(RequestKey key)
452     {
453         if (removeRequestContainer.contains(key))
454         {
455             removeRequestContainer[key].reset();
456             instanceIdDb.free(key.eid, key.instanceId);
457             handlers.erase(key);
458             removeRequestContainer.erase(key);
459         }
460     }
461 };
462 
463 /** @class SendRecvMsgOperation
464  *
465  *  Represents the state and logic for a single send/receive message operation
466  *
467  * @tparam RequestInterface - Request class type
468  * @tparam stdexec::receiver - Execute receiver
469  */
470 template <class RequestInterface, stdexec::receiver R>
471 struct SendRecvMsgOperation
472 {
473     SendRecvMsgOperation() = delete;
474 
475     explicit SendRecvMsgOperation(Handler<RequestInterface>& handler,
476                                   mctp_eid_t eid, pldm::Request&& request,
477                                   R&& r) :
478         handler(handler),
479         request(std::move(request)), receiver(std::move(r))
480     {
481         auto requestMsg =
482             reinterpret_cast<const pldm_msg*>(this->request.data());
483         requestKey = RequestKey{
484             eid,
485             requestMsg->hdr.instance_id,
486             requestMsg->hdr.type,
487             requestMsg->hdr.command,
488         };
489         response = nullptr;
490         respMsgLen = 0;
491     }
492 
493     /** @brief Checks if the operation has been requested to stop.
494      *         If so, it sets the state to stopped.Registers the request with
495      *         the handler. If registration fails, sets an error on the
496      *         receiver. If stopping is possible, sets up a stop callback.
497      *
498      *  @param[in] op - operation request
499      *
500      *  @return Execute errors
501      */
502     friend void tag_invoke(stdexec::start_t, SendRecvMsgOperation& op) noexcept
503     {
504         auto stopToken = stdexec::get_stop_token(stdexec::get_env(op.receiver));
505 
506         // operation already cancelled
507         if (stopToken.stop_requested())
508         {
509             return stdexec::set_stopped(std::move(op.receiver));
510         }
511 
512         using namespace std::placeholders;
513         auto rc = op.handler.registerRequest(
514             op.requestKey.eid, op.requestKey.instanceId, op.requestKey.type,
515             op.requestKey.command, std::move(op.request),
516             std::bind(&SendRecvMsgOperation::onComplete, &op, _1, _2, _3));
517         if (rc)
518         {
519             return stdexec::set_value(std::move(op.receiver), rc,
520                                       static_cast<const pldm_msg*>(nullptr),
521                                       static_cast<size_t>(0));
522         }
523 
524         if (stopToken.stop_possible())
525         {
526             op.stopCallback.emplace(
527                 std::move(stopToken),
528                 std::bind(&SendRecvMsgOperation::onStop, &op));
529         }
530     }
531 
532     /** @brief Unregisters the request and sets the state to stopped on the
533      *         receiver.
534      */
535     void onStop()
536     {
537         handler.unregisterRequest(requestKey.eid, requestKey.instanceId,
538                                   requestKey.type, requestKey.command);
539         return stdexec::set_stopped(std::move(receiver));
540     }
541 
542     /** @brief This function resets the stop callback. Validates the response
543      *         and sets either an error or a value on the receiver.
544      *
545      *  @param[in] eid - endpoint ID of the remote MCTP endpoint
546      *  @param[in] response - PLDM response message
547      *  @param[in] respMsgLen - length of the response message
548      *
549      *  @return PLDM completion code
550      */
551     void onComplete(mctp_eid_t eid, const pldm_msg* response, size_t respMsgLen)
552     {
553         stopCallback.reset();
554         assert(eid == this->requestKey.eid);
555         auto rc = PLDM_SUCCESS;
556         if (!response && !respMsgLen)
557         {
558             rc = PLDM_ERROR_NOT_READY;
559         }
560         return stdexec::set_value(std::move(receiver), static_cast<int>(rc),
561                                   response, respMsgLen);
562     }
563 
564   private:
565     /** @brief Reference to a Handler object that manages the request/response
566      *         logic.
567      */
568     requester::Handler<RequestInterface>& handler;
569 
570     /** @brief Stores information about the request such as eid, instanceId,
571      *         type, and command.
572      */
573     RequestKey requestKey;
574 
575     /** @brief The request message to be sent.
576      */
577     pldm::Request request;
578 
579     /** @brief The response message for the sent request message.
580      */
581     const pldm_msg* response;
582 
583     /** @brief The length of response message for the sent request message.
584      */
585     size_t respMsgLen;
586 
587     /** @brief The receiver to be notified with the result of the operation.
588      */
589     R receiver;
590 
591     /** @brief An optional callback that handles stopping the operation if
592      *         requested.
593      */
594     std::optional<typename stdexec::stop_token_of_t<
595         stdexec::env_of_t<R>>::template callback_type<std::function<void()>>>
596         stopCallback = std::nullopt;
597 };
598 
599 /** @class SendRecvMsgSender
600  *
601  *  Represents the single message sender
602  *
603  * @tparam RequestInterface - Request class type
604  */
605 template <class RequestInterface>
606 struct SendRecvMsgSender
607 {
608     using is_sender = void;
609 
610     SendRecvMsgSender() = delete;
611 
612     explicit SendRecvMsgSender(requester::Handler<RequestInterface>& handler,
613                                mctp_eid_t eid, pldm::Request&& request) :
614         handler(handler),
615         eid(eid), request(std::move(request))
616     {}
617 
618     friend auto tag_invoke(stdexec::get_completion_signatures_t,
619                            const SendRecvMsgSender&, auto)
620         -> stdexec::completion_signatures<
621             stdexec::set_value_t(int, const pldm_msg*, size_t),
622             stdexec::set_stopped_t()>;
623 
624     /** @brief Execute the sending the request message */
625     template <stdexec::receiver R>
626     friend auto tag_invoke(stdexec::connect_t, SendRecvMsgSender&& self, R r)
627     {
628         return SendRecvMsgOperation<RequestInterface, R>(
629             self.handler, self.eid, std::move(self.request), std::move(r));
630     }
631 
632   private:
633     /** @brief Reference to a Handler object that manages the request/response
634      *         logic.
635      */
636     requester::Handler<RequestInterface>& handler;
637 
638     /** @brief MCTP Endpoint ID of request message */
639     mctp_eid_t eid;
640 
641     /** @brief Request message */
642     pldm::Request request;
643 };
644 
645 /** @brief Wrap registerRequest with coroutine API.
646  *
647  *  @param[in] eid - endpoint ID of the remote MCTP endpoint
648  *  @param[in] request - PLDM request message
649  *
650  *  @return Return [PLDM_ERROR, _, _] if registerRequest fails.
651  *          Return [PLDM_ERROR_NOT_READY, nullptr, 0] if timed out.
652  *          Return [PLDM_SUCCESS, resp, len] if succeeded
653  */
654 template <class RequestInterface>
655 stdexec::sender_of<stdexec::set_value_t(SendRecvCoResp)> auto
656     Handler<RequestInterface>::sendRecvMsg(mctp_eid_t eid,
657                                            pldm::Request&& request)
658 {
659     return SendRecvMsgSender(*this, eid, std::move(request)) |
660            stdexec::then([](int rc, const pldm_msg* resp, size_t respLen) {
661         return std::make_tuple(rc, resp, respLen);
662     });
663 }
664 
665 } // namespace requester
666 
667 } // namespace pldm
668