platform-mc: Set the local terminus as event receiver

Send `SetEventReceiver` to the discoveried terminus with the
configurable local EID to set the local terminus as event receiver.
Before send `SetEventReceiver` the local terminus also send
`EventMessageSupported` to get the `synchronyConfigurationSupported`.
The `eventMessageGlobalEnable` and `heartbeatTimer` options in the
`SetEventReceiver` command will depend on the responded
`synchronyConfigurationSupported`.

Signed-off-by: Thu Nguyen <thu@os.amperecomputing.com>
Signed-off-by: Gilbert Chen <gilbertc@nvidia.com>
Change-Id: Ia798c1cd5d946ac519933bca60620e970fe10b0a
diff --git a/platform-mc/platform_manager.cpp b/platform-mc/platform_manager.cpp
index f6ffcc5..35526fb 100644
--- a/platform-mc/platform_manager.cpp
+++ b/platform-mc/platform_manager.cpp
@@ -36,7 +36,111 @@
 
             terminus->parseTerminusPDRs();
         }
+
+        auto rc = co_await configEventReceiver(tid);
+        if (rc)
+        {
+            lg2::error(
+                "Failed to config event receiver for terminus with TID: {TID}, error: {ERROR}",
+                "TID", tid, "ERROR", rc);
+        }
     }
+
+    co_return PLDM_SUCCESS;
+}
+
+exec::task<int> PlatformManager::configEventReceiver(pldm_tid_t tid)
+{
+    if (!termini.contains(tid))
+    {
+        co_return PLDM_ERROR;
+    }
+
+    auto& terminus = termini[tid];
+    if (!terminus->doesSupportCommand(PLDM_PLATFORM,
+                                      PLDM_EVENT_MESSAGE_SUPPORTED))
+    {
+        terminus->synchronyConfigurationSupported.byte = 0;
+    }
+    else
+    {
+        /**
+         *  Get synchronyConfigurationSupported use PLDM command
+         *  eventMessageBufferSize
+         */
+        uint8_t synchronyConfiguration = 0;
+        uint8_t numberEventClassReturned = 0;
+        std::vector<uint8_t> eventClass{};
+        auto rc = co_await eventMessageSupported(
+            tid, 1, synchronyConfiguration,
+            terminus->synchronyConfigurationSupported, numberEventClassReturned,
+            eventClass);
+        if (rc != PLDM_SUCCESS)
+        {
+            lg2::error(
+                "Failed to get event message supported for terminus with TID: {TID}, error: {ERROR}",
+                "TID", tid, "ERROR", rc);
+            terminus->synchronyConfigurationSupported.byte = 0;
+        }
+    }
+
+    if (!terminus->doesSupportCommand(PLDM_PLATFORM, PLDM_SET_EVENT_RECEIVER))
+    {
+        lg2::error("Terminus {TID} does not support Event", "TID", tid);
+        co_return PLDM_ERROR;
+    }
+
+    /**
+     *  Set Event receiver base on synchronyConfigurationSupported data
+     *  use PLDM command SetEventReceiver
+     */
+    pldm_event_message_global_enable eventMessageGlobalEnable =
+        PLDM_EVENT_MESSAGE_GLOBAL_DISABLE;
+    uint16_t heartbeatTimer = 0;
+
+    /* Use PLDM_EVENT_MESSAGE_GLOBAL_ENABLE_ASYNC_KEEP_ALIVE when
+     * for eventMessageGlobalEnable when the terminus supports that type
+     */
+    if (terminus->synchronyConfigurationSupported.byte &
+        (1 << PLDM_EVENT_MESSAGE_GLOBAL_ENABLE_ASYNC_KEEP_ALIVE))
+    {
+        heartbeatTimer = HEARTBEAT_TIMEOUT;
+        eventMessageGlobalEnable =
+            PLDM_EVENT_MESSAGE_GLOBAL_ENABLE_ASYNC_KEEP_ALIVE;
+    }
+    /* Use PLDM_EVENT_MESSAGE_GLOBAL_ENABLE_ASYNC when
+     * for eventMessageGlobalEnable when the terminus does not support
+     * PLDM_EVENT_MESSAGE_GLOBAL_ENABLE_ASYNC_KEEP_ALIVE
+     * and supports PLDM_EVENT_MESSAGE_GLOBAL_ENABLE_ASYNC type
+     */
+    else if (terminus->synchronyConfigurationSupported.byte &
+             (1 << PLDM_EVENT_MESSAGE_GLOBAL_ENABLE_ASYNC))
+    {
+        eventMessageGlobalEnable = PLDM_EVENT_MESSAGE_GLOBAL_ENABLE_ASYNC;
+    }
+    /* Only use PLDM_EVENT_MESSAGE_GLOBAL_ENABLE_POLLING
+     * for eventMessageGlobalEnable when the terminus only supports
+     * this type
+     */
+    else if (terminus->synchronyConfigurationSupported.byte &
+             (1 << PLDM_EVENT_MESSAGE_GLOBAL_ENABLE_POLLING))
+    {
+        eventMessageGlobalEnable = PLDM_EVENT_MESSAGE_GLOBAL_ENABLE_POLLING;
+    }
+
+    if (eventMessageGlobalEnable != PLDM_EVENT_MESSAGE_GLOBAL_DISABLE)
+    {
+        auto rc = co_await setEventReceiver(tid, eventMessageGlobalEnable,
+                                            PLDM_TRANSPORT_PROTOCOL_TYPE_MCTP,
+                                            heartbeatTimer);
+        if (rc != PLDM_SUCCESS)
+        {
+            lg2::error(
+                "Failed to set event receiver for terminus with TID: {TID}, error: {ERROR}",
+                "TID", tid, "ERROR", rc);
+        }
+    }
+
     co_return PLDM_SUCCESS;
 }
 
@@ -265,5 +369,122 @@
     co_return completionCode;
 }
 
+exec::task<int> PlatformManager::setEventReceiver(
+    pldm_tid_t tid, pldm_event_message_global_enable eventMessageGlobalEnable,
+    pldm_transport_protocol_type protocolType, uint16_t heartbeatTimer)
+{
+    size_t requestBytes = PLDM_SET_EVENT_RECEIVER_REQ_BYTES;
+    /**
+     * Ignore heartbeatTimer bytes when eventMessageGlobalEnable is not
+     * ENABLE_ASYNC_KEEP_ALIVE
+     */
+    if (eventMessageGlobalEnable !=
+        PLDM_EVENT_MESSAGE_GLOBAL_ENABLE_ASYNC_KEEP_ALIVE)
+    {
+        requestBytes = requestBytes - sizeof(heartbeatTimer);
+    }
+    Request request(sizeof(pldm_msg_hdr) + requestBytes);
+    auto requestMsg = reinterpret_cast<pldm_msg*>(request.data());
+    auto rc = encode_set_event_receiver_req(
+        0, eventMessageGlobalEnable, protocolType,
+        terminusManager.getLocalEid(), heartbeatTimer, requestMsg);
+    if (rc)
+    {
+        lg2::error(
+            "Failed to encode request SetEventReceiver for terminus ID {TID}, error {RC} ",
+            "TID", tid, "RC", rc);
+        co_return rc;
+    }
+
+    const pldm_msg* responseMsg = nullptr;
+    size_t responseLen = 0;
+    rc = co_await terminusManager.sendRecvPldmMsg(tid, request, &responseMsg,
+                                                  &responseLen);
+    if (rc)
+    {
+        lg2::error(
+            "Failed to send SetEventReceiver message for terminus {TID}, error {RC}",
+            "TID", tid, "RC", rc);
+        co_return rc;
+    }
+
+    uint8_t completionCode;
+    rc = decode_set_event_receiver_resp(responseMsg, responseLen,
+                                        &completionCode);
+    if (rc)
+    {
+        lg2::error(
+            "Failed to decode response SetEventReceiver for terminus ID {TID}, error {RC} ",
+            "TID", tid, "RC", rc);
+        co_return rc;
+    }
+
+    if (completionCode != PLDM_SUCCESS)
+    {
+        lg2::error(
+            "Error : SetEventReceiver for terminus ID {TID}, complete code {CC}.",
+            "TID", tid, "CC", completionCode);
+        co_return completionCode;
+    }
+
+    co_return completionCode;
+}
+
+exec::task<int> PlatformManager::eventMessageSupported(
+    pldm_tid_t tid, uint8_t formatVersion, uint8_t& synchronyConfiguration,
+    bitfield8_t& synchronyConfigurationSupported,
+    uint8_t& numberEventClassReturned, std::vector<uint8_t>& eventClass)
+{
+    Request request(
+        sizeof(pldm_msg_hdr) + PLDM_EVENT_MESSAGE_SUPPORTED_REQ_BYTES);
+    auto requestMsg = reinterpret_cast<pldm_msg*>(request.data());
+    auto rc = encode_event_message_supported_req(0, formatVersion, requestMsg);
+    if (rc)
+    {
+        lg2::error(
+            "Failed to encode request EventMessageSupported for terminus ID {TID}, error {RC} ",
+            "TID", tid, "RC", rc);
+        co_return rc;
+    }
+
+    const pldm_msg* responseMsg = nullptr;
+    size_t responseLen = 0;
+    rc = co_await terminusManager.sendRecvPldmMsg(tid, request, &responseMsg,
+                                                  &responseLen);
+    if (rc)
+    {
+        lg2::error(
+            "Failed to send EventMessageSupported message for terminus {TID}, error {RC}",
+            "TID", tid, "RC", rc);
+        co_return rc;
+    }
+
+    uint8_t completionCode = 0;
+    uint8_t eventClassCount = static_cast<uint8_t>(responseLen) -
+                              PLDM_EVENT_MESSAGE_SUPPORTED_MIN_RESP_BYTES;
+    eventClass.resize(eventClassCount);
+
+    rc = decode_event_message_supported_resp(
+        responseMsg, responseLen, &completionCode, &synchronyConfiguration,
+        &synchronyConfigurationSupported, &numberEventClassReturned,
+        eventClass.data(), eventClassCount);
+    if (rc)
+    {
+        lg2::error(
+            "Failed to decode response EventMessageSupported for terminus ID {TID}, error {RC} ",
+            "TID", tid, "RC", rc);
+        co_return rc;
+    }
+
+    if (completionCode != PLDM_SUCCESS)
+    {
+        lg2::error(
+            "Error : EventMessageSupported for terminus ID {TID}, complete code {CC}.",
+            "TID", tid, "CC", completionCode);
+        co_return completionCode;
+    }
+
+    co_return completionCode;
+}
 } // namespace platform_mc
 } // namespace pldm