xref: /openbmc/bmcweb/redfish-core/lib/event_service.hpp (revision 276ede551ae1371b73631cbc3860c377bb26fe35)
1 /*
2 Copyright (c) 2020 Intel Corporation
3 
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7 
8       http://www.apache.org/licenses/LICENSE-2.0
9 
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16 #pragma once
17 #include "app.hpp"
18 #include "event_service_manager.hpp"
19 #include "generated/enums/event_service.hpp"
20 #include "http/utility.hpp"
21 #include "logging.hpp"
22 #include "query.hpp"
23 #include "registries.hpp"
24 #include "registries/privilege_registry.hpp"
25 #include "registries_selector.hpp"
26 #include "snmp_trap_event_clients.hpp"
27 #include "utils/json_utils.hpp"
28 
29 #include <boost/beast/http/fields.hpp>
30 #include <boost/system/error_code.hpp>
31 #include <boost/url/parse.hpp>
32 #include <sdbusplus/unpack_properties.hpp>
33 #include <utils/dbus_utils.hpp>
34 
35 #include <charconv>
36 #include <memory>
37 #include <optional>
38 #include <ranges>
39 #include <span>
40 #include <string>
41 #include <vector>
42 
43 namespace redfish
44 {
45 
46 static constexpr const std::array<const char*, 2> supportedEvtFormatTypes = {
47     eventFormatType, metricReportFormatType};
48 static constexpr const std::array<const char*, 4> supportedRegPrefixes = {
49     "Base", "OpenBMC", "TaskEvent", "HeartbeatEvent"};
50 static constexpr const std::array<const char*, 3> supportedRetryPolicies = {
51     "TerminateAfterRetries", "SuspendRetries", "RetryForever"};
52 
53 static constexpr const std::array<const char*, 2> supportedResourceTypes = {
54     "Task", "Heartbeat"};
55 
56 inline void requestRoutesEventService(App& app)
57 {
58     BMCWEB_ROUTE(app, "/redfish/v1/EventService/")
59         .privileges(redfish::privileges::getEventService)
60         .methods(
61             boost::beast::http::verb::
62                 get)([&app](
63                          const crow::Request& req,
64                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
65             if (!redfish::setUpRedfishRoute(app, req, asyncResp))
66             {
67                 return;
68             }
69 
70             asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/EventService";
71             asyncResp->res.jsonValue["@odata.type"] =
72                 "#EventService.v1_5_0.EventService";
73             asyncResp->res.jsonValue["Id"] = "EventService";
74             asyncResp->res.jsonValue["Name"] = "Event Service";
75             asyncResp->res.jsonValue["ServerSentEventUri"] =
76                 "/redfish/v1/EventService/SSE";
77 
78             asyncResp->res.jsonValue["Subscriptions"]["@odata.id"] =
79                 "/redfish/v1/EventService/Subscriptions";
80             asyncResp->res.jsonValue["Actions"]["#EventService.SubmitTestEvent"]
81                                     ["target"] =
82                 "/redfish/v1/EventService/Actions/EventService.SubmitTestEvent";
83 
84             const persistent_data::EventServiceConfig eventServiceConfig =
85                 persistent_data::EventServiceStore::getInstance()
86                     .getEventServiceConfig();
87 
88             asyncResp->res.jsonValue["Status"]["State"] =
89                 (eventServiceConfig.enabled ? "Enabled" : "Disabled");
90             asyncResp->res.jsonValue["ServiceEnabled"] =
91                 eventServiceConfig.enabled;
92             asyncResp->res.jsonValue["DeliveryRetryAttempts"] =
93                 eventServiceConfig.retryAttempts;
94             asyncResp->res.jsonValue["DeliveryRetryIntervalSeconds"] =
95                 eventServiceConfig.retryTimeoutInterval;
96             asyncResp->res.jsonValue["EventFormatTypes"] =
97                 supportedEvtFormatTypes;
98             asyncResp->res.jsonValue["RegistryPrefixes"] = supportedRegPrefixes;
99             asyncResp->res.jsonValue["ResourceTypes"] = supportedResourceTypes;
100 
101             nlohmann::json::object_t supportedSSEFilters;
102             supportedSSEFilters["EventFormatType"] = true;
103             supportedSSEFilters["MessageId"] = true;
104             supportedSSEFilters["MetricReportDefinition"] = true;
105             supportedSSEFilters["RegistryPrefix"] = true;
106             supportedSSEFilters["OriginResource"] = false;
107             supportedSSEFilters["ResourceType"] = false;
108 
109             asyncResp->res.jsonValue["SSEFilterPropertiesSupported"] =
110                 std::move(supportedSSEFilters);
111         });
112 
113     BMCWEB_ROUTE(app, "/redfish/v1/EventService/")
114         .privileges(redfish::privileges::patchEventService)
115         .methods(boost::beast::http::verb::patch)(
116             [&app](const crow::Request& req,
117                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
118                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
119                 {
120                     return;
121                 }
122                 std::optional<bool> serviceEnabled;
123                 std::optional<uint32_t> retryAttemps;
124                 std::optional<uint32_t> retryInterval;
125                 if (!json_util::readJsonPatch( //
126                         req, asyncResp->res, //
127                         "DeliveryRetryAttempts", retryAttemps, //
128                         "DeliveryRetryIntervalSeconds", retryInterval, //
129                         "ServiceEnabled", serviceEnabled //
130                         ))
131                 {
132                     return;
133                 }
134 
135                 persistent_data::EventServiceConfig eventServiceConfig =
136                     persistent_data::EventServiceStore::getInstance()
137                         .getEventServiceConfig();
138 
139                 if (serviceEnabled)
140                 {
141                     eventServiceConfig.enabled = *serviceEnabled;
142                 }
143 
144                 if (retryAttemps)
145                 {
146                     // Supported range [1-3]
147                     if ((*retryAttemps < 1) || (*retryAttemps > 3))
148                     {
149                         messages::queryParameterOutOfRange(
150                             asyncResp->res, std::to_string(*retryAttemps),
151                             "DeliveryRetryAttempts", "[1-3]");
152                     }
153                     else
154                     {
155                         eventServiceConfig.retryAttempts = *retryAttemps;
156                     }
157                 }
158 
159                 if (retryInterval)
160                 {
161                     // Supported range [5 - 180]
162                     if ((*retryInterval < 5) || (*retryInterval > 180))
163                     {
164                         messages::queryParameterOutOfRange(
165                             asyncResp->res, std::to_string(*retryInterval),
166                             "DeliveryRetryIntervalSeconds", "[5-180]");
167                     }
168                     else
169                     {
170                         eventServiceConfig.retryTimeoutInterval =
171                             *retryInterval;
172                     }
173                 }
174 
175                 EventServiceManager::getInstance().setEventServiceConfig(
176                     eventServiceConfig);
177             });
178 }
179 
180 inline void requestRoutesSubmitTestEvent(App& app)
181 {
182     BMCWEB_ROUTE(
183         app, "/redfish/v1/EventService/Actions/EventService.SubmitTestEvent/")
184         .privileges(redfish::privileges::postEventService)
185         .methods(boost::beast::http::verb::post)(
186             [&app](const crow::Request& req,
187                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
188                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
189                 {
190                     return;
191                 }
192                 if (!EventServiceManager::getInstance().sendTestEventLog())
193                 {
194                     messages::serviceDisabled(asyncResp->res,
195                                               "/redfish/v1/EventService/");
196                     return;
197                 }
198                 asyncResp->res.result(boost::beast::http::status::no_content);
199             });
200 }
201 
202 inline void doSubscriptionCollection(
203     const boost::system::error_code& ec,
204     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
205     const dbus::utility::ManagedObjectType& resp)
206 {
207     if (ec)
208     {
209         if (ec.value() == EBADR || ec.value() == EHOSTUNREACH)
210         {
211             // This is an optional process so just return if it isn't there
212             return;
213         }
214 
215         BMCWEB_LOG_ERROR("D-Bus response error on GetManagedObjects {}", ec);
216         messages::internalError(asyncResp->res);
217         return;
218     }
219     nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"];
220     for (const auto& objpath : resp)
221     {
222         sdbusplus::message::object_path path(objpath.first);
223         const std::string snmpId = path.filename();
224         if (snmpId.empty())
225         {
226             BMCWEB_LOG_ERROR("The SNMP client ID is wrong");
227             messages::internalError(asyncResp->res);
228             return;
229         }
230 
231         getSnmpSubscriptionList(asyncResp, snmpId, memberArray);
232     }
233 }
234 
235 inline void requestRoutesEventDestinationCollection(App& app)
236 {
237     BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/")
238         .privileges(redfish::privileges::getEventDestinationCollection)
239         .methods(boost::beast::http::verb::get)(
240             [&app](const crow::Request& req,
241                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
242                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
243                 {
244                     return;
245                 }
246                 asyncResp->res.jsonValue["@odata.type"] =
247                     "#EventDestinationCollection.EventDestinationCollection";
248                 asyncResp->res.jsonValue["@odata.id"] =
249                     "/redfish/v1/EventService/Subscriptions";
250                 asyncResp->res.jsonValue["Name"] =
251                     "Event Destination Collections";
252 
253                 nlohmann::json& memberArray =
254                     asyncResp->res.jsonValue["Members"];
255 
256                 std::vector<std::string> subscripIds =
257                     EventServiceManager::getInstance().getAllIDs();
258                 memberArray = nlohmann::json::array();
259                 asyncResp->res.jsonValue["Members@odata.count"] =
260                     subscripIds.size();
261 
262                 for (const std::string& id : subscripIds)
263                 {
264                     nlohmann::json::object_t member;
265                     member["@odata.id"] = boost::urls::format(
266                         "/redfish/v1/EventService/Subscriptions/{}" + id);
267                     memberArray.emplace_back(std::move(member));
268                 }
269                 crow::connections::systemBus->async_method_call(
270                     [asyncResp](const boost::system::error_code& ec,
271                                 const dbus::utility::ManagedObjectType& resp) {
272                         doSubscriptionCollection(ec, asyncResp, resp);
273                     },
274                     "xyz.openbmc_project.Network.SNMP",
275                     "/xyz/openbmc_project/network/snmp/manager",
276                     "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
277             });
278 
279     BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/")
280         .privileges(redfish::privileges::postEventDestinationCollection)
281         .methods(
282             boost::beast::http::verb::
283                 post)([&app](
284                           const crow::Request& req,
285                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
286             if (!redfish::setUpRedfishRoute(app, req, asyncResp))
287             {
288                 return;
289             }
290             if (EventServiceManager::getInstance().getNumberOfSubscriptions() >=
291                 maxNoOfSubscriptions)
292             {
293                 messages::eventSubscriptionLimitExceeded(asyncResp->res);
294                 return;
295             }
296             std::string destUrl;
297             std::string protocol;
298             std::optional<bool> verifyCertificate;
299             std::optional<std::string> context;
300             std::optional<std::string> subscriptionType;
301             std::optional<std::string> eventFormatType2;
302             std::optional<std::string> retryPolicy;
303             std::optional<bool> sendHeartbeat;
304             std::optional<uint64_t> hbIntervalMinutes;
305             std::optional<std::vector<std::string>> msgIds;
306             std::optional<std::vector<std::string>> regPrefixes;
307             std::optional<std::vector<std::string>> originResources;
308             std::optional<std::vector<std::string>> resTypes;
309             std::optional<std::vector<nlohmann::json::object_t>> headers;
310             std::optional<std::vector<nlohmann::json::object_t>> mrdJsonArray;
311 
312             if (!json_util::readJsonPatch( //
313                     req, asyncResp->res, //
314                     "Context", context, //
315                     "DeliveryRetryPolicy", retryPolicy, //
316                     "Destination", destUrl, //
317                     "EventFormatType", eventFormatType2, //
318                     "HeartbeatIntervalMinutes", hbIntervalMinutes, //
319                     "HttpHeaders", headers, //
320                     "MessageIds", msgIds, //
321                     "MetricReportDefinitions", mrdJsonArray, //
322                     "OriginResources", originResources, //
323                     "Protocol", protocol, //
324                     "RegistryPrefixes", regPrefixes, //
325                     "ResourceTypes", resTypes, //
326                     "SendHeartbeat", sendHeartbeat, //
327                     "SubscriptionType", subscriptionType, //
328                     "VerifyCertificate", verifyCertificate //
329                     ))
330             {
331                 return;
332             }
333             // clang-format on
334 
335             // https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers
336             static constexpr const uint16_t maxDestinationSize = 2000;
337             if (destUrl.size() > maxDestinationSize)
338             {
339                 messages::stringValueTooLong(asyncResp->res, "Destination",
340                                              maxDestinationSize);
341                 return;
342             }
343 
344             if (regPrefixes && msgIds)
345             {
346                 if (!regPrefixes->empty() && !msgIds->empty())
347                 {
348                     messages::propertyValueConflict(
349                         asyncResp->res, "MessageIds", "RegistryPrefixes");
350                     return;
351                 }
352             }
353 
354             boost::system::result<boost::urls::url> url =
355                 boost::urls::parse_absolute_uri(destUrl);
356             if (!url)
357             {
358                 BMCWEB_LOG_WARNING(
359                     "Failed to validate and split destination url");
360                 messages::propertyValueFormatError(asyncResp->res, destUrl,
361                                                    "Destination");
362                 return;
363             }
364             url->normalize();
365 
366             // port_number returns zero if it is not a valid representable port
367             if (url->has_port() && url->port_number() == 0)
368             {
369                 BMCWEB_LOG_WARNING("{} is an invalid port in destination url",
370                                    url->port());
371                 messages::propertyValueFormatError(asyncResp->res, destUrl,
372                                                    "Destination");
373                 return;
374             }
375 
376             crow::utility::setProtocolDefaults(*url, protocol);
377             crow::utility::setPortDefaults(*url);
378 
379             if (url->path().empty())
380             {
381                 url->set_path("/");
382             }
383 
384             if (url->has_userinfo())
385             {
386                 messages::propertyValueFormatError(asyncResp->res, destUrl,
387                                                    "Destination");
388                 return;
389             }
390 
391             if (protocol == "SNMPv2c")
392             {
393                 if (context)
394                 {
395                     messages::propertyValueConflict(asyncResp->res, "Context",
396                                                     "Protocol");
397                     return;
398                 }
399                 if (eventFormatType2)
400                 {
401                     messages::propertyValueConflict(
402                         asyncResp->res, "EventFormatType", "Protocol");
403                     return;
404                 }
405                 if (retryPolicy)
406                 {
407                     messages::propertyValueConflict(asyncResp->res,
408                                                     "RetryPolicy", "Protocol");
409                     return;
410                 }
411                 if (sendHeartbeat)
412                 {
413                     messages::propertyValueConflict(
414                         asyncResp->res, "SendHeartbeat", "Protocol");
415                     return;
416                 }
417                 if (hbIntervalMinutes)
418                 {
419                     messages::propertyValueConflict(
420                         asyncResp->res, "HeartbeatIntervalMinutes", "Protocol");
421                     return;
422                 }
423                 if (msgIds)
424                 {
425                     messages::propertyValueConflict(asyncResp->res,
426                                                     "MessageIds", "Protocol");
427                     return;
428                 }
429                 if (regPrefixes)
430                 {
431                     messages::propertyValueConflict(
432                         asyncResp->res, "RegistryPrefixes", "Protocol");
433                     return;
434                 }
435                 if (resTypes)
436                 {
437                     messages::propertyValueConflict(
438                         asyncResp->res, "ResourceTypes", "Protocol");
439                     return;
440                 }
441                 if (headers)
442                 {
443                     messages::propertyValueConflict(asyncResp->res,
444                                                     "HttpHeaders", "Protocol");
445                     return;
446                 }
447                 if (mrdJsonArray)
448                 {
449                     messages::propertyValueConflict(
450                         asyncResp->res, "MetricReportDefinitions", "Protocol");
451                     return;
452                 }
453                 if (url->scheme() != "snmp")
454                 {
455                     messages::propertyValueConflict(asyncResp->res,
456                                                     "Destination", "Protocol");
457                     return;
458                 }
459 
460                 addSnmpTrapClient(asyncResp, url->host_address(),
461                                   url->port_number());
462                 return;
463             }
464 
465             std::shared_ptr<Subscription> subValue =
466                 std::make_shared<Subscription>(
467                     std::make_shared<persistent_data::UserSubscription>(), *url,
468                     app.ioContext());
469 
470             if (subscriptionType)
471             {
472                 if (*subscriptionType != "RedfishEvent")
473                 {
474                     messages::propertyValueNotInList(
475                         asyncResp->res, *subscriptionType, "SubscriptionType");
476                     return;
477                 }
478                 subValue->userSub->subscriptionType = *subscriptionType;
479             }
480             else
481             {
482                 // Default
483                 subValue->userSub->subscriptionType = "RedfishEvent";
484             }
485 
486             if (protocol != "Redfish")
487             {
488                 messages::propertyValueNotInList(asyncResp->res, protocol,
489                                                  "Protocol");
490                 return;
491             }
492             subValue->userSub->protocol = protocol;
493 
494             if (verifyCertificate)
495             {
496                 subValue->userSub->verifyCertificate = *verifyCertificate;
497             }
498 
499             if (eventFormatType2)
500             {
501                 if (std::ranges::find(supportedEvtFormatTypes,
502                                       *eventFormatType2) ==
503                     supportedEvtFormatTypes.end())
504                 {
505                     messages::propertyValueNotInList(
506                         asyncResp->res, *eventFormatType2, "EventFormatType");
507                     return;
508                 }
509                 subValue->userSub->eventFormatType = *eventFormatType2;
510             }
511             else
512             {
513                 // If not specified, use default "Event"
514                 subValue->userSub->eventFormatType = "Event";
515             }
516 
517             if (context)
518             {
519                 // This value is selected arbitrarily.
520                 constexpr const size_t maxContextSize = 256;
521                 if (context->size() > maxContextSize)
522                 {
523                     messages::stringValueTooLong(asyncResp->res, "Context",
524                                                  maxContextSize);
525                     return;
526                 }
527                 subValue->userSub->customText = *context;
528             }
529 
530             if (headers)
531             {
532                 size_t cumulativeLen = 0;
533 
534                 for (const nlohmann::json::object_t& headerChunk : *headers)
535                 {
536                     for (const auto& item : headerChunk)
537                     {
538                         const std::string* value =
539                             item.second.get_ptr<const std::string*>();
540                         if (value == nullptr)
541                         {
542                             messages::propertyValueFormatError(
543                                 asyncResp->res, item.second,
544                                 "HttpHeaders/" + item.first);
545                             return;
546                         }
547                         // Adding a new json value is the size of the key, +
548                         // the size of the value + 2 * 2 quotes for each, +
549                         // the colon and space between. example:
550                         // "key": "value"
551                         cumulativeLen += item.first.size() + value->size() + 6;
552                         // This value is selected to mirror http_connection.hpp
553                         constexpr const uint16_t maxHeaderSizeED = 8096;
554                         if (cumulativeLen > maxHeaderSizeED)
555                         {
556                             messages::arraySizeTooLong(
557                                 asyncResp->res, "HttpHeaders", maxHeaderSizeED);
558                             return;
559                         }
560                         subValue->userSub->httpHeaders.set(item.first, *value);
561                     }
562                 }
563             }
564 
565             if (regPrefixes)
566             {
567                 for (const std::string& it : *regPrefixes)
568                 {
569                     if (std::ranges::find(supportedRegPrefixes, it) ==
570                         supportedRegPrefixes.end())
571                     {
572                         messages::propertyValueNotInList(asyncResp->res, it,
573                                                          "RegistryPrefixes");
574                         return;
575                     }
576                 }
577                 subValue->userSub->registryPrefixes = *regPrefixes;
578             }
579 
580             if (originResources)
581             {
582                 subValue->userSub->originResources = *originResources;
583             }
584 
585             if (resTypes)
586             {
587                 for (const std::string& it : *resTypes)
588                 {
589                     if (std::ranges::find(supportedResourceTypes, it) ==
590                         supportedResourceTypes.end())
591                     {
592                         messages::propertyValueNotInList(asyncResp->res, it,
593                                                          "ResourceTypes");
594                         return;
595                     }
596                 }
597                 subValue->userSub->resourceTypes = *resTypes;
598             }
599 
600             if (msgIds)
601             {
602                 std::vector<std::string> registryPrefix;
603 
604                 // If no registry prefixes are mentioned, consider all
605                 // supported prefixes
606                 if (subValue->userSub->registryPrefixes.empty())
607                 {
608                     registryPrefix.assign(supportedRegPrefixes.begin(),
609                                           supportedRegPrefixes.end());
610                 }
611                 else
612                 {
613                     registryPrefix = subValue->userSub->registryPrefixes;
614                 }
615 
616                 for (const std::string& id : *msgIds)
617                 {
618                     bool validId = false;
619 
620                     // Check for Message ID in each of the selected Registry
621                     for (const std::string& it : registryPrefix)
622                     {
623                         const std::span<const redfish::registries::MessageEntry>
624                             registry =
625                                 redfish::registries::getRegistryFromPrefix(it);
626 
627                         if (std::ranges::any_of(
628                                 registry,
629                                 [&id](const redfish::registries::MessageEntry&
630                                           messageEntry) {
631                                     return id == messageEntry.first;
632                                 }))
633                         {
634                             validId = true;
635                             break;
636                         }
637                     }
638 
639                     if (!validId)
640                     {
641                         messages::propertyValueNotInList(asyncResp->res, id,
642                                                          "MessageIds");
643                         return;
644                     }
645                 }
646 
647                 subValue->userSub->registryMsgIds = *msgIds;
648             }
649 
650             if (retryPolicy)
651             {
652                 if (std::ranges::find(supportedRetryPolicies, *retryPolicy) ==
653                     supportedRetryPolicies.end())
654                 {
655                     messages::propertyValueNotInList(
656                         asyncResp->res, *retryPolicy, "DeliveryRetryPolicy");
657                     return;
658                 }
659                 subValue->userSub->retryPolicy = *retryPolicy;
660             }
661             else
662             {
663                 // Default "TerminateAfterRetries"
664                 subValue->userSub->retryPolicy = "TerminateAfterRetries";
665             }
666             if (sendHeartbeat)
667             {
668                 subValue->userSub->sendHeartbeat = *sendHeartbeat;
669             }
670             if (hbIntervalMinutes)
671             {
672                 if (*hbIntervalMinutes < 1 || *hbIntervalMinutes > 65535)
673                 {
674                     messages::propertyValueOutOfRange(
675                         asyncResp->res, *hbIntervalMinutes,
676                         "HeartbeatIntervalMinutes");
677                     return;
678                 }
679                 subValue->userSub->hbIntervalMinutes = *hbIntervalMinutes;
680             }
681 
682             if (mrdJsonArray)
683             {
684                 for (nlohmann::json::object_t& mrdObj : *mrdJsonArray)
685                 {
686                     std::string mrdUri;
687 
688                     if (!json_util::readJsonObject(mrdObj, asyncResp->res,
689                                                    "@odata.id", mrdUri))
690 
691                     {
692                         return;
693                     }
694                     subValue->userSub->metricReportDefinitions.emplace_back(
695                         mrdUri);
696                 }
697             }
698 
699             std::string id =
700                 EventServiceManager::getInstance().addPushSubscription(
701                     subValue);
702             if (id.empty())
703             {
704                 messages::internalError(asyncResp->res);
705                 return;
706             }
707 
708             messages::created(asyncResp->res);
709             asyncResp->res.addHeader(
710                 "Location", "/redfish/v1/EventService/Subscriptions/" + id);
711 
712             // schedule a heartbeat
713             if (subValue->userSub->sendHeartbeat)
714             {
715                 subValue->scheduleNextHeartbeatEvent();
716             }
717         });
718 }
719 
720 inline void requestRoutesEventDestination(App& app)
721 {
722     BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/")
723         .privileges(redfish::privileges::getEventDestination)
724         .methods(boost::beast::http::verb::get)(
725             [&app](const crow::Request& req,
726                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
727                    const std::string& param) {
728                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
729                 {
730                     return;
731                 }
732 
733                 if (param.starts_with("snmp"))
734                 {
735                     getSnmpTrapClient(asyncResp, param);
736                     return;
737                 }
738 
739                 std::shared_ptr<Subscription> subValue =
740                     EventServiceManager::getInstance().getSubscription(param);
741                 if (subValue == nullptr)
742                 {
743                     asyncResp->res.result(
744                         boost::beast::http::status::not_found);
745                     return;
746                 }
747                 const std::string& id = param;
748 
749                 const persistent_data::UserSubscription& userSub =
750                     *subValue->userSub;
751 
752                 nlohmann::json& jVal = asyncResp->res.jsonValue;
753                 jVal["@odata.type"] =
754                     "#EventDestination.v1_14_1.EventDestination";
755                 jVal["Protocol"] =
756                     event_destination::EventDestinationProtocol::Redfish;
757                 jVal["@odata.id"] = boost::urls::format(
758                     "/redfish/v1/EventService/Subscriptions/{}", id);
759                 jVal["Id"] = id;
760                 jVal["Name"] = "Event Destination " + id;
761                 jVal["Destination"] = userSub.destinationUrl;
762                 jVal["Context"] = userSub.customText;
763                 jVal["SubscriptionType"] = userSub.subscriptionType;
764                 jVal["HttpHeaders"] = nlohmann::json::array();
765                 jVal["EventFormatType"] = userSub.eventFormatType;
766                 jVal["RegistryPrefixes"] = userSub.registryPrefixes;
767                 jVal["ResourceTypes"] = userSub.resourceTypes;
768 
769                 jVal["MessageIds"] = userSub.registryMsgIds;
770                 jVal["DeliveryRetryPolicy"] = userSub.retryPolicy;
771                 jVal["SendHeartbeat"] = userSub.sendHeartbeat;
772                 jVal["HeartbeatIntervalMinutes"] = userSub.hbIntervalMinutes;
773                 jVal["VerifyCertificate"] = userSub.verifyCertificate;
774 
775                 nlohmann::json::array_t mrdJsonArray;
776                 for (const auto& mdrUri : userSub.metricReportDefinitions)
777                 {
778                     nlohmann::json::object_t mdr;
779                     mdr["@odata.id"] = mdrUri;
780                     mrdJsonArray.emplace_back(std::move(mdr));
781                 }
782                 jVal["MetricReportDefinitions"] = mrdJsonArray;
783             });
784     BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/")
785         // The below privilege is wrong, it should be ConfigureManager OR
786         // ConfigureSelf
787         // https://github.com/openbmc/bmcweb/issues/220
788         //.privileges(redfish::privileges::patchEventDestination)
789         .privileges({{"ConfigureManager"}})
790         .methods(boost::beast::http::verb::patch)(
791             [&app](const crow::Request& req,
792                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
793                    const std::string& param) {
794                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
795                 {
796                     return;
797                 }
798                 std::shared_ptr<Subscription> subValue =
799                     EventServiceManager::getInstance().getSubscription(param);
800                 if (subValue == nullptr)
801                 {
802                     asyncResp->res.result(
803                         boost::beast::http::status::not_found);
804                     return;
805                 }
806 
807                 std::optional<std::string> context;
808                 std::optional<std::string> retryPolicy;
809                 std::optional<bool> sendHeartbeat;
810                 std::optional<uint64_t> hbIntervalMinutes;
811                 std::optional<bool> verifyCertificate;
812                 std::optional<std::vector<nlohmann::json::object_t>> headers;
813 
814                 if (!json_util::readJsonPatch( //
815                         req, asyncResp->res, //
816                         "Context", context, //
817                         "DeliveryRetryPolicy", retryPolicy, //
818                         "HeartbeatIntervalMinutes", hbIntervalMinutes, //
819                         "HttpHeaders", headers, //
820                         "SendHeartbeat", sendHeartbeat, //
821                         "VerifyCertificate", verifyCertificate //
822                         ))
823                 {
824                     return;
825                 }
826 
827                 if (context)
828                 {
829                     subValue->userSub->customText = *context;
830                 }
831 
832                 if (headers)
833                 {
834                     boost::beast::http::fields fields;
835                     for (const nlohmann::json::object_t& headerChunk : *headers)
836                     {
837                         for (const auto& it : headerChunk)
838                         {
839                             const std::string* value =
840                                 it.second.get_ptr<const std::string*>();
841                             if (value == nullptr)
842                             {
843                                 messages::propertyValueFormatError(
844                                     asyncResp->res, it.second,
845                                     "HttpHeaders/" + it.first);
846                                 return;
847                             }
848                             fields.set(it.first, *value);
849                         }
850                     }
851                     subValue->userSub->httpHeaders = std::move(fields);
852                 }
853 
854                 if (retryPolicy)
855                 {
856                     if (std::ranges::find(supportedRetryPolicies,
857                                           *retryPolicy) ==
858                         supportedRetryPolicies.end())
859                     {
860                         messages::propertyValueNotInList(asyncResp->res,
861                                                          *retryPolicy,
862                                                          "DeliveryRetryPolicy");
863                         return;
864                     }
865                     subValue->userSub->retryPolicy = *retryPolicy;
866                 }
867 
868                 if (sendHeartbeat)
869                 {
870                     subValue->userSub->sendHeartbeat = *sendHeartbeat;
871                 }
872                 if (hbIntervalMinutes)
873                 {
874                     if (*hbIntervalMinutes < 1 || *hbIntervalMinutes > 65535)
875                     {
876                         messages::propertyValueOutOfRange(
877                             asyncResp->res, *hbIntervalMinutes,
878                             "HeartbeatIntervalMinutes");
879                         return;
880                     }
881                     subValue->userSub->hbIntervalMinutes = *hbIntervalMinutes;
882                 }
883 
884                 if (hbIntervalMinutes || sendHeartbeat)
885                 {
886                     // if Heartbeat interval or send heart were changed, cancel
887                     // the heartbeat timer if running and start a new heartbeat
888                     // if needed
889                     subValue->heartbeatParametersChanged();
890                 }
891 
892                 if (verifyCertificate)
893                 {
894                     subValue->userSub->verifyCertificate = *verifyCertificate;
895                 }
896 
897                 EventServiceManager::getInstance().updateSubscriptionData();
898             });
899     BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/")
900         // The below privilege is wrong, it should be ConfigureManager OR
901         // ConfigureSelf
902         // https://github.com/openbmc/bmcweb/issues/220
903         //.privileges(redfish::privileges::deleteEventDestination)
904         .privileges({{"ConfigureManager"}})
905         .methods(boost::beast::http::verb::delete_)(
906             [&app](const crow::Request& req,
907                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
908                    const std::string& param) {
909                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
910                 {
911                     return;
912                 }
913                 EventServiceManager& event = EventServiceManager::getInstance();
914                 if (param.starts_with("snmp"))
915                 {
916                     deleteSnmpTrapClient(asyncResp, param);
917                     event.deleteSubscription(param);
918                     return;
919                 }
920 
921                 if (!event.deleteSubscription(param))
922                 {
923                     messages::resourceNotFound(asyncResp->res,
924                                                "EventDestination", param);
925                     return;
926                 }
927                 messages::success(asyncResp->res);
928             });
929 }
930 
931 } // namespace redfish
932