#pragma once #include "common/types.hpp" #include "pldmd/instance_id.hpp" #include "request.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include PHOSPHOR_LOG2_USING; namespace pldm { namespace requester { /** @struct RequestKey * * RequestKey uniquely identifies the PLDM request message to match it with the * response and a combination of MCTP endpoint ID, PLDM instance ID, PLDM type * and PLDM command is the key. */ struct RequestKey { mctp_eid_t eid; //!< MCTP endpoint ID uint8_t instanceId; //!< PLDM instance ID uint8_t type; //!< PLDM type uint8_t command; //!< PLDM command bool operator==(const RequestKey& e) const { return ((eid == e.eid) && (instanceId == e.instanceId) && (type == e.type) && (command == e.command)); } }; /** @struct RequestKeyHasher * * This is a simple hash function, since the instance ID generator API * generates unique instance IDs for MCTP endpoint ID. */ struct RequestKeyHasher { std::size_t operator()(const RequestKey& key) const { return (key.eid << 24 | key.instanceId << 16 | key.type << 8 | key.command); } }; using ResponseHandler = fu2::unique_function; /** @class Handler * * This class handles the lifecycle of the PLDM request message based on the * instance ID expiration interval, number of request retries and the timeout * waiting for a response. The registered response handlers are invoked with * response once the PLDM responder sends the response. If no response is * received within the instance ID expiration interval or any other failure the * response handler is invoked with the empty response. * * @tparam RequestInterface - Request class type */ template class Handler { public: Handler() = delete; Handler(const Handler&) = delete; Handler(Handler&&) = delete; Handler& operator=(const Handler&) = delete; Handler& operator=(Handler&&) = delete; ~Handler() = default; /** @brief Constructor * * @param[in] fd - fd of MCTP communications socket * @param[in] event - reference to PLDM daemon's main event loop * @param[in] instanceIdDb - reference to an InstanceIdDb * @param[in] currentSendbuffSize - current send buffer size * @param[in] verbose - verbose tracing flag * @param[in] instanceIdExpiryInterval - instance ID expiration interval * @param[in] numRetries - number of request retries * @param[in] responseTimeOut - time to wait between each retry */ explicit Handler( int fd, sdeventplus::Event& event, pldm::InstanceIdDb& instanceIdDb, int currentSendbuffSize, bool verbose, std::chrono::seconds instanceIdExpiryInterval = std::chrono::seconds(INSTANCE_ID_EXPIRATION_INTERVAL), uint8_t numRetries = static_cast(NUMBER_OF_REQUEST_RETRIES), std::chrono::milliseconds responseTimeOut = std::chrono::milliseconds(RESPONSE_TIME_OUT)) : fd(fd), event(event), instanceIdDb(instanceIdDb), currentSendbuffSize(currentSendbuffSize), verbose(verbose), instanceIdExpiryInterval(instanceIdExpiryInterval), numRetries(numRetries), responseTimeOut(responseTimeOut) {} /** @brief Register a PLDM request message * * @param[in] eid - endpoint ID of the remote MCTP endpoint * @param[in] instanceId - instance ID to match request and response * @param[in] type - PLDM type * @param[in] command - PLDM command * @param[in] requestMsg - PLDM request message * @param[in] responseHandler - Response handler for this request * * @return return PLDM_SUCCESS on success and PLDM_ERROR otherwise */ int registerRequest(mctp_eid_t eid, uint8_t instanceId, uint8_t type, uint8_t command, pldm::Request&& requestMsg, ResponseHandler&& responseHandler) { RequestKey key{eid, instanceId, type, command}; auto instanceIdExpiryCallBack = [key, this](void) { if (this->handlers.contains(key)) { error( "Response not received for the request, instance ID expired. EID = {EID} INSTANCE_ID = {INST_ID} TYPE = {REQ_KEY_TYPE} COMMAND = {REQ_KEY_CMD}", "EID", (unsigned)key.eid, "INST_ID", (unsigned)key.instanceId, "REQ_KEY_TYPE", (unsigned)key.type, "REQ_KEY_CMD", (unsigned)key.command); auto& [request, responseHandler, timerInstance] = this->handlers[key]; request->stop(); auto rc = timerInstance->stop(); if (rc) { error( "Failed to stop the instance ID expiry timer. RC = {RC}", "RC", static_cast(rc)); } // Call response handler with an empty response to indicate no // response responseHandler(key.eid, nullptr, 0); this->removeRequestContainer.emplace( key, std::make_unique( event, std::bind(&Handler::removeRequestEntry, this, key))); } else { // This condition is not possible, if a response is received // before the instance ID expiry, then the response handler // is executed and the entry will be removed. assert(false); } }; if (handlers.contains(key)) { error("The eid:InstanceID {EID}:{IID} is using.", "EID", (unsigned)eid, "IID", (unsigned)instanceId); return PLDM_ERROR; } auto request = std::make_unique( fd, eid, event, std::move(requestMsg), numRetries, responseTimeOut, currentSendbuffSize, verbose); auto timer = std::make_unique( event.get(), instanceIdExpiryCallBack); auto rc = request->start(); if (rc) { instanceIdDb.free(eid, instanceId); error("Failure to send the PLDM request message"); return rc; } try { timer->start(duration_cast( instanceIdExpiryInterval)); } catch (const std::runtime_error& e) { instanceIdDb.free(eid, instanceId); error( "Failed to start the instance ID expiry timer. RC = {ERR_EXCEP}", "ERR_EXCEP", e.what()); return PLDM_ERROR; } handlers.emplace(key, std::make_tuple(std::move(request), std::move(responseHandler), std::move(timer))); return rc; } /** @brief Handle PLDM response message * * @param[in] eid - endpoint ID of the remote MCTP endpoint * @param[in] instanceId - instance ID to match request and response * @param[in] type - PLDM type * @param[in] command - PLDM command * @param[in] response - PLDM response message * @param[in] respMsgLen - length of the response message */ void handleResponse(mctp_eid_t eid, uint8_t instanceId, uint8_t type, uint8_t command, const pldm_msg* response, size_t respMsgLen) { RequestKey key{eid, instanceId, type, command}; if (handlers.contains(key)) { auto& [request, responseHandler, timerInstance] = handlers[key]; request->stop(); auto rc = timerInstance->stop(); if (rc) { error("Failed to stop the instance ID expiry timer. RC = {RC}", "RC", static_cast(rc)); } responseHandler(eid, response, respMsgLen); instanceIdDb.free(key.eid, key.instanceId); handlers.erase(key); } else { // Got a response for a PLDM request message not registered with the // request handler, so freeing up the instance ID, this can be other // OpenBMC applications relying on PLDM D-Bus apis like // openpower-occ-control and softoff instanceIdDb.free(key.eid, key.instanceId); } } private: int fd; //!< file descriptor of MCTP communications socket sdeventplus::Event& event; //!< reference to PLDM daemon's main event loop pldm::InstanceIdDb& instanceIdDb; //!< reference to an InstanceIdDb int currentSendbuffSize; //!< current Send Buffer size bool verbose; //!< verbose tracing flag std::chrono::seconds instanceIdExpiryInterval; //!< Instance ID expiration interval uint8_t numRetries; //!< number of request retries std::chrono::milliseconds responseTimeOut; //!< time to wait between each retry /** @brief Container for storing the details of the PLDM request * message, handler for the corresponding PLDM response and the * timer object for the Instance ID expiration */ using RequestValue = std::tuple, ResponseHandler, std::unique_ptr>; /** @brief Container for storing the PLDM request entries */ std::unordered_map handlers; /** @brief Container to store information about the request entries to be * removed after the instance ID timer expires */ std::unordered_map, RequestKeyHasher> removeRequestContainer; /** @brief Remove request entry for which the instance ID expired * * @param[in] key - key for the Request */ void removeRequestEntry(RequestKey key) { if (removeRequestContainer.contains(key)) { removeRequestContainer[key].reset(); instanceIdDb.free(key.eid, key.instanceId); handlers.erase(key); removeRequestContainer.erase(key); } } }; } // namespace requester } // namespace pldm