1 #pragma once
2 
3 #include "app.hpp"
4 #include "generated/enums/log_service.hpp"
5 #include "query.hpp"
6 #include "registries/openbmc_message_registry.hpp"
7 #include "registries/privilege_registry.hpp"
8 #include "utils/time_utils.hpp"
9 
10 #include <cstdint>
11 #include <memory>
12 #include <string_view>
13 #include <utility>
14 #include <vector>
15 
16 namespace redfish
17 {
18 
19 inline void handleSystemsLogServicesPostCodesGet(
20     App& app, const crow::Request& req,
21     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
22     const std::string& systemName)
23 {
24     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
25     {
26         return;
27     }
28     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
29     {
30         // Option currently returns no systems.  TBD
31         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
32                                    systemName);
33         return;
34     }
35     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
36     {
37         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
38                                    systemName);
39         return;
40     }
41     asyncResp->res.jsonValue["@odata.id"] =
42         std::format("/redfish/v1/Systems/{}/LogServices/PostCodes",
43                     BMCWEB_REDFISH_SYSTEM_URI_NAME);
44     asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService";
45     asyncResp->res.jsonValue["Name"] = "POST Code Log Service";
46     asyncResp->res.jsonValue["Description"] = "POST Code Log Service";
47     asyncResp->res.jsonValue["Id"] = "PostCodes";
48     asyncResp->res.jsonValue["OverWritePolicy"] =
49         log_service::OverWritePolicy::WrapsWhenFull;
50     asyncResp->res.jsonValue["Entries"]["@odata.id"] =
51         std::format("/redfish/v1/Systems/{}/LogServices/PostCodes/Entries",
52                     BMCWEB_REDFISH_SYSTEM_URI_NAME);
53 
54     std::pair<std::string, std::string> redfishDateTimeOffset =
55         redfish::time_utils::getDateTimeOffsetNow();
56     asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
57     asyncResp->res.jsonValue["DateTimeLocalOffset"] =
58         redfishDateTimeOffset.second;
59 
60     asyncResp->res
61         .jsonValue["Actions"]["#LogService.ClearLog"]["target"] = std::format(
62         "/redfish/v1/Systems/{}/LogServices/PostCodes/Actions/LogService.ClearLog",
63         BMCWEB_REDFISH_SYSTEM_URI_NAME);
64 }
65 
66 inline void handleSystemsLogServicesPostCodesPost(
67     App& app, const crow::Request& req,
68     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
69     const std::string& systemName)
70 {
71     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
72     {
73         return;
74     }
75     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
76     {
77         // Option currently returns no systems.  TBD
78         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
79                                    systemName);
80         return;
81     }
82     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
83     {
84         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
85                                    systemName);
86         return;
87     }
88     BMCWEB_LOG_DEBUG("Do delete all postcodes entries.");
89 
90     // Make call to post-code service to request clear all
91     crow::connections::systemBus->async_method_call(
92         [asyncResp](const boost::system::error_code& ec) {
93             if (ec)
94             {
95                 // TODO Handle for specific error code
96                 BMCWEB_LOG_ERROR("doClearPostCodes resp_handler got error {}",
97                                  ec);
98                 asyncResp->res.result(
99                     boost::beast::http::status::internal_server_error);
100                 messages::internalError(asyncResp->res);
101                 return;
102             }
103             messages::success(asyncResp->res);
104         },
105         "xyz.openbmc_project.State.Boot.PostCode0",
106         "/xyz/openbmc_project/State/Boot/PostCode0",
107         "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
108 }
109 
110 /**
111  * @brief Parse post code ID and get the current value and index value
112  *        eg: postCodeID=B1-2, currentValue=1, index=2
113  *
114  * @param[in]  postCodeID     Post Code ID
115  * @param[out] currentValue   Current value
116  * @param[out] index          Index value
117  *
118  * @return bool true if the parsing is successful, false the parsing fails
119  */
120 inline bool parsePostCode(std::string_view postCodeID, uint64_t& currentValue,
121                           uint16_t& index)
122 {
123     std::vector<std::string> split;
124     bmcweb::split(split, postCodeID, '-');
125     if (split.size() != 2)
126     {
127         return false;
128     }
129     std::string_view postCodeNumber = split[0];
130     if (postCodeNumber.size() < 2)
131     {
132         return false;
133     }
134     if (postCodeNumber[0] != 'B')
135     {
136         return false;
137     }
138     postCodeNumber.remove_prefix(1);
139     auto [ptrIndex, ecIndex] =
140         std::from_chars(postCodeNumber.begin(), postCodeNumber.end(), index);
141     if (ptrIndex != postCodeNumber.end() || ecIndex != std::errc())
142     {
143         return false;
144     }
145 
146     std::string_view postCodeIndex = split[1];
147 
148     auto [ptrValue, ecValue] = std::from_chars(
149         postCodeIndex.begin(), postCodeIndex.end(), currentValue);
150 
151     return ptrValue == postCodeIndex.end() && ecValue == std::errc();
152 }
153 
154 static bool fillPostCodeEntry(
155     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
156     const boost::container::flat_map<
157         uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& postcode,
158     const uint16_t bootIndex, const uint64_t codeIndex = 0,
159     const uint64_t skip = 0, const uint64_t top = 0)
160 {
161     // Get the Message from the MessageRegistry
162     const registries::Message* message =
163         registries::getMessage("OpenBMC.0.2.BIOSPOSTCode");
164     if (message == nullptr)
165     {
166         BMCWEB_LOG_ERROR("Couldn't find known message?");
167         return false;
168     }
169     uint64_t currentCodeIndex = 0;
170     uint64_t firstCodeTimeUs = 0;
171     for (const std::pair<uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>&
172              code : postcode)
173     {
174         currentCodeIndex++;
175         std::string postcodeEntryID =
176             "B" + std::to_string(bootIndex) + "-" +
177             std::to_string(currentCodeIndex); // 1 based index in EntryID string
178 
179         uint64_t usecSinceEpoch = code.first;
180         uint64_t usTimeOffset = 0;
181 
182         if (1 == currentCodeIndex)
183         { // already incremented
184             firstCodeTimeUs = code.first;
185         }
186         else
187         {
188             usTimeOffset = code.first - firstCodeTimeUs;
189         }
190 
191         // skip if no specific codeIndex is specified and currentCodeIndex does
192         // not fall between top and skip
193         if ((codeIndex == 0) &&
194             (currentCodeIndex <= skip || currentCodeIndex > top))
195         {
196             continue;
197         }
198 
199         // skip if a specific codeIndex is specified and does not match the
200         // currentIndex
201         if ((codeIndex > 0) && (currentCodeIndex != codeIndex))
202         {
203             // This is done for simplicity. 1st entry is needed to calculate
204             // time offset. To improve efficiency, one can get to the entry
205             // directly (possibly with flatmap's nth method)
206             continue;
207         }
208 
209         // currentCodeIndex is within top and skip or equal to specified code
210         // index
211 
212         // Get the Created time from the timestamp
213         std::string entryTimeStr;
214         entryTimeStr = redfish::time_utils::getDateTimeUintUs(usecSinceEpoch);
215 
216         // assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex)
217         std::ostringstream hexCode;
218         hexCode << "0x" << std::setfill('0') << std::setw(2) << std::hex
219                 << std::get<0>(code.second);
220         std::ostringstream timeOffsetStr;
221         // Set Fixed -Point Notation
222         timeOffsetStr << std::fixed;
223         // Set precision to 4 digits
224         timeOffsetStr << std::setprecision(4);
225         // Add double to stream
226         timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000;
227 
228         std::string bootIndexStr = std::to_string(bootIndex);
229         std::string timeOffsetString = timeOffsetStr.str();
230         std::string hexCodeStr = hexCode.str();
231 
232         std::array<std::string_view, 3> messageArgs = {
233             bootIndexStr, timeOffsetString, hexCodeStr};
234 
235         std::string msg =
236             redfish::registries::fillMessageArgs(messageArgs, message->message);
237         if (msg.empty())
238         {
239             messages::internalError(asyncResp->res);
240             return false;
241         }
242 
243         // Get Severity template from message registry
244         std::string severity;
245         if (message != nullptr)
246         {
247             severity = message->messageSeverity;
248         }
249 
250         // Format entry
251         nlohmann::json::object_t bmcLogEntry;
252         bmcLogEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
253         bmcLogEntry["@odata.id"] = boost::urls::format(
254             "/redfish/v1/Systems/{}/LogServices/PostCodes/Entries/{}",
255             BMCWEB_REDFISH_SYSTEM_URI_NAME, postcodeEntryID);
256         bmcLogEntry["Name"] = "POST Code Log Entry";
257         bmcLogEntry["Id"] = postcodeEntryID;
258         bmcLogEntry["Message"] = std::move(msg);
259         bmcLogEntry["MessageId"] = "OpenBMC.0.2.BIOSPOSTCode";
260         bmcLogEntry["MessageArgs"] = messageArgs;
261         bmcLogEntry["EntryType"] = "Event";
262         bmcLogEntry["Severity"] = std::move(severity);
263         bmcLogEntry["Created"] = entryTimeStr;
264         if (!std::get<std::vector<uint8_t>>(code.second).empty())
265         {
266             bmcLogEntry["AdditionalDataURI"] =
267                 std::format(
268                     "/redfish/v1/Systems/{}/LogServices/PostCodes/Entries/",
269                     BMCWEB_REDFISH_SYSTEM_URI_NAME) +
270                 postcodeEntryID + "/attachment";
271         }
272 
273         // codeIndex is only specified when querying single entry, return only
274         // that entry in this case
275         if (codeIndex != 0)
276         {
277             asyncResp->res.jsonValue.update(bmcLogEntry);
278             return true;
279         }
280 
281         nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
282         logEntryArray.emplace_back(std::move(bmcLogEntry));
283     }
284 
285     // Return value is always false when querying multiple entries
286     return false;
287 }
288 
289 inline void
290     getPostCodeForEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
291                         const std::string& entryId)
292 {
293     uint16_t bootIndex = 0;
294     uint64_t codeIndex = 0;
295     if (!parsePostCode(entryId, codeIndex, bootIndex))
296     {
297         // Requested ID was not found
298         messages::resourceNotFound(asyncResp->res, "LogEntry", entryId);
299         return;
300     }
301 
302     if (bootIndex == 0 || codeIndex == 0)
303     {
304         // 0 is an invalid index
305         messages::resourceNotFound(asyncResp->res, "LogEntry", entryId);
306         return;
307     }
308 
309     crow::connections::systemBus->async_method_call(
310         [asyncResp, entryId, bootIndex,
311          codeIndex](const boost::system::error_code& ec,
312                     const boost::container::flat_map<
313                         uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>&
314                         postcode) {
315             if (ec)
316             {
317                 BMCWEB_LOG_DEBUG("DBUS POST CODE PostCode response error");
318                 messages::internalError(asyncResp->res);
319                 return;
320             }
321 
322             if (postcode.empty())
323             {
324                 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId);
325                 return;
326             }
327 
328             if (!fillPostCodeEntry(asyncResp, postcode, bootIndex, codeIndex))
329             {
330                 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId);
331                 return;
332             }
333         },
334         "xyz.openbmc_project.State.Boot.PostCode0",
335         "/xyz/openbmc_project/State/Boot/PostCode0",
336         "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp",
337         bootIndex);
338 }
339 
340 inline void
341     getPostCodeForBoot(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
342                        const uint16_t bootIndex, const uint16_t bootCount,
343                        const uint64_t entryCount, size_t skip, size_t top)
344 {
345     crow::connections::systemBus->async_method_call(
346         [asyncResp, bootIndex, bootCount, entryCount, skip,
347          top](const boost::system::error_code& ec,
348               const boost::container::flat_map<
349                   uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>&
350                   postcode) {
351             if (ec)
352             {
353                 BMCWEB_LOG_DEBUG("DBUS POST CODE PostCode response error");
354                 messages::internalError(asyncResp->res);
355                 return;
356             }
357 
358             uint64_t endCount = entryCount;
359             if (!postcode.empty())
360             {
361                 endCount = entryCount + postcode.size();
362                 if (skip < endCount && (top + skip) > entryCount)
363                 {
364                     uint64_t thisBootSkip =
365                         std::max(static_cast<uint64_t>(skip), entryCount) -
366                         entryCount;
367                     uint64_t thisBootTop =
368                         std::min(static_cast<uint64_t>(top + skip), endCount) -
369                         entryCount;
370 
371                     fillPostCodeEntry(asyncResp, postcode, bootIndex, 0,
372                                       thisBootSkip, thisBootTop);
373                 }
374                 asyncResp->res.jsonValue["Members@odata.count"] = endCount;
375             }
376 
377             // continue to previous bootIndex
378             if (bootIndex < bootCount)
379             {
380                 getPostCodeForBoot(asyncResp,
381                                    static_cast<uint16_t>(bootIndex + 1),
382                                    bootCount, endCount, skip, top);
383             }
384             else if (skip + top < endCount)
385             {
386                 asyncResp->res.jsonValue["Members@odata.nextLink"] =
387                     std::format(
388                         "/redfish/v1/Systems/{}/LogServices/PostCodes/Entries?$skip=",
389                         BMCWEB_REDFISH_SYSTEM_URI_NAME) +
390                     std::to_string(skip + top);
391             }
392         },
393         "xyz.openbmc_project.State.Boot.PostCode0",
394         "/xyz/openbmc_project/State/Boot/PostCode0",
395         "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp",
396         bootIndex);
397 }
398 
399 inline void
400     getCurrentBootNumber(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
401                          size_t skip, size_t top)
402 {
403     uint64_t entryCount = 0;
404     sdbusplus::asio::getProperty<uint16_t>(
405         *crow::connections::systemBus,
406         "xyz.openbmc_project.State.Boot.PostCode0",
407         "/xyz/openbmc_project/State/Boot/PostCode0",
408         "xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount",
409         [asyncResp, entryCount, skip,
410          top](const boost::system::error_code& ec, const uint16_t bootCount) {
411             if (ec)
412             {
413                 BMCWEB_LOG_DEBUG("DBUS response error {}", ec);
414                 messages::internalError(asyncResp->res);
415                 return;
416             }
417             getPostCodeForBoot(asyncResp, 1, bootCount, entryCount, skip, top);
418         });
419 }
420 
421 inline void handleSystemsLogServicesPostCodesEntriesGet(
422     App& app, const crow::Request& req,
423     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
424     const std::string& systemName)
425 {
426     query_param::QueryCapabilities capabilities = {
427         .canDelegateTop = true,
428         .canDelegateSkip = true,
429     };
430     query_param::Query delegatedQuery;
431     if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
432                                                   delegatedQuery, capabilities))
433     {
434         return;
435     }
436     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
437     {
438         // Option currently returns no systems.  TBD
439         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
440                                    systemName);
441         return;
442     }
443 
444     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
445     {
446         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
447                                    systemName);
448         return;
449     }
450     asyncResp->res.jsonValue["@odata.type"] =
451         "#LogEntryCollection.LogEntryCollection";
452     asyncResp->res.jsonValue["@odata.id"] =
453         std::format("/redfish/v1/Systems/{}/LogServices/PostCodes/Entries",
454                     BMCWEB_REDFISH_SYSTEM_URI_NAME);
455     asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries";
456     asyncResp->res.jsonValue["Description"] =
457         "Collection of POST Code Log Entries";
458     asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
459     asyncResp->res.jsonValue["Members@odata.count"] = 0;
460     size_t skip = delegatedQuery.skip.value_or(0);
461     size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
462     getCurrentBootNumber(asyncResp, skip, top);
463 }
464 
465 inline void handleSystemsLogServicesPostCodesEntriesEntryAdditionalDataGet(
466     App& app, const crow::Request& req,
467     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
468     const std::string& systemName, const std::string& postCodeID)
469 {
470     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
471     {
472         return;
473     }
474     if (!http_helpers::isContentTypeAllowed(
475             req.getHeaderValue("Accept"),
476             http_helpers::ContentType::OctetStream, true))
477     {
478         asyncResp->res.result(boost::beast::http::status::bad_request);
479         return;
480     }
481     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
482     {
483         // Option currently returns no systems.  TBD
484         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
485                                    systemName);
486         return;
487     }
488     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
489     {
490         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
491                                    systemName);
492         return;
493     }
494 
495     uint64_t currentValue = 0;
496     uint16_t index = 0;
497     if (!parsePostCode(postCodeID, currentValue, index))
498     {
499         messages::resourceNotFound(asyncResp->res, "LogEntry", postCodeID);
500         return;
501     }
502 
503     crow::connections::systemBus->async_method_call(
504         [asyncResp, postCodeID, currentValue](
505             const boost::system::error_code& ec,
506             const std::vector<std::tuple<uint64_t, std::vector<uint8_t>>>&
507                 postcodes) {
508             if (ec.value() == EBADR)
509             {
510                 messages::resourceNotFound(asyncResp->res, "LogEntry",
511                                            postCodeID);
512                 return;
513             }
514             if (ec)
515             {
516                 BMCWEB_LOG_DEBUG("DBUS response error {}", ec);
517                 messages::internalError(asyncResp->res);
518                 return;
519             }
520 
521             size_t value = static_cast<size_t>(currentValue) - 1;
522             if (value == std::string::npos || postcodes.size() < currentValue)
523             {
524                 BMCWEB_LOG_WARNING("Wrong currentValue value");
525                 messages::resourceNotFound(asyncResp->res, "LogEntry",
526                                            postCodeID);
527                 return;
528             }
529 
530             const auto& [tID, c] = postcodes[value];
531             if (c.empty())
532             {
533                 BMCWEB_LOG_WARNING("No found post code data");
534                 messages::resourceNotFound(asyncResp->res, "LogEntry",
535                                            postCodeID);
536                 return;
537             }
538             // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
539             const char* d = reinterpret_cast<const char*>(c.data());
540             std::string_view strData(d, c.size());
541 
542             asyncResp->res.addHeader(boost::beast::http::field::content_type,
543                                      "application/octet-stream");
544             asyncResp->res.addHeader(
545                 boost::beast::http::field::content_transfer_encoding, "Base64");
546             asyncResp->res.write(crow::utility::base64encode(strData));
547         },
548         "xyz.openbmc_project.State.Boot.PostCode0",
549         "/xyz/openbmc_project/State/Boot/PostCode0",
550         "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodes", index);
551 }
552 
553 inline void handleSystemsLogServicesPostCodesEntriesEntryGet(
554     App& app, const crow::Request& req,
555     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
556     const std::string& systemName, const std::string& targetID)
557 {
558     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
559     {
560         return;
561     }
562     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
563     {
564         // Option currently returns no systems.  TBD
565         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
566                                    systemName);
567         return;
568     }
569     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
570     {
571         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
572                                    systemName);
573         return;
574     }
575 
576     getPostCodeForEntry(asyncResp, targetID);
577 }
578 
579 inline void requestRoutesSystemsLogServicesPostCode(App& app)
580 {
581     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/PostCodes/")
582         .privileges(redfish::privileges::getLogService)
583         .methods(boost::beast::http::verb::get)(std::bind_front(
584             handleSystemsLogServicesPostCodesGet, std::ref(app)));
585 
586     BMCWEB_ROUTE(
587         app,
588         "/redfish/v1/Systems/<str>/LogServices/PostCodes/Actions/LogService.ClearLog/")
589         // The following privilege is incorrect;  It should be ConfigureManager
590         //.privileges(redfish::privileges::postLogService)
591         .privileges({{"ConfigureComponents"}})
592         .methods(boost::beast::http::verb::post)(std::bind_front(
593             handleSystemsLogServicesPostCodesPost, std::ref(app)));
594 
595     BMCWEB_ROUTE(app,
596                  "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/")
597         .privileges(redfish::privileges::getLogEntryCollection)
598         .methods(boost::beast::http::verb::get)(std::bind_front(
599             handleSystemsLogServicesPostCodesEntriesGet, std::ref(app)));
600 
601     BMCWEB_ROUTE(
602         app, "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/<str>/")
603         .privileges(redfish::privileges::getLogEntry)
604         .methods(boost::beast::http::verb::get)(std::bind_front(
605             handleSystemsLogServicesPostCodesEntriesEntryGet, std::ref(app)));
606 
607     BMCWEB_ROUTE(
608         app,
609         "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/<str>/attachment/")
610         .privileges(redfish::privileges::getLogEntry)
611         .methods(boost::beast::http::verb::get)(std::bind_front(
612             handleSystemsLogServicesPostCodesEntriesEntryAdditionalDataGet,
613             std::ref(app)));
614 }
615 } // namespace redfish
616