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