1 #pragma once
2 
3 #include "app.hpp"
4 #include "dbus_utility.hpp"
5 #include "generated/enums/metric_report_definition.hpp"
6 #include "query.hpp"
7 #include "registries/privilege_registry.hpp"
8 #include "sensors.hpp"
9 #include "utils/collection.hpp"
10 #include "utils/dbus_utils.hpp"
11 #include "utils/telemetry_utils.hpp"
12 #include "utils/time_utils.hpp"
13 
14 #include <boost/container/flat_map.hpp>
15 #include <boost/url/format.hpp>
16 #include <sdbusplus/asio/property.hpp>
17 #include <sdbusplus/unpack_properties.hpp>
18 
19 #include <array>
20 #include <map>
21 #include <optional>
22 #include <span>
23 #include <string>
24 #include <string_view>
25 #include <tuple>
26 #include <utility>
27 #include <variant>
28 #include <vector>
29 
30 namespace redfish
31 {
32 
33 namespace telemetry
34 {
35 
36 using ReadingParameters = std::vector<std::tuple<
37     std::vector<std::tuple<sdbusplus::message::object_path, std::string>>,
38     std::string, std::string, uint64_t>>;
39 
40 inline bool verifyCommonErrors(crow::Response& res, const std::string& id,
41                                const boost::system::error_code& ec)
42 {
43     if (ec.value() == EBADR || ec == boost::system::errc::host_unreachable)
44     {
45         messages::resourceNotFound(res, "MetricReportDefinition", id);
46         return false;
47     }
48 
49     if (ec == boost::system::errc::file_exists)
50     {
51         messages::resourceAlreadyExists(res, "MetricReportDefinition", "Id",
52                                         id);
53         return false;
54     }
55 
56     if (ec == boost::system::errc::too_many_files_open)
57     {
58         messages::createLimitReachedForResource(res);
59         return false;
60     }
61 
62     if (ec)
63     {
64         BMCWEB_LOG_ERROR("DBUS response error {}", ec);
65         messages::internalError(res);
66         return false;
67     }
68 
69     return true;
70 }
71 
72 inline metric_report_definition::ReportActionsEnum
73     toRedfishReportAction(std::string_view dbusValue)
74 {
75     if (dbusValue ==
76         "xyz.openbmc_project.Telemetry.Report.ReportActions.EmitsReadingsUpdate")
77     {
78         return metric_report_definition::ReportActionsEnum::RedfishEvent;
79     }
80     if (dbusValue ==
81         "xyz.openbmc_project.Telemetry.Report.ReportActions.LogToMetricReportsCollection")
82     {
83         return metric_report_definition::ReportActionsEnum::
84             LogToMetricReportsCollection;
85     }
86     return metric_report_definition::ReportActionsEnum::Invalid;
87 }
88 
89 inline std::string toDbusReportAction(std::string_view redfishValue)
90 {
91     if (redfishValue == "RedfishEvent")
92     {
93         return "xyz.openbmc_project.Telemetry.Report.ReportActions.EmitsReadingsUpdate";
94     }
95     if (redfishValue == "LogToMetricReportsCollection")
96     {
97         return "xyz.openbmc_project.Telemetry.Report.ReportActions.LogToMetricReportsCollection";
98     }
99     return "";
100 }
101 
102 inline metric_report_definition::MetricReportDefinitionType
103     toRedfishReportingType(std::string_view dbusValue)
104 {
105     if (dbusValue ==
106         "xyz.openbmc_project.Telemetry.Report.ReportingType.OnChange")
107     {
108         return metric_report_definition::MetricReportDefinitionType::OnChange;
109     }
110     if (dbusValue ==
111         "xyz.openbmc_project.Telemetry.Report.ReportingType.OnRequest")
112     {
113         return metric_report_definition::MetricReportDefinitionType::OnRequest;
114     }
115     if (dbusValue ==
116         "xyz.openbmc_project.Telemetry.Report.ReportingType.Periodic")
117     {
118         return metric_report_definition::MetricReportDefinitionType::Periodic;
119     }
120     return metric_report_definition::MetricReportDefinitionType::Invalid;
121 }
122 
123 inline std::string toDbusReportingType(std::string_view redfishValue)
124 {
125     if (redfishValue == "OnChange")
126     {
127         return "xyz.openbmc_project.Telemetry.Report.ReportingType.OnChange";
128     }
129     if (redfishValue == "OnRequest")
130     {
131         return "xyz.openbmc_project.Telemetry.Report.ReportingType.OnRequest";
132     }
133     if (redfishValue == "Periodic")
134     {
135         return "xyz.openbmc_project.Telemetry.Report.ReportingType.Periodic";
136     }
137     return "";
138 }
139 
140 inline metric_report_definition::CollectionTimeScope
141     toRedfishCollectionTimeScope(std::string_view dbusValue)
142 {
143     if (dbusValue ==
144         "xyz.openbmc_project.Telemetry.Report.CollectionTimescope.Point")
145     {
146         return metric_report_definition::CollectionTimeScope::Point;
147     }
148     if (dbusValue ==
149         "xyz.openbmc_project.Telemetry.Report.CollectionTimescope.Interval")
150     {
151         return metric_report_definition::CollectionTimeScope::Interval;
152     }
153     if (dbusValue ==
154         "xyz.openbmc_project.Telemetry.Report.CollectionTimescope.StartupInterval")
155     {
156         return metric_report_definition::CollectionTimeScope::StartupInterval;
157     }
158     return metric_report_definition::CollectionTimeScope::Invalid;
159 }
160 
161 inline std::string toDbusCollectionTimeScope(std::string_view redfishValue)
162 {
163     if (redfishValue == "Point")
164     {
165         return "xyz.openbmc_project.Telemetry.Report.CollectionTimescope.Point";
166     }
167     if (redfishValue == "Interval")
168     {
169         return "xyz.openbmc_project.Telemetry.Report.CollectionTimescope.Interval";
170     }
171     if (redfishValue == "StartupInterval")
172     {
173         return "xyz.openbmc_project.Telemetry.Report.CollectionTimescope.StartupInterval";
174     }
175     return "";
176 }
177 
178 inline metric_report_definition::ReportUpdatesEnum
179     toRedfishReportUpdates(std::string_view dbusValue)
180 {
181     if (dbusValue ==
182         "xyz.openbmc_project.Telemetry.Report.ReportUpdates.Overwrite")
183     {
184         return metric_report_definition::ReportUpdatesEnum::Overwrite;
185     }
186     if (dbusValue ==
187         "xyz.openbmc_project.Telemetry.Report.ReportUpdates.AppendWrapsWhenFull")
188     {
189         return metric_report_definition::ReportUpdatesEnum::AppendWrapsWhenFull;
190     }
191     if (dbusValue ==
192         "xyz.openbmc_project.Telemetry.Report.ReportUpdates.AppendStopsWhenFull")
193     {
194         return metric_report_definition::ReportUpdatesEnum::AppendStopsWhenFull;
195     }
196     return metric_report_definition::ReportUpdatesEnum::Invalid;
197 }
198 
199 inline std::string toDbusReportUpdates(std::string_view redfishValue)
200 {
201     if (redfishValue == "Overwrite")
202     {
203         return "xyz.openbmc_project.Telemetry.Report.ReportUpdates.Overwrite";
204     }
205     if (redfishValue == "AppendWrapsWhenFull")
206     {
207         return "xyz.openbmc_project.Telemetry.Report.ReportUpdates.AppendWrapsWhenFull";
208     }
209     if (redfishValue == "AppendStopsWhenFull")
210     {
211         return "xyz.openbmc_project.Telemetry.Report.ReportUpdates.AppendStopsWhenFull";
212     }
213     return "";
214 }
215 
216 inline std::optional<nlohmann::json::array_t> getLinkedTriggers(
217     std::span<const sdbusplus::message::object_path> triggerPaths)
218 {
219     nlohmann::json::array_t triggers;
220 
221     for (const sdbusplus::message::object_path& path : triggerPaths)
222     {
223         if (path.parent_path() !=
224             "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService")
225         {
226             BMCWEB_LOG_ERROR("Property Triggers contains invalid value: {}",
227                              path.str);
228             return std::nullopt;
229         }
230 
231         std::string id = path.filename();
232         if (id.empty())
233         {
234             BMCWEB_LOG_ERROR("Property Triggers contains invalid value: {}",
235                              path.str);
236             return std::nullopt;
237         }
238         nlohmann::json::object_t trigger;
239         trigger["@odata.id"] =
240             boost::urls::format("/redfish/v1/TelemetryService/Triggers/{}", id);
241         triggers.emplace_back(std::move(trigger));
242     }
243 
244     return triggers;
245 }
246 
247 inline void
248     fillReportDefinition(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
249                          const std::string& id,
250                          const dbus::utility::DBusPropertiesMap& properties)
251 {
252     std::vector<std::string> reportActions;
253     ReadingParameters readingParams;
254     std::string reportingType;
255     std::string reportUpdates;
256     std::string name;
257     uint64_t appendLimit = 0;
258     uint64_t interval = 0;
259     bool enabled = false;
260     std::vector<sdbusplus::message::object_path> triggers;
261 
262     const bool success = sdbusplus::unpackPropertiesNoThrow(
263         dbus_utils::UnpackErrorPrinter(), properties, "ReportingType",
264         reportingType, "Interval", interval, "ReportActions", reportActions,
265         "ReportUpdates", reportUpdates, "AppendLimit", appendLimit,
266         "ReadingParameters", readingParams, "Name", name, "Enabled", enabled,
267         "Triggers", triggers);
268 
269     if (!success)
270     {
271         messages::internalError(asyncResp->res);
272         return;
273     }
274 
275     metric_report_definition::MetricReportDefinitionType redfishReportingType =
276         toRedfishReportingType(reportingType);
277     if (redfishReportingType ==
278         metric_report_definition::MetricReportDefinitionType::Invalid)
279     {
280         messages::internalError(asyncResp->res);
281         return;
282     }
283 
284     asyncResp->res.jsonValue["MetricReportDefinitionType"] =
285         redfishReportingType;
286 
287     std::optional<nlohmann::json::array_t> linkedTriggers =
288         getLinkedTriggers(triggers);
289     if (!linkedTriggers)
290     {
291         messages::internalError(asyncResp->res);
292         return;
293     }
294 
295     asyncResp->res.jsonValue["Links"]["Triggers"] = std::move(*linkedTriggers);
296 
297     nlohmann::json::array_t redfishReportActions;
298     for (const std::string& action : reportActions)
299     {
300         metric_report_definition::ReportActionsEnum redfishAction =
301             toRedfishReportAction(action);
302         if (redfishAction ==
303             metric_report_definition::ReportActionsEnum::Invalid)
304         {
305             messages::internalError(asyncResp->res);
306             return;
307         }
308 
309         redfishReportActions.emplace_back(redfishAction);
310     }
311 
312     asyncResp->res.jsonValue["ReportActions"] = std::move(redfishReportActions);
313 
314     nlohmann::json::array_t metrics = nlohmann::json::array();
315     for (const auto& [sensorData, collectionFunction, collectionTimeScope,
316                       collectionDuration] : readingParams)
317     {
318         nlohmann::json::array_t metricProperties;
319 
320         for (const auto& [sensorPath, sensorMetadata] : sensorData)
321         {
322             metricProperties.emplace_back(sensorMetadata);
323         }
324 
325         nlohmann::json::object_t metric;
326 
327         metric_report_definition::CalculationAlgorithmEnum
328             redfishCollectionFunction =
329                 telemetry::toRedfishCollectionFunction(collectionFunction);
330         if (redfishCollectionFunction ==
331             metric_report_definition::CalculationAlgorithmEnum::Invalid)
332         {
333             messages::internalError(asyncResp->res);
334             return;
335         }
336         metric["CollectionFunction"] = redfishCollectionFunction;
337 
338         metric_report_definition::CollectionTimeScope
339             redfishCollectionTimeScope =
340                 toRedfishCollectionTimeScope(collectionTimeScope);
341         if (redfishCollectionTimeScope ==
342             metric_report_definition::CollectionTimeScope::Invalid)
343         {
344             messages::internalError(asyncResp->res);
345             return;
346         }
347         metric["CollectionTimeScope"] = redfishCollectionTimeScope;
348 
349         metric["MetricProperties"] = std::move(metricProperties);
350         metric["CollectionDuration"] = time_utils::toDurationString(
351             std::chrono::milliseconds(collectionDuration));
352         metrics.emplace_back(std::move(metric));
353     }
354     asyncResp->res.jsonValue["Metrics"] = std::move(metrics);
355 
356     if (enabled)
357     {
358         asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
359     }
360     else
361     {
362         asyncResp->res.jsonValue["Status"]["State"] = "Disabled";
363     }
364 
365     metric_report_definition::ReportUpdatesEnum redfishReportUpdates =
366         toRedfishReportUpdates(reportUpdates);
367     if (redfishReportUpdates ==
368         metric_report_definition::ReportUpdatesEnum::Invalid)
369     {
370         messages::internalError(asyncResp->res);
371         return;
372     }
373     asyncResp->res.jsonValue["ReportUpdates"] = redfishReportUpdates;
374 
375     asyncResp->res.jsonValue["MetricReportDefinitionEnabled"] = enabled;
376     asyncResp->res.jsonValue["AppendLimit"] = appendLimit;
377     asyncResp->res.jsonValue["Name"] = name;
378     asyncResp->res.jsonValue["Schedule"]["RecurrenceInterval"] =
379         time_utils::toDurationString(std::chrono::milliseconds(interval));
380     asyncResp->res.jsonValue["@odata.type"] =
381         "#MetricReportDefinition.v1_3_0.MetricReportDefinition";
382     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
383         "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", id);
384     asyncResp->res.jsonValue["Id"] = id;
385     asyncResp->res.jsonValue["MetricReport"]["@odata.id"] = boost::urls::format(
386         "/redfish/v1/TelemetryService/MetricReports/{}", id);
387 }
388 
389 struct AddReportArgs
390 {
391     struct MetricArgs
392     {
393         std::vector<std::string> uris;
394         std::string collectionFunction;
395         std::string collectionTimeScope;
396         uint64_t collectionDuration = 0;
397     };
398 
399     std::string id;
400     std::string name;
401     std::string reportingType;
402     std::string reportUpdates;
403     uint64_t appendLimit = std::numeric_limits<uint64_t>::max();
404     std::vector<std::string> reportActions;
405     uint64_t interval = std::numeric_limits<uint64_t>::max();
406     std::vector<MetricArgs> metrics;
407     bool metricReportDefinitionEnabled = true;
408 };
409 
410 inline bool toDbusReportActions(crow::Response& res,
411                                 const std::vector<std::string>& actions,
412                                 std::vector<std::string>& outReportActions)
413 {
414     size_t index = 0;
415     for (const std::string& action : actions)
416     {
417         std::string dbusReportAction = toDbusReportAction(action);
418         if (dbusReportAction.empty())
419         {
420             messages::propertyValueNotInList(
421                 res, action, "ReportActions/" + std::to_string(index));
422             return false;
423         }
424 
425         outReportActions.emplace_back(std::move(dbusReportAction));
426         index++;
427     }
428     return true;
429 }
430 
431 inline bool getUserMetric(crow::Response& res, nlohmann::json& metric,
432                           AddReportArgs::MetricArgs& metricArgs)
433 {
434     std::optional<std::vector<std::string>> uris;
435     std::optional<std::string> collectionDurationStr;
436     std::optional<std::string> collectionFunction;
437     std::optional<std::string> collectionTimeScopeStr;
438 
439     if (!json_util::readJson(metric, res, "MetricProperties", uris,
440                              "CollectionFunction", collectionFunction,
441                              "CollectionTimeScope", collectionTimeScopeStr,
442                              "CollectionDuration", collectionDurationStr))
443     {
444         return false;
445     }
446 
447     if (uris)
448     {
449         metricArgs.uris = std::move(*uris);
450     }
451 
452     if (collectionFunction)
453     {
454         std::string dbusCollectionFunction =
455             telemetry::toDbusCollectionFunction(*collectionFunction);
456         if (dbusCollectionFunction.empty())
457         {
458             messages::propertyValueIncorrect(res, "CollectionFunction",
459                                              *collectionFunction);
460             return false;
461         }
462         metricArgs.collectionFunction = std::move(dbusCollectionFunction);
463     }
464 
465     if (collectionTimeScopeStr)
466     {
467         std::string dbusCollectionTimeScope =
468             toDbusCollectionTimeScope(*collectionTimeScopeStr);
469         if (dbusCollectionTimeScope.empty())
470         {
471             messages::propertyValueIncorrect(res, "CollectionTimeScope",
472                                              *collectionTimeScopeStr);
473             return false;
474         }
475         metricArgs.collectionTimeScope = std::move(dbusCollectionTimeScope);
476     }
477 
478     if (collectionDurationStr)
479     {
480         std::optional<std::chrono::milliseconds> duration =
481             time_utils::fromDurationString(*collectionDurationStr);
482 
483         if (!duration || duration->count() < 0)
484         {
485             messages::propertyValueIncorrect(res, "CollectionDuration",
486                                              *collectionDurationStr);
487             return false;
488         }
489 
490         metricArgs.collectionDuration =
491             static_cast<uint64_t>(duration->count());
492     }
493 
494     return true;
495 }
496 
497 inline bool getUserMetrics(crow::Response& res,
498                            std::span<nlohmann::json> metrics,
499                            std::vector<AddReportArgs::MetricArgs>& result)
500 {
501     result.reserve(metrics.size());
502 
503     for (nlohmann::json& m : metrics)
504     {
505         AddReportArgs::MetricArgs metricArgs;
506 
507         if (!getUserMetric(res, m, metricArgs))
508         {
509             return false;
510         }
511 
512         result.emplace_back(std::move(metricArgs));
513     }
514 
515     return true;
516 }
517 
518 inline bool getUserParameters(crow::Response& res, const crow::Request& req,
519                               AddReportArgs& args)
520 {
521     std::optional<std::string> id;
522     std::optional<std::string> name;
523     std::optional<std::string> reportingTypeStr;
524     std::optional<std::string> reportUpdatesStr;
525     std::optional<uint64_t> appendLimit;
526     std::optional<bool> metricReportDefinitionEnabled;
527     std::optional<std::vector<nlohmann::json>> metrics;
528     std::optional<std::vector<std::string>> reportActionsStr;
529     std::optional<nlohmann::json> schedule;
530 
531     if (!json_util::readJsonPatch(
532             req, res, "Id", id, "Name", name, "Metrics", metrics,
533             "MetricReportDefinitionType", reportingTypeStr, "ReportUpdates",
534             reportUpdatesStr, "AppendLimit", appendLimit, "ReportActions",
535             reportActionsStr, "Schedule", schedule,
536             "MetricReportDefinitionEnabled", metricReportDefinitionEnabled))
537     {
538         return false;
539     }
540 
541     if (id)
542     {
543         constexpr const char* allowedCharactersInId =
544             "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
545         if (id->empty() ||
546             id->find_first_not_of(allowedCharactersInId) != std::string::npos)
547         {
548             messages::propertyValueIncorrect(res, "Id", *id);
549             return false;
550         }
551         args.id = *id;
552     }
553 
554     if (name)
555     {
556         args.name = *name;
557     }
558 
559     if (reportingTypeStr)
560     {
561         std::string dbusReportingType = toDbusReportingType(*reportingTypeStr);
562         if (dbusReportingType.empty())
563         {
564             messages::propertyValueNotInList(res, *reportingTypeStr,
565                                              "MetricReportDefinitionType");
566             return false;
567         }
568         args.reportingType = dbusReportingType;
569     }
570 
571     if (reportUpdatesStr)
572     {
573         std::string dbusReportUpdates = toDbusReportUpdates(*reportUpdatesStr);
574         if (dbusReportUpdates.empty())
575         {
576             messages::propertyValueNotInList(res, *reportUpdatesStr,
577                                              "ReportUpdates");
578             return false;
579         }
580         args.reportUpdates = dbusReportUpdates;
581     }
582 
583     if (appendLimit)
584     {
585         args.appendLimit = *appendLimit;
586     }
587 
588     if (metricReportDefinitionEnabled)
589     {
590         args.metricReportDefinitionEnabled = *metricReportDefinitionEnabled;
591     }
592 
593     if (reportActionsStr)
594     {
595         if (!toDbusReportActions(res, *reportActionsStr, args.reportActions))
596         {
597             return false;
598         }
599     }
600 
601     if (reportingTypeStr == "Periodic")
602     {
603         if (!schedule)
604         {
605             messages::createFailedMissingReqProperties(res, "Schedule");
606             return false;
607         }
608 
609         std::string durationStr;
610         if (!json_util::readJson(*schedule, res, "RecurrenceInterval",
611                                  durationStr))
612         {
613             return false;
614         }
615 
616         std::optional<std::chrono::milliseconds> durationNum =
617             time_utils::fromDurationString(durationStr);
618         if (!durationNum || durationNum->count() < 0)
619         {
620             messages::propertyValueIncorrect(res, "RecurrenceInterval",
621                                              durationStr);
622             return false;
623         }
624         args.interval = static_cast<uint64_t>(durationNum->count());
625     }
626 
627     if (metrics)
628     {
629         if (!getUserMetrics(res, *metrics, args.metrics))
630         {
631             return false;
632         }
633     }
634 
635     return true;
636 }
637 
638 inline bool getChassisSensorNodeFromMetrics(
639     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
640     std::span<const AddReportArgs::MetricArgs> metrics,
641     boost::container::flat_set<std::pair<std::string, std::string>>& matched)
642 {
643     for (const auto& metric : metrics)
644     {
645         std::optional<IncorrectMetricUri> error =
646             getChassisSensorNode(metric.uris, matched);
647         if (error)
648         {
649             messages::propertyValueIncorrect(asyncResp->res, error->uri,
650                                              "MetricProperties/" +
651                                                  std::to_string(error->index));
652             return false;
653         }
654     }
655     return true;
656 }
657 
658 class AddReport
659 {
660   public:
661     AddReport(AddReportArgs argsIn,
662               const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn) :
663         asyncResp(asyncRespIn),
664         args(std::move(argsIn))
665     {}
666 
667     ~AddReport()
668     {
669         boost::asio::post(crow::connections::systemBus->get_io_context(),
670                           std::bind_front(&performAddReport, asyncResp, args,
671                                           std::move(uriToDbus)));
672     }
673 
674     static void performAddReport(
675         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
676         const AddReportArgs& args,
677         const boost::container::flat_map<std::string, std::string>& uriToDbus)
678     {
679         if (asyncResp->res.result() != boost::beast::http::status::ok)
680         {
681             return;
682         }
683 
684         telemetry::ReadingParameters readingParams;
685         readingParams.reserve(args.metrics.size());
686 
687         for (const auto& metric : args.metrics)
688         {
689             std::vector<
690                 std::tuple<sdbusplus::message::object_path, std::string>>
691                 sensorParams;
692             sensorParams.reserve(metric.uris.size());
693 
694             for (size_t i = 0; i < metric.uris.size(); i++)
695             {
696                 const std::string& uri = metric.uris[i];
697                 auto el = uriToDbus.find(uri);
698                 if (el == uriToDbus.end())
699                 {
700                     BMCWEB_LOG_ERROR(
701                         "Failed to find DBus sensor corresponding to URI {}",
702                         uri);
703                     messages::propertyValueNotInList(asyncResp->res, uri,
704                                                      "MetricProperties/" +
705                                                          std::to_string(i));
706                     return;
707                 }
708 
709                 const std::string& dbusPath = el->second;
710                 sensorParams.emplace_back(dbusPath, uri);
711             }
712 
713             readingParams.emplace_back(
714                 std::move(sensorParams), metric.collectionFunction,
715                 metric.collectionTimeScope, metric.collectionDuration);
716         }
717 
718         crow::connections::systemBus->async_method_call(
719             [asyncResp, id = args.id, uriToDbus](
720                 const boost::system::error_code& ec, const std::string&) {
721             if (ec == boost::system::errc::file_exists)
722             {
723                 messages::resourceAlreadyExists(
724                     asyncResp->res, "MetricReportDefinition", "Id", id);
725                 return;
726             }
727             if (ec == boost::system::errc::too_many_files_open)
728             {
729                 messages::createLimitReachedForResource(asyncResp->res);
730                 return;
731             }
732             if (ec == boost::system::errc::argument_list_too_long)
733             {
734                 nlohmann::json metricProperties = nlohmann::json::array();
735                 for (const auto& [uri, _] : uriToDbus)
736                 {
737                     metricProperties.emplace_back(uri);
738                 }
739                 messages::propertyValueIncorrect(
740                     asyncResp->res, metricProperties, "MetricProperties");
741                 return;
742             }
743             if (ec)
744             {
745                 messages::internalError(asyncResp->res);
746                 BMCWEB_LOG_ERROR("respHandler DBus error {}", ec);
747                 return;
748             }
749 
750             messages::created(asyncResp->res);
751             },
752             telemetry::service, "/xyz/openbmc_project/Telemetry/Reports",
753             "xyz.openbmc_project.Telemetry.ReportManager", "AddReport",
754             "TelemetryService/" + args.id, args.name, args.reportingType,
755             args.reportUpdates, args.appendLimit, args.reportActions,
756             args.interval, readingParams, args.metricReportDefinitionEnabled);
757     }
758 
759     AddReport(const AddReport&) = delete;
760     AddReport(AddReport&&) = delete;
761     AddReport& operator=(const AddReport&) = delete;
762     AddReport& operator=(AddReport&&) = delete;
763 
764     void insert(const std::map<std::string, std::string>& el)
765     {
766         uriToDbus.insert(el.begin(), el.end());
767     }
768 
769   private:
770     std::shared_ptr<bmcweb::AsyncResp> asyncResp;
771     AddReportArgs args;
772     boost::container::flat_map<std::string, std::string> uriToDbus{};
773 };
774 
775 class UpdateMetrics
776 {
777   public:
778     UpdateMetrics(std::string_view idIn,
779                   const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn) :
780         id(idIn),
781         asyncResp(asyncRespIn)
782     {}
783 
784     ~UpdateMetrics()
785     {
786         try
787         {
788             setReadingParams();
789         }
790         catch (const std::exception& e)
791         {
792             BMCWEB_LOG_ERROR("{}", e.what());
793         }
794         catch (...)
795         {
796             BMCWEB_LOG_ERROR("Unknown error");
797         }
798     }
799 
800     UpdateMetrics(const UpdateMetrics&) = delete;
801     UpdateMetrics(UpdateMetrics&&) = delete;
802     UpdateMetrics& operator=(const UpdateMetrics&) = delete;
803     UpdateMetrics& operator=(UpdateMetrics&&) = delete;
804 
805     std::string id;
806     std::map<std::string, std::string> metricPropertyToDbusPaths;
807 
808     void insert(const std::map<std::string, std::string>&
809                     additionalMetricPropertyToDbusPaths)
810     {
811         metricPropertyToDbusPaths.insert(
812             additionalMetricPropertyToDbusPaths.begin(),
813             additionalMetricPropertyToDbusPaths.end());
814     }
815 
816     void emplace(std::span<const std::tuple<sdbusplus::message::object_path,
817                                             std::string>>
818                      pathAndUri,
819                  const AddReportArgs::MetricArgs& metricArgs)
820     {
821         readingParamsUris.emplace_back(metricArgs.uris);
822         readingParams.emplace_back(
823             std::vector(pathAndUri.begin(), pathAndUri.end()),
824             metricArgs.collectionFunction, metricArgs.collectionTimeScope,
825             metricArgs.collectionDuration);
826     }
827 
828     void setReadingParams()
829     {
830         if (asyncResp->res.result() != boost::beast::http::status::ok)
831         {
832             return;
833         }
834 
835         for (size_t index = 0; index < readingParamsUris.size(); ++index)
836         {
837             std::span<const std::string> newUris = readingParamsUris[index];
838 
839             const std::optional<std::vector<
840                 std::tuple<sdbusplus::message::object_path, std::string>>>
841                 readingParam = sensorPathToUri(newUris);
842 
843             if (!readingParam)
844             {
845                 return;
846             }
847 
848             std::get<0>(readingParams[index]) = *readingParam;
849         }
850 
851         crow::connections::systemBus->async_method_call(
852             [asyncResp(this->asyncResp),
853              reportId = id](const boost::system::error_code& ec) {
854             if (!verifyCommonErrors(asyncResp->res, reportId, ec))
855             {
856                 return;
857             }
858             },
859             "xyz.openbmc_project.Telemetry", getDbusReportPath(id),
860             "org.freedesktop.DBus.Properties", "Set",
861             "xyz.openbmc_project.Telemetry.Report", "ReadingParameters",
862             dbus::utility::DbusVariantType{readingParams});
863     }
864 
865   private:
866     std::optional<
867         std::vector<std::tuple<sdbusplus::message::object_path, std::string>>>
868         sensorPathToUri(std::span<const std::string> uris) const
869     {
870         std::vector<std::tuple<sdbusplus::message::object_path, std::string>>
871             result;
872 
873         for (const std::string& uri : uris)
874         {
875             auto it = metricPropertyToDbusPaths.find(uri);
876             if (it == metricPropertyToDbusPaths.end())
877             {
878                 messages::propertyValueNotInList(asyncResp->res, uri,
879                                                  "MetricProperties");
880                 return {};
881             }
882             result.emplace_back(it->second, uri);
883         }
884 
885         return result;
886     }
887 
888     const std::shared_ptr<bmcweb::AsyncResp> asyncResp;
889     std::vector<std::vector<std::string>> readingParamsUris;
890     ReadingParameters readingParams{};
891 };
892 
893 inline void
894     setReportEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
895                      std::string_view id, bool enabled)
896 {
897     crow::connections::systemBus->async_method_call(
898         [asyncResp, id = std::string(id)](const boost::system::error_code& ec) {
899         if (!verifyCommonErrors(asyncResp->res, id, ec))
900         {
901             return;
902         }
903         },
904         "xyz.openbmc_project.Telemetry", getDbusReportPath(id),
905         "org.freedesktop.DBus.Properties", "Set",
906         "xyz.openbmc_project.Telemetry.Report", "Enabled",
907         dbus::utility::DbusVariantType{enabled});
908 }
909 
910 inline void setReportTypeAndInterval(
911     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, std::string_view id,
912     const std::string& reportingType, uint64_t recurrenceInterval)
913 {
914     crow::connections::systemBus->async_method_call(
915         [asyncResp, id = std::string(id)](const boost::system::error_code& ec) {
916         if (!verifyCommonErrors(asyncResp->res, id, ec))
917         {
918             return;
919         }
920         },
921         "xyz.openbmc_project.Telemetry", getDbusReportPath(id),
922         "xyz.openbmc_project.Telemetry.Report", "SetReportingProperties",
923         reportingType, recurrenceInterval);
924 }
925 
926 inline void
927     setReportUpdates(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
928                      std::string_view id, const std::string& reportUpdates)
929 {
930     crow::connections::systemBus->async_method_call(
931         [asyncResp, id = std::string(id)](const boost::system::error_code& ec) {
932         if (!verifyCommonErrors(asyncResp->res, id, ec))
933         {
934             return;
935         }
936         },
937         "xyz.openbmc_project.Telemetry", getDbusReportPath(id),
938         "org.freedesktop.DBus.Properties", "Set",
939         "xyz.openbmc_project.Telemetry.Report", "ReportUpdates",
940         dbus::utility::DbusVariantType{reportUpdates});
941 }
942 
943 inline void
944     setReportActions(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
945                      std::string_view id,
946                      const std::vector<std::string>& dbusReportActions)
947 {
948     crow::connections::systemBus->async_method_call(
949         [asyncResp, id = std::string(id)](const boost::system::error_code& ec) {
950         if (!verifyCommonErrors(asyncResp->res, id, ec))
951         {
952             return;
953         }
954         },
955         "xyz.openbmc_project.Telemetry", getDbusReportPath(id),
956         "org.freedesktop.DBus.Properties", "Set",
957         "xyz.openbmc_project.Telemetry.Report", "ReportActions",
958         dbus::utility::DbusVariantType{dbusReportActions});
959 }
960 
961 inline void
962     setReportMetrics(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
963                      std::string_view id, std::span<nlohmann::json> metrics)
964 {
965     sdbusplus::asio::getAllProperties(
966         *crow::connections::systemBus, telemetry::service,
967         telemetry::getDbusReportPath(id), telemetry::reportInterface,
968         [asyncResp, id = std::string(id),
969          redfishMetrics = std::vector<nlohmann::json>(metrics.begin(),
970                                                       metrics.end())](
971             boost::system::error_code ec,
972             const dbus::utility::DBusPropertiesMap& properties) mutable {
973         if (!redfish::telemetry::verifyCommonErrors(asyncResp->res, id, ec))
974         {
975             return;
976         }
977 
978         ReadingParameters readingParams;
979 
980         const bool success = sdbusplus::unpackPropertiesNoThrow(
981             dbus_utils::UnpackErrorPrinter(), properties, "ReadingParameters",
982             readingParams);
983 
984         if (!success)
985         {
986             messages::internalError(asyncResp->res);
987             return;
988         }
989 
990         auto updateMetricsReq = std::make_shared<UpdateMetrics>(id, asyncResp);
991 
992         boost::container::flat_set<std::pair<std::string, std::string>>
993             chassisSensors;
994 
995         size_t index = 0;
996         for (nlohmann::json& metric : redfishMetrics)
997         {
998             if (metric.is_null())
999             {
1000                 continue;
1001             }
1002 
1003             AddReportArgs::MetricArgs metricArgs;
1004             std::vector<
1005                 std::tuple<sdbusplus::message::object_path, std::string>>
1006                 pathAndUri;
1007 
1008             if (index < readingParams.size())
1009             {
1010                 const ReadingParameters::value_type& existing =
1011                     readingParams[index];
1012 
1013                 pathAndUri = std::get<0>(existing);
1014                 metricArgs.collectionFunction = std::get<1>(existing);
1015                 metricArgs.collectionTimeScope = std::get<2>(existing);
1016                 metricArgs.collectionDuration = std::get<3>(existing);
1017             }
1018 
1019             if (!getUserMetric(asyncResp->res, metric, metricArgs))
1020             {
1021                 return;
1022             }
1023 
1024             std::optional<IncorrectMetricUri> error =
1025                 getChassisSensorNode(metricArgs.uris, chassisSensors);
1026 
1027             if (error)
1028             {
1029                 messages::propertyValueIncorrect(
1030                     asyncResp->res, error->uri,
1031                     "MetricProperties/" + std::to_string(error->index));
1032                 return;
1033             }
1034 
1035             updateMetricsReq->emplace(pathAndUri, metricArgs);
1036             index++;
1037         }
1038 
1039         for (const auto& [chassis, sensorType] : chassisSensors)
1040         {
1041             retrieveUriToDbusMap(
1042                 chassis, sensorType,
1043                 [asyncResp, updateMetricsReq](
1044                     const boost::beast::http::status status,
1045                     const std::map<std::string, std::string>& uriToDbus) {
1046                 if (status != boost::beast::http::status::ok)
1047                 {
1048                     BMCWEB_LOG_ERROR(
1049                         "Failed to retrieve URI to dbus sensors map with err {}",
1050                         static_cast<unsigned>(status));
1051                     return;
1052                 }
1053                 updateMetricsReq->insert(uriToDbus);
1054                 });
1055         }
1056         });
1057 }
1058 
1059 inline void handleMetricReportDefinitionCollectionHead(
1060     App& app, const crow::Request& req,
1061     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1062 {
1063     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1064     {
1065         return;
1066     }
1067     asyncResp->res.addHeader(
1068         boost::beast::http::field::link,
1069         "</redfish/v1/JsonSchemas/MetricReportDefinitionCollection/MetricReportDefinitionCollection.json>; rel=describedby");
1070 }
1071 
1072 inline void handleMetricReportDefinitionCollectionGet(
1073     App& app, const crow::Request& req,
1074     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1075 {
1076     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1077     {
1078         return;
1079     }
1080     asyncResp->res.addHeader(
1081         boost::beast::http::field::link,
1082         "</redfish/v1/JsonSchemas/MetricReportDefinition/MetricReportDefinition.json>; rel=describedby");
1083 
1084     asyncResp->res.jsonValue["@odata.type"] =
1085         "#MetricReportDefinitionCollection."
1086         "MetricReportDefinitionCollection";
1087     asyncResp->res.jsonValue["@odata.id"] =
1088         "/redfish/v1/TelemetryService/MetricReportDefinitions";
1089     asyncResp->res.jsonValue["Name"] = "Metric Definition Collection";
1090     constexpr std::array<std::string_view, 1> interfaces{
1091         telemetry::reportInterface};
1092     collection_util::getCollectionMembers(
1093         asyncResp,
1094         boost::urls::url(
1095             "/redfish/v1/TelemetryService/MetricReportDefinitions"),
1096         interfaces, "/xyz/openbmc_project/Telemetry/Reports/TelemetryService");
1097 }
1098 
1099 inline void
1100     handleReportPatch(App& app, const crow::Request& req,
1101                       const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1102                       std::string_view id)
1103 {
1104     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1105     {
1106         return;
1107     }
1108 
1109     std::optional<std::string> reportingTypeStr;
1110     std::optional<std::string> reportUpdatesStr;
1111     std::optional<bool> metricReportDefinitionEnabled;
1112     std::optional<std::vector<nlohmann::json>> metrics;
1113     std::optional<std::vector<std::string>> reportActionsStr;
1114     std::optional<nlohmann::json> schedule;
1115 
1116     if (!json_util::readJsonPatch(
1117             req, asyncResp->res, "Metrics", metrics,
1118             "MetricReportDefinitionType", reportingTypeStr, "ReportUpdates",
1119             reportUpdatesStr, "ReportActions", reportActionsStr, "Schedule",
1120             schedule, "MetricReportDefinitionEnabled",
1121             metricReportDefinitionEnabled))
1122     {
1123         return;
1124     }
1125 
1126     if (metricReportDefinitionEnabled)
1127     {
1128         setReportEnabled(asyncResp, id, *metricReportDefinitionEnabled);
1129     }
1130 
1131     if (reportUpdatesStr)
1132     {
1133         std::string dbusReportUpdates = toDbusReportUpdates(*reportUpdatesStr);
1134         if (dbusReportUpdates.empty())
1135         {
1136             messages::propertyValueNotInList(asyncResp->res, *reportUpdatesStr,
1137                                              "ReportUpdates");
1138             return;
1139         }
1140         setReportUpdates(asyncResp, id, dbusReportUpdates);
1141     }
1142 
1143     if (reportActionsStr)
1144     {
1145         std::vector<std::string> dbusReportActions;
1146         if (!toDbusReportActions(asyncResp->res, *reportActionsStr,
1147                                  dbusReportActions))
1148         {
1149             return;
1150         }
1151         setReportActions(asyncResp, id, dbusReportActions);
1152     }
1153 
1154     if (reportingTypeStr || schedule)
1155     {
1156         std::string dbusReportingType;
1157         if (reportingTypeStr)
1158         {
1159             dbusReportingType = toDbusReportingType(*reportingTypeStr);
1160             if (dbusReportingType.empty())
1161             {
1162                 messages::propertyValueNotInList(asyncResp->res,
1163                                                  *reportingTypeStr,
1164                                                  "MetricReportDefinitionType");
1165                 return;
1166             }
1167         }
1168 
1169         uint64_t recurrenceInterval = std::numeric_limits<uint64_t>::max();
1170         if (schedule)
1171         {
1172             std::string durationStr;
1173             if (!json_util::readJson(*schedule, asyncResp->res,
1174                                      "RecurrenceInterval", durationStr))
1175             {
1176                 return;
1177             }
1178 
1179             std::optional<std::chrono::milliseconds> durationNum =
1180                 time_utils::fromDurationString(durationStr);
1181             if (!durationNum || durationNum->count() < 0)
1182             {
1183                 messages::propertyValueIncorrect(
1184                     asyncResp->res, "RecurrenceInterval", durationStr);
1185                 return;
1186             }
1187 
1188             recurrenceInterval = static_cast<uint64_t>(durationNum->count());
1189         }
1190 
1191         setReportTypeAndInterval(asyncResp, id, dbusReportingType,
1192                                  recurrenceInterval);
1193     }
1194 
1195     if (metrics)
1196     {
1197         setReportMetrics(asyncResp, id, *metrics);
1198     }
1199 }
1200 
1201 inline void
1202     handleReportDelete(App& app, const crow::Request& req,
1203                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1204                        std::string_view id)
1205 {
1206     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1207     {
1208         return;
1209     }
1210 
1211     const std::string reportPath = getDbusReportPath(id);
1212 
1213     crow::connections::systemBus->async_method_call(
1214         [asyncResp,
1215          reportId = std::string(id)](const boost::system::error_code& ec) {
1216         if (!verifyCommonErrors(asyncResp->res, reportId, ec))
1217         {
1218             return;
1219         }
1220         asyncResp->res.result(boost::beast::http::status::no_content);
1221         },
1222         service, reportPath, "xyz.openbmc_project.Object.Delete", "Delete");
1223 }
1224 } // namespace telemetry
1225 
1226 inline void handleMetricReportDefinitionsPost(
1227     App& app, const crow::Request& req,
1228     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1229 {
1230     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1231     {
1232         return;
1233     }
1234 
1235     telemetry::AddReportArgs args;
1236     if (!telemetry::getUserParameters(asyncResp->res, req, args))
1237     {
1238         return;
1239     }
1240 
1241     boost::container::flat_set<std::pair<std::string, std::string>>
1242         chassisSensors;
1243     if (!telemetry::getChassisSensorNodeFromMetrics(asyncResp, args.metrics,
1244                                                     chassisSensors))
1245     {
1246         return;
1247     }
1248 
1249     auto addReportReq = std::make_shared<telemetry::AddReport>(std::move(args),
1250                                                                asyncResp);
1251     for (const auto& [chassis, sensorType] : chassisSensors)
1252     {
1253         retrieveUriToDbusMap(
1254             chassis, sensorType,
1255             [asyncResp, addReportReq](
1256                 const boost::beast::http::status status,
1257                 const std::map<std::string, std::string>& uriToDbus) {
1258             if (status != boost::beast::http::status::ok)
1259             {
1260                 BMCWEB_LOG_ERROR(
1261                     "Failed to retrieve URI to dbus sensors map with err {}",
1262                     static_cast<unsigned>(status));
1263                 return;
1264             }
1265             addReportReq->insert(uriToDbus);
1266             });
1267     }
1268 }
1269 
1270 inline void
1271     handleMetricReportHead(App& app, const crow::Request& req,
1272                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1273                            const std::string& /*id*/)
1274 {
1275     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1276     {
1277         return;
1278     }
1279     asyncResp->res.addHeader(
1280         boost::beast::http::field::link,
1281         "</redfish/v1/JsonSchemas/MetricReport/MetricReport.json>; rel=describedby");
1282 }
1283 
1284 inline void
1285     handleMetricReportGet(App& app, const crow::Request& req,
1286                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1287                           const std::string& id)
1288 {
1289     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1290     {
1291         return;
1292     }
1293     asyncResp->res.addHeader(
1294         boost::beast::http::field::link,
1295         "</redfish/v1/JsonSchemas/MetricReport/MetricReport.json>; rel=describedby");
1296 
1297     sdbusplus::asio::getAllProperties(
1298         *crow::connections::systemBus, telemetry::service,
1299         telemetry::getDbusReportPath(id), telemetry::reportInterface,
1300         [asyncResp, id](const boost::system::error_code& ec,
1301                         const dbus::utility::DBusPropertiesMap& properties) {
1302         if (!redfish::telemetry::verifyCommonErrors(asyncResp->res, id, ec))
1303         {
1304             return;
1305         }
1306 
1307         telemetry::fillReportDefinition(asyncResp, id, properties);
1308         });
1309 }
1310 
1311 inline void handleMetricReportDelete(
1312     App& app, const crow::Request& req,
1313     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
1314 
1315 {
1316     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1317     {
1318         return;
1319     }
1320 
1321     const std::string reportPath = telemetry::getDbusReportPath(id);
1322 
1323     crow::connections::systemBus->async_method_call(
1324         [asyncResp, id](const boost::system::error_code& ec) {
1325         /*
1326          * boost::system::errc and std::errc are missing value
1327          * for EBADR error that is defined in Linux.
1328          */
1329         if (ec.value() == EBADR)
1330         {
1331             messages::resourceNotFound(asyncResp->res, "MetricReportDefinition",
1332                                        id);
1333             return;
1334         }
1335 
1336         if (ec)
1337         {
1338             BMCWEB_LOG_ERROR("respHandler DBus error {}", ec);
1339             messages::internalError(asyncResp->res);
1340             return;
1341         }
1342 
1343         asyncResp->res.result(boost::beast::http::status::no_content);
1344         },
1345         telemetry::service, reportPath, "xyz.openbmc_project.Object.Delete",
1346         "Delete");
1347 }
1348 
1349 inline void requestRoutesMetricReportDefinitionCollection(App& app)
1350 {
1351     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/")
1352         .privileges(redfish::privileges::headMetricReportDefinitionCollection)
1353         .methods(boost::beast::http::verb::head)(std::bind_front(
1354             telemetry::handleMetricReportDefinitionCollectionHead,
1355             std::ref(app)));
1356 
1357     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/")
1358         .privileges(redfish::privileges::getMetricReportDefinitionCollection)
1359         .methods(boost::beast::http::verb::get)(std::bind_front(
1360             telemetry::handleMetricReportDefinitionCollectionGet,
1361             std::ref(app)));
1362 
1363     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/")
1364         .privileges(redfish::privileges::postMetricReportDefinitionCollection)
1365         .methods(boost::beast::http::verb::post)(
1366             std::bind_front(handleMetricReportDefinitionsPost, std::ref(app)));
1367 }
1368 
1369 inline void requestRoutesMetricReportDefinition(App& app)
1370 {
1371     BMCWEB_ROUTE(app,
1372                  "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/")
1373         .privileges(redfish::privileges::getMetricReportDefinition)
1374         .methods(boost::beast::http::verb::head)(
1375             std::bind_front(handleMetricReportHead, std::ref(app)));
1376 
1377     BMCWEB_ROUTE(app,
1378                  "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/")
1379         .privileges(redfish::privileges::getMetricReportDefinition)
1380         .methods(boost::beast::http::verb::get)(
1381             std::bind_front(handleMetricReportGet, std::ref(app)));
1382 
1383     BMCWEB_ROUTE(app,
1384                  "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/")
1385         .privileges(redfish::privileges::deleteMetricReportDefinition)
1386         .methods(boost::beast::http::verb::delete_)(
1387             std::bind_front(handleMetricReportDelete, std::ref(app)));
1388 
1389     BMCWEB_ROUTE(app,
1390                  "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/")
1391         .privileges(redfish::privileges::patchMetricReportDefinition)
1392         .methods(boost::beast::http::verb::patch)(
1393             std::bind_front(telemetry::handleReportPatch, std::ref(app)));
1394 }
1395 } // namespace redfish
1396