Implement Subscription Heartbeat Logic

This implements the subscription heartbeat logic which will send the
message `RedfishServiceFunctional` periodically with the interval of
`HeartbeatIntervalMinutes` specified in subscription property [1][2], if
`SendHeartbeat` is enabled..

Note the heartbeat enablement is per event destination as DMTF specifies
[3] like
```
... This message shall only be sent if specifically requested by an event
destination during the creation of a subscription...
```

This also add `HeartbeatEvent` to supported registry prefixes like
```
curl -k -X GET https://${bmc}/redfish/v1/EventService/
{
  ...
  "RegistryPrefixes": [
    "Base",
    "OpenBMC",
    "TaskEvent",
    "HeartbeatEvent"
  ],
  "ResourceTypes": [
    "Task",
    "Heartbeat"
  ],
```

Tested:

1) A single subscription and heartbeat via Redfish Event Listener

- Create a subscription via Redfish Event Listener
- PATCH `SendHeartbeat=true` and `HeartbeatIntervalMinutes` like

```
curl -k -X PATCH https://${bmc}/redfish/v1/EventService/Subscriptions/${SUBID} \
        -H "Content-Type: application/json" \
        -d '{"SendHeartbeat":true, "HeartbeatIntervalMinutes":1}'
```

- Monitor the Redfish Event Listener and check the following heartbeat
  messages periodically (per HeartbeatIntervalMinutes)

```
response_type: POST
headers: {'Host': '9.3.62.209', 'Content-Length': '230'}

response={
  "@odata.type": "#Event.v1_4_0.Event",
  "Events": [
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "EventId": "HeartbeatId",
      "EventTimestamp": "2024-11-21T12:21:47+00:00",
      "MemberId": "0",
      "Message": "Redfish service is functional.",
      "MessageArgs": [],
      "MessageId": "HeartbeatEvent.1.0.1.RedfishServiceFunctional",
      "MessageSeverity": "OK",
      "OriginOfCondition": "/redfish/v1/EventService/Subscriptions/1521743607",
      "Resolution": "None."
    }
  ],
  "Id": "HeartbeatId",
  "Name": "Event Log"
}
```

- Change `SendHeartbeat` to false and see whether the heartbeat message
  is stopped.

2) Multiple sbscribers with the different heartbeat setups

- create 2 event listeners with 2 different destinations (e.g., port
  8080 and 8081).
- Patch sendheartbeat=true to only one subscriber.
- Check whether the only subscriber that enables `SendHeartbeat` is
  receiving the heartbeat messages.

3) Redfish Service Validator passes

[1] https://github.com/openbmc/bmcweb/blob/02ea923f13de196726ac2f022766a6f80bee1c0a/redfish-core/schema/dmtf/json-schema/EventDestination.v1_15_0.json#L356
[2] https://redfish.dmtf.org/registries/HeartbeatEvent.1.0.1.json
[3] https://github.com/DMTF/Redfish/blob/d9e54fc8393d8930bd42e8b134741f5051a2680f/registries/HeartbeatEvent.1.0.1.json#L14

Change-Id: I8682e05f4459940913ba189f1ed016874e38dd4a
Signed-off-by: Myung Bae <myungbae@us.ibm.com>
diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp
index 04ca735..57385a2 100644
--- a/redfish-core/lib/event_service.hpp
+++ b/redfish-core/lib/event_service.hpp
@@ -45,13 +45,13 @@
 
 static constexpr const std::array<const char*, 2> supportedEvtFormatTypes = {
     eventFormatType, metricReportFormatType};
-static constexpr const std::array<const char*, 3> supportedRegPrefixes = {
-    "Base", "OpenBMC", "TaskEvent"};
+static constexpr const std::array<const char*, 4> supportedRegPrefixes = {
+    "Base", "OpenBMC", "TaskEvent", "HeartbeatEvent"};
 static constexpr const std::array<const char*, 3> supportedRetryPolicies = {
     "TerminateAfterRetries", "SuspendRetries", "RetryForever"};
 
-static constexpr const std::array<const char*, 1> supportedResourceTypes = {
-    "Task"};
+static constexpr const std::array<const char*, 2> supportedResourceTypes = {
+    "Task", "Heartbeat"};
 
 inline void requestRoutesEventService(App& app)
 {
@@ -708,6 +708,12 @@
             messages::created(asyncResp->res);
             asyncResp->res.addHeader(
                 "Location", "/redfish/v1/EventService/Subscriptions/" + id);
+
+            // schedule a heartbeat
+            if (subValue->userSub->sendHeartbeat)
+            {
+                subValue->scheduleNextHeartbeatEvent();
+            }
         });
 }
 
@@ -875,6 +881,14 @@
                     subValue->userSub->hbIntervalMinutes = *hbIntervalMinutes;
                 }
 
+                if (hbIntervalMinutes || sendHeartbeat)
+                {
+                    // if Heartbeat interval or send heart were changed, cancel
+                    // the heartbeat timer if running and start a new heartbeat
+                    // if needed
+                    subValue->heartbeatParametersChanged();
+                }
+
                 if (verifyCertificate)
                 {
                     subValue->userSub->verifyCertificate = *verifyCertificate;