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