xref: /openbmc/pldm/requester/handler.hpp (revision 70a47baf)
1 #pragma once
2 
3 #include "config.h"
4 
5 #include "libpldm/base.h"
6 #include "libpldm/requester/pldm.h"
7 
8 #include "common/types.hpp"
9 #include "pldmd/dbus_impl_requester.hpp"
10 #include "request.hpp"
11 
12 #include <function2/function2.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 <memory>
20 #include <tuple>
21 #include <unordered_map>
22 
23 namespace pldm
24 {
25 
26 namespace requester
27 {
28 
29 /** @struct RequestKey
30  *
31  *  RequestKey uniquely identifies the PLDM request message to match it with the
32  *  response and a combination of MCTP endpoint ID, PLDM instance ID, PLDM type
33  *  and PLDM command is the key.
34  */
35 struct RequestKey
36 {
37     mctp_eid_t eid;     //!< MCTP endpoint ID
38     uint8_t instanceId; //!< PLDM instance ID
39     uint8_t type;       //!< PLDM type
40     uint8_t command;    //!< PLDM command
41 
42     bool operator==(const RequestKey& e) const
43     {
44         return ((eid == e.eid) && (instanceId == e.instanceId) &&
45                 (type == e.type) && (command == e.command));
46     }
47 };
48 
49 /** @struct RequestKeyHasher
50  *
51  *  This is a simple hash function, since the instance ID generator API
52  *  generates unique instance IDs for MCTP endpoint ID.
53  */
54 struct RequestKeyHasher
55 {
56     std::size_t operator()(const RequestKey& key) const
57     {
58         return (key.eid << 24 | key.instanceId << 16 | key.type << 8 |
59                 key.command);
60     }
61 };
62 
63 using ResponseHandler = fu2::unique_function<void(
64     mctp_eid_t eid, const pldm_msg* response, size_t respMsgLen)>;
65 
66 /** @class Handler
67  *
68  *  This class handles the lifecycle of the PLDM request message based on the
69  *  instance ID expiration interval, number of request retries and the timeout
70  *  waiting for a response. The registered response handlers are invoked with
71  *  response once the PLDM responder sends the response. If no response is
72  *  received within the instance ID expiration interval or any other failure the
73  *  response handler is invoked with the empty response.
74  *
75  * @tparam RequestInterface - Request class type
76  */
77 template <class RequestInterface>
78 class Handler
79 {
80 
81   public:
82     Handler() = delete;
83     Handler(const Handler&) = delete;
84     Handler(Handler&&) = delete;
85     Handler& operator=(const Handler&) = delete;
86     Handler& operator=(Handler&&) = delete;
87     ~Handler() = default;
88 
89     /** @brief Constructor
90      *
91      *  @param[in] fd - fd of MCTP communications socket
92      *  @param[in] event - reference to PLDM daemon's main event loop
93      *  @param[in] requester - reference to Requester object
94      *  @param[in] instanceIdExpiryInterval - instance ID expiration interval
95      *  @param[in] numRetries - number of request retries
96      *  @param[in] responseTimeOut - time to wait between each retry
97      */
98     explicit Handler(
99         int fd, sdeventplus::Event& event, pldm::dbus_api::Requester& requester,
100         std::chrono::seconds instanceIdExpiryInterval =
101             std::chrono::seconds(INSTANCE_ID_EXPIRATION_INTERVAL),
102         uint8_t numRetries = static_cast<uint8_t>(NUMBER_OF_REQUEST_RETRIES),
103         std::chrono::milliseconds responseTimeOut =
104             std::chrono::milliseconds(RESPONSE_TIME_OUT)) :
105         fd(fd),
106         event(event), requester(requester),
107         instanceIdExpiryInterval(instanceIdExpiryInterval),
108         numRetries(numRetries), responseTimeOut(responseTimeOut)
109     {}
110 
111     /** @brief Register a PLDM request message
112      *
113      *  @param[in] eid - endpoint ID of the remote MCTP endpoint
114      *  @param[in] instanceId - instance ID to match request and response
115      *  @param[in] type - PLDM type
116      *  @param[in] command - PLDM command
117      *  @param[in] requestMsg - PLDM request message
118      *  @param[in] responseHandler - Response handler for this request
119      *
120      *  @return return PLDM_SUCCESS on success and PLDM_ERROR otherwise
121      */
122     int registerRequest(mctp_eid_t eid, uint8_t instanceId, uint8_t type,
123                         uint8_t command, pldm::Request&& requestMsg,
124                         ResponseHandler&& responseHandler)
125     {
126         RequestKey key{eid, instanceId, type, command};
127 
128         auto instanceIdExpiryCallBack = [key, this](void) {
129             if (this->handlers.contains(key))
130             {
131                 std::cerr << "Response not received for the request, instance "
132                              "ID expired."
133                           << " EID = " << (unsigned)key.eid
134                           << " INSTANCE_ID = " << (unsigned)key.instanceId
135                           << " TYPE = " << (unsigned)key.type
136                           << " COMMAND = " << (unsigned)key.command << "\n";
137                 auto& [request, responseHandler, timerInstance] =
138                     this->handlers[key];
139                 request->stop();
140                 auto rc = timerInstance->stop();
141                 if (rc)
142                 {
143                     std::cerr
144                         << "Failed to stop the instance ID expiry timer. RC = "
145                         << rc << "\n";
146                 }
147                 // Call response handler with an empty response to indicate no
148                 // response
149                 responseHandler(key.eid, nullptr, 0);
150                 this->removeRequestContainer.emplace(
151                     key, std::make_unique<sdeventplus::source::Defer>(
152                              event, std::bind(&Handler::removeRequestEntry,
153                                               this, key)));
154             }
155             else
156             {
157                 // This condition is not possible, if a response is received
158                 // before the instance ID expiry, then the response handler
159                 // is executed and the entry will be removed.
160                 assert(false);
161             }
162         };
163 
164         auto request = std::make_unique<RequestInterface>(
165             fd, eid, event, std::move(requestMsg), numRetries, responseTimeOut);
166         auto timer = std::make_unique<phosphor::Timer>(
167             event.get(), instanceIdExpiryCallBack);
168 
169         auto rc = request->start();
170         if (rc)
171         {
172             requester.markFree(eid, instanceId);
173             std::cerr << "Failure to send the PLDM request message"
174                       << "\n";
175             return rc;
176         }
177 
178         try
179         {
180             timer->start(duration_cast<std::chrono::microseconds>(
181                 instanceIdExpiryInterval));
182         }
183         catch (const std::runtime_error& e)
184         {
185             requester.markFree(eid, instanceId);
186             std::cerr << "Failed to start the instance ID expiry timer. RC = "
187                       << e.what() << "\n";
188             return PLDM_ERROR;
189         }
190 
191         handlers.emplace(key, std::make_tuple(std::move(request),
192                                               std::move(responseHandler),
193                                               std::move(timer)));
194         return rc;
195     }
196 
197     /** @brief Handle PLDM response message
198      *
199      *  @param[in] eid - endpoint ID of the remote MCTP endpoint
200      *  @param[in] instanceId - instance ID to match request and response
201      *  @param[in] type - PLDM type
202      *  @param[in] command - PLDM command
203      *  @param[in] response - PLDM response message
204      *  @param[in] respMsgLen - length of the response message
205      */
206     void handleResponse(mctp_eid_t eid, uint8_t instanceId, uint8_t type,
207                         uint8_t command, const pldm_msg* response,
208                         size_t respMsgLen)
209     {
210         RequestKey key{eid, instanceId, type, command};
211         if (handlers.contains(key))
212         {
213             auto& [request, responseHandler, timerInstance] = handlers[key];
214             request->stop();
215             auto rc = timerInstance->stop();
216             if (rc)
217             {
218                 std::cerr
219                     << "Failed to stop the instance ID expiry timer. RC = "
220                     << rc << "\n";
221             }
222             responseHandler(eid, response, respMsgLen);
223             requester.markFree(key.eid, key.instanceId);
224             handlers.erase(key);
225         }
226         else
227         {
228             // Got a response for a PLDM request message not registered with the
229             // request handler, so freeing up the instance ID, this can be other
230             // OpenBMC applications relying on PLDM D-Bus apis like
231             // openpower-occ-control and softoff
232             requester.markFree(key.eid, key.instanceId);
233         }
234     }
235 
236   private:
237     int fd; //!< file descriptor of MCTP communications socket
238     sdeventplus::Event& event; //!< reference to PLDM daemon's main event loop
239     pldm::dbus_api::Requester& requester; //!< reference to Requester object
240     std::chrono::seconds
241         instanceIdExpiryInterval; //!< Instance ID expiration interval
242     uint8_t numRetries;           //!< number of request retries
243     std::chrono::milliseconds
244         responseTimeOut; //!< time to wait between each retry
245 
246     /** @brief Container for storing the details of the PLDM request message,
247      *         handler for the corresponding PLDM response and the timer object
248      *         for the Instance ID expiration
249      */
250     using RequestValue =
251         std::tuple<std::unique_ptr<RequestInterface>, ResponseHandler,
252                    std::unique_ptr<phosphor::Timer>>;
253 
254     /** @brief Container for storing the PLDM request entries */
255     std::unordered_map<RequestKey, RequestValue, RequestKeyHasher> handlers;
256 
257     /** @brief Container to store information about the request entries to be
258      *         removed after the instance ID timer expires
259      */
260     std::unordered_map<RequestKey, std::unique_ptr<sdeventplus::source::Defer>,
261                        RequestKeyHasher>
262         removeRequestContainer;
263 
264     /** @brief Remove request entry for which the instance ID expired
265      *
266      *  @param[in] key - key for the Request
267      */
268     void removeRequestEntry(RequestKey key)
269     {
270         if (removeRequestContainer.contains(key))
271         {
272             removeRequestContainer[key].reset();
273             requester.markFree(key.eid, key.instanceId);
274             handlers.erase(key);
275             removeRequestContainer.erase(key);
276         }
277     }
278 };
279 
280 } // namespace requester
281 
282 } // namespace pldm
283