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