xref: /openbmc/pldm/requester/handler.hpp (revision db49e0d3)
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 
83   public:
84     Handler() = delete;
85     Handler(const Handler&) = delete;
86     Handler(Handler&&) = delete;
87     Handler& operator=(const Handler&) = delete;
88     Handler& operator=(Handler&&) = delete;
89     ~Handler() = default;
90 
91     /** @brief Constructor
92      *
93      *  @param[in] fd - fd of MCTP communications socket
94      *  @param[in] event - reference to PLDM daemon's main event loop
95      *  @param[in] requester - reference to Requester object
96      *  @param[in] currentSendbuffSize - current send buffer size
97      *  @param[in] verbose - verbose tracing flag
98      *  @param[in] instanceIdExpiryInterval - instance ID expiration interval
99      *  @param[in] numRetries - number of request retries
100      *  @param[in] responseTimeOut - time to wait between each retry
101      */
102     explicit Handler(
103         int fd, sdeventplus::Event& event, pldm::dbus_api::Requester& requester,
104         int currentSendbuffSize, bool verbose,
105         std::chrono::seconds instanceIdExpiryInterval =
106             std::chrono::seconds(INSTANCE_ID_EXPIRATION_INTERVAL),
107         uint8_t numRetries = static_cast<uint8_t>(NUMBER_OF_REQUEST_RETRIES),
108         std::chrono::milliseconds responseTimeOut =
109             std::chrono::milliseconds(RESPONSE_TIME_OUT)) :
110         fd(fd),
111         event(event), requester(requester),
112         currentSendbuffSize(currentSendbuffSize), verbose(verbose),
113         instanceIdExpiryInterval(instanceIdExpiryInterval),
114         numRetries(numRetries), responseTimeOut(responseTimeOut)
115     {}
116 
117     /** @brief Register a PLDM request message
118      *
119      *  @param[in] eid - endpoint ID of the remote MCTP endpoint
120      *  @param[in] instanceId - instance ID to match request and response
121      *  @param[in] type - PLDM type
122      *  @param[in] command - PLDM command
123      *  @param[in] requestMsg - PLDM request message
124      *  @param[in] responseHandler - Response handler for this request
125      *
126      *  @return return PLDM_SUCCESS on success and PLDM_ERROR otherwise
127      */
128     int registerRequest(mctp_eid_t eid, uint8_t instanceId, uint8_t type,
129                         uint8_t command, pldm::Request&& requestMsg,
130                         ResponseHandler&& responseHandler)
131     {
132         RequestKey key{eid, instanceId, type, command};
133 
134         auto instanceIdExpiryCallBack = [key, this](void) {
135             if (this->handlers.contains(key))
136             {
137                 std::cerr << "Response not received for the request, instance "
138                              "ID expired."
139                           << " EID = " << (unsigned)key.eid
140                           << " INSTANCE_ID = " << (unsigned)key.instanceId
141                           << " TYPE = " << (unsigned)key.type
142                           << " COMMAND = " << (unsigned)key.command << "\n";
143                 auto& [request, responseHandler, timerInstance] =
144                     this->handlers[key];
145                 request->stop();
146                 auto rc = timerInstance->stop();
147                 if (rc)
148                 {
149                     std::cerr
150                         << "Failed to stop the instance ID expiry timer. RC = "
151                         << rc << "\n";
152                 }
153                 // Call response handler with an empty response to indicate no
154                 // response
155                 responseHandler(key.eid, nullptr, 0);
156                 this->removeRequestContainer.emplace(
157                     key, std::make_unique<sdeventplus::source::Defer>(
158                              event, std::bind(&Handler::removeRequestEntry,
159                                               this, key)));
160             }
161             else
162             {
163                 // This condition is not possible, if a response is received
164                 // before the instance ID expiry, then the response handler
165                 // is executed and the entry will be removed.
166                 assert(false);
167             }
168         };
169 
170         auto request = std::make_unique<RequestInterface>(
171             fd, eid, event, std::move(requestMsg), numRetries, responseTimeOut,
172             currentSendbuffSize, verbose);
173         auto timer = std::make_unique<phosphor::Timer>(
174             event.get(), instanceIdExpiryCallBack);
175 
176         auto rc = request->start();
177         if (rc)
178         {
179             requester.markFree(eid, instanceId);
180             std::cerr << "Failure to send the PLDM request message"
181                       << "\n";
182             return rc;
183         }
184 
185         try
186         {
187             timer->start(duration_cast<std::chrono::microseconds>(
188                 instanceIdExpiryInterval));
189         }
190         catch (const std::runtime_error& e)
191         {
192             requester.markFree(eid, instanceId);
193             std::cerr << "Failed to start the instance ID expiry timer. RC = "
194                       << e.what() << "\n";
195             return PLDM_ERROR;
196         }
197 
198         handlers.emplace(key, std::make_tuple(std::move(request),
199                                               std::move(responseHandler),
200                                               std::move(timer)));
201         return rc;
202     }
203 
204     /** @brief Handle PLDM response message
205      *
206      *  @param[in] eid - endpoint ID of the remote MCTP endpoint
207      *  @param[in] instanceId - instance ID to match request and response
208      *  @param[in] type - PLDM type
209      *  @param[in] command - PLDM command
210      *  @param[in] response - PLDM response message
211      *  @param[in] respMsgLen - length of the response message
212      */
213     void handleResponse(mctp_eid_t eid, uint8_t instanceId, uint8_t type,
214                         uint8_t command, const pldm_msg* response,
215                         size_t respMsgLen)
216     {
217         RequestKey key{eid, instanceId, type, command};
218         if (handlers.contains(key))
219         {
220             auto& [request, responseHandler, timerInstance] = handlers[key];
221             request->stop();
222             auto rc = timerInstance->stop();
223             if (rc)
224             {
225                 std::cerr
226                     << "Failed to stop the instance ID expiry timer. RC = "
227                     << rc << "\n";
228             }
229             responseHandler(eid, response, respMsgLen);
230             requester.markFree(key.eid, key.instanceId);
231             handlers.erase(key);
232         }
233         else
234         {
235             // Got a response for a PLDM request message not registered with the
236             // request handler, so freeing up the instance ID, this can be other
237             // OpenBMC applications relying on PLDM D-Bus apis like
238             // openpower-occ-control and softoff
239             requester.markFree(key.eid, key.instanceId);
240         }
241     }
242 
243   private:
244     int fd; //!< file descriptor of MCTP communications socket
245     sdeventplus::Event& event; //!< reference to PLDM daemon's main event loop
246     pldm::dbus_api::Requester& requester; //!< reference to Requester object
247     int currentSendbuffSize;              //!< current Send Buffer size
248     bool verbose;                         //!< verbose tracing flag
249     std::chrono::seconds
250         instanceIdExpiryInterval; //!< Instance ID expiration interval
251     uint8_t numRetries;           //!< number of request retries
252     std::chrono::milliseconds
253         responseTimeOut; //!< time to wait between each retry
254 
255     /** @brief Container for storing the details of the PLDM request
256      *         message, handler for the corresponding PLDM response and the
257      *         timer object for the Instance ID expiration
258      */
259     using RequestValue =
260         std::tuple<std::unique_ptr<RequestInterface>, ResponseHandler,
261                    std::unique_ptr<phosphor::Timer>>;
262 
263     /** @brief Container for storing the PLDM request entries */
264     std::unordered_map<RequestKey, RequestValue, RequestKeyHasher> handlers;
265 
266     /** @brief Container to store information about the request entries to be
267      *         removed after the instance ID timer expires
268      */
269     std::unordered_map<RequestKey, std::unique_ptr<sdeventplus::source::Defer>,
270                        RequestKeyHasher>
271         removeRequestContainer;
272 
273     /** @brief Remove request entry for which the instance ID expired
274      *
275      *  @param[in] key - key for the Request
276      */
277     void removeRequestEntry(RequestKey key)
278     {
279         if (removeRequestContainer.contains(key))
280         {
281             removeRequestContainer[key].reset();
282             requester.markFree(key.eid, key.instanceId);
283             handlers.erase(key);
284             removeRequestContainer.erase(key);
285         }
286     }
287 };
288 
289 } // namespace requester
290 
291 } // namespace pldm
292