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