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