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