1 /*
2 Copyright (c) 2018 Intel Corporation
3 
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7 
8       http://www.apache.org/licenses/LICENSE-2.0
9 
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16 #pragma once
17 
18 #include "bmcweb_config.h"
19 
20 #include "app.hpp"
21 #include "dbus_utility.hpp"
22 #include "error_messages.hpp"
23 #include "generated/enums/update_service.hpp"
24 #include "multipart_parser.hpp"
25 #include "ossl_random.hpp"
26 #include "query.hpp"
27 #include "registries/privilege_registry.hpp"
28 #include "task.hpp"
29 #include "task_messages.hpp"
30 #include "utils/collection.hpp"
31 #include "utils/dbus_utils.hpp"
32 #include "utils/json_utils.hpp"
33 #include "utils/sw_utils.hpp"
34 
35 #include <sys/mman.h>
36 
37 #include <boost/system/error_code.hpp>
38 #include <boost/url/format.hpp>
39 #include <sdbusplus/asio/property.hpp>
40 #include <sdbusplus/bus/match.hpp>
41 #include <sdbusplus/unpack_properties.hpp>
42 
43 #include <array>
44 #include <cstddef>
45 #include <filesystem>
46 #include <functional>
47 #include <iterator>
48 #include <memory>
49 #include <optional>
50 #include <string>
51 #include <string_view>
52 #include <unordered_map>
53 #include <vector>
54 
55 namespace redfish
56 {
57 
58 // Match signals added on software path
59 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
60 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateMatcher;
61 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
62 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateErrorMatcher;
63 // Only allow one update at a time
64 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
65 static bool fwUpdateInProgress = false;
66 // Timer for software available
67 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
68 static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer;
69 
70 struct MemoryFileDescriptor
71 {
72     int fd = -1;
73 
74     explicit MemoryFileDescriptor(const std::string& filename) :
75         fd(memfd_create(filename.c_str(), 0))
76     {}
77 
78     MemoryFileDescriptor(const MemoryFileDescriptor&) = default;
79     MemoryFileDescriptor(MemoryFileDescriptor&& other) noexcept : fd(other.fd)
80     {
81         other.fd = -1;
82     }
83     MemoryFileDescriptor& operator=(const MemoryFileDescriptor&) = delete;
84     MemoryFileDescriptor& operator=(MemoryFileDescriptor&&) = default;
85 
86     ~MemoryFileDescriptor()
87     {
88         if (fd != -1)
89         {
90             close(fd);
91         }
92     }
93 
94     bool rewind() const
95     {
96         if (lseek(fd, 0, SEEK_SET) == -1)
97         {
98             BMCWEB_LOG_ERROR("Failed to seek to beginning of image memfd");
99             return false;
100         }
101         return true;
102     }
103 };
104 
105 inline void cleanUp()
106 {
107     fwUpdateInProgress = false;
108     fwUpdateMatcher = nullptr;
109     fwUpdateErrorMatcher = nullptr;
110 }
111 
112 inline void activateImage(const std::string& objPath,
113                           const std::string& service)
114 {
115     BMCWEB_LOG_DEBUG("Activate image for {} {}", objPath, service);
116     sdbusplus::asio::setProperty(
117         *crow::connections::systemBus, service, objPath,
118         "xyz.openbmc_project.Software.Activation", "RequestedActivation",
119         "xyz.openbmc_project.Software.Activation.RequestedActivations.Active",
120         [](const boost::system::error_code& ec) {
121             if (ec)
122             {
123                 BMCWEB_LOG_DEBUG("error_code = {}", ec);
124                 BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
125             }
126         });
127 }
128 
129 inline bool handleCreateTask(const boost::system::error_code& ec2,
130                              sdbusplus::message_t& msg,
131                              const std::shared_ptr<task::TaskData>& taskData)
132 {
133     if (ec2)
134     {
135         return task::completed;
136     }
137 
138     std::string iface;
139     dbus::utility::DBusPropertiesMap values;
140 
141     std::string index = std::to_string(taskData->index);
142     msg.read(iface, values);
143 
144     if (iface == "xyz.openbmc_project.Software.Activation")
145     {
146         const std::string* state = nullptr;
147         for (const auto& property : values)
148         {
149             if (property.first == "Activation")
150             {
151                 state = std::get_if<std::string>(&property.second);
152                 if (state == nullptr)
153                 {
154                     taskData->messages.emplace_back(messages::internalError());
155                     return task::completed;
156                 }
157             }
158         }
159 
160         if (state == nullptr)
161         {
162             return !task::completed;
163         }
164 
165         if (state->ends_with("Invalid") || state->ends_with("Failed"))
166         {
167             taskData->state = "Exception";
168             taskData->status = "Warning";
169             taskData->messages.emplace_back(messages::taskAborted(index));
170             return task::completed;
171         }
172 
173         if (state->ends_with("Staged"))
174         {
175             taskData->state = "Stopping";
176             taskData->messages.emplace_back(messages::taskPaused(index));
177 
178             // its staged, set a long timer to
179             // allow them time to complete the
180             // update (probably cycle the
181             // system) if this expires then
182             // task will be canceled
183             taskData->extendTimer(std::chrono::hours(5));
184             return !task::completed;
185         }
186 
187         if (state->ends_with("Active"))
188         {
189             taskData->messages.emplace_back(messages::taskCompletedOK(index));
190             taskData->state = "Completed";
191             return task::completed;
192         }
193     }
194     else if (iface == "xyz.openbmc_project.Software.ActivationProgress")
195     {
196         const uint8_t* progress = nullptr;
197         for (const auto& property : values)
198         {
199             if (property.first == "Progress")
200             {
201                 progress = std::get_if<uint8_t>(&property.second);
202                 if (progress == nullptr)
203                 {
204                     taskData->messages.emplace_back(messages::internalError());
205                     return task::completed;
206                 }
207             }
208         }
209 
210         if (progress == nullptr)
211         {
212             return !task::completed;
213         }
214         taskData->percentComplete = *progress;
215         taskData->messages.emplace_back(
216             messages::taskProgressChanged(index, *progress));
217 
218         // if we're getting status updates it's
219         // still alive, update timer
220         taskData->extendTimer(std::chrono::minutes(5));
221     }
222 
223     // as firmware update often results in a
224     // reboot, the task  may never "complete"
225     // unless it is an error
226 
227     return !task::completed;
228 }
229 
230 inline void createTask(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
231                        task::Payload&& payload,
232                        const sdbusplus::message::object_path& objPath)
233 {
234     std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
235         std::bind_front(handleCreateTask),
236         "type='signal',interface='org.freedesktop.DBus.Properties',"
237         "member='PropertiesChanged',path='" +
238             objPath.str + "'");
239     task->startTimer(std::chrono::minutes(5));
240     task->populateResp(asyncResp->res);
241     task->payload.emplace(std::move(payload));
242 }
243 
244 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
245 // then no asyncResp updates will occur
246 inline void
247     softwareInterfaceAdded(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
248                            sdbusplus::message_t& m, task::Payload&& payload)
249 {
250     dbus::utility::DBusInterfacesMap interfacesProperties;
251 
252     sdbusplus::message::object_path objPath;
253 
254     m.read(objPath, interfacesProperties);
255 
256     BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
257     for (const auto& interface : interfacesProperties)
258     {
259         BMCWEB_LOG_DEBUG("interface = {}", interface.first);
260 
261         if (interface.first == "xyz.openbmc_project.Software.Activation")
262         {
263             // Retrieve service and activate
264             constexpr std::array<std::string_view, 1> interfaces = {
265                 "xyz.openbmc_project.Software.Activation"};
266             dbus::utility::getDbusObject(
267                 objPath.str, interfaces,
268                 [objPath, asyncResp, payload(std::move(payload))](
269                     const boost::system::error_code& ec,
270                     const std::vector<
271                         std::pair<std::string, std::vector<std::string>>>&
272                         objInfo) mutable {
273                     if (ec)
274                     {
275                         BMCWEB_LOG_DEBUG("error_code = {}", ec);
276                         BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
277                         if (asyncResp)
278                         {
279                             messages::internalError(asyncResp->res);
280                         }
281                         cleanUp();
282                         return;
283                     }
284                     // Ensure we only got one service back
285                     if (objInfo.size() != 1)
286                     {
287                         BMCWEB_LOG_ERROR("Invalid Object Size {}",
288                                          objInfo.size());
289                         if (asyncResp)
290                         {
291                             messages::internalError(asyncResp->res);
292                         }
293                         cleanUp();
294                         return;
295                     }
296                     // cancel timer only when
297                     // xyz.openbmc_project.Software.Activation interface
298                     // is added
299                     fwAvailableTimer = nullptr;
300 
301                     activateImage(objPath.str, objInfo[0].first);
302                     if (asyncResp)
303                     {
304                         createTask(asyncResp, std::move(payload), objPath);
305                     }
306                     fwUpdateInProgress = false;
307                 });
308 
309             break;
310         }
311     }
312 }
313 
314 inline void afterAvailbleTimerAsyncWait(
315     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
316     const boost::system::error_code& ec)
317 {
318     cleanUp();
319     if (ec == boost::asio::error::operation_aborted)
320     {
321         // expected, we were canceled before the timer completed.
322         return;
323     }
324     BMCWEB_LOG_ERROR("Timed out waiting for firmware object being created");
325     BMCWEB_LOG_ERROR("FW image may has already been uploaded to server");
326     if (ec)
327     {
328         BMCWEB_LOG_ERROR("Async_wait failed{}", ec);
329         return;
330     }
331     if (asyncResp)
332     {
333         redfish::messages::internalError(asyncResp->res);
334     }
335 }
336 
337 inline void
338     handleUpdateErrorType(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
339                           const std::string& url, const std::string& type)
340 {
341     if (type == "xyz.openbmc_project.Software.Image.Error.UnTarFailure")
342     {
343         redfish::messages::invalidUpload(asyncResp->res, url,
344                                          "Invalid archive");
345     }
346     else if (type ==
347              "xyz.openbmc_project.Software.Image.Error.ManifestFileFailure")
348     {
349         redfish::messages::invalidUpload(asyncResp->res, url,
350                                          "Invalid manifest");
351     }
352     else if (type == "xyz.openbmc_project.Software.Image.Error.ImageFailure")
353     {
354         redfish::messages::invalidUpload(asyncResp->res, url,
355                                          "Invalid image format");
356     }
357     else if (type == "xyz.openbmc_project.Software.Version.Error.AlreadyExists")
358     {
359         redfish::messages::invalidUpload(asyncResp->res, url,
360                                          "Image version already exists");
361 
362         redfish::messages::resourceAlreadyExists(
363             asyncResp->res, "UpdateService", "Version", "uploaded version");
364     }
365     else if (type == "xyz.openbmc_project.Software.Image.Error.BusyFailure")
366     {
367         redfish::messages::resourceExhaustion(asyncResp->res, url);
368     }
369     else if (type == "xyz.openbmc_project.Software.Version.Error.Incompatible")
370     {
371         redfish::messages::invalidUpload(asyncResp->res, url,
372                                          "Incompatible image version");
373     }
374     else if (type ==
375              "xyz.openbmc_project.Software.Version.Error.ExpiredAccessKey")
376     {
377         redfish::messages::invalidUpload(asyncResp->res, url,
378                                          "Update Access Key Expired");
379     }
380     else if (type ==
381              "xyz.openbmc_project.Software.Version.Error.InvalidSignature")
382     {
383         redfish::messages::invalidUpload(asyncResp->res, url,
384                                          "Invalid image signature");
385     }
386     else if (type ==
387                  "xyz.openbmc_project.Software.Image.Error.InternalFailure" ||
388              type == "xyz.openbmc_project.Software.Version.Error.HostFile")
389     {
390         BMCWEB_LOG_ERROR("Software Image Error type={}", type);
391         redfish::messages::internalError(asyncResp->res);
392     }
393     else
394     {
395         // Unrelated error types. Ignored
396         BMCWEB_LOG_INFO("Non-Software-related Error type={}. Ignored", type);
397         return;
398     }
399     // Clear the timer
400     fwAvailableTimer = nullptr;
401 }
402 
403 inline void
404     afterUpdateErrorMatcher(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
405                             const std::string& url, sdbusplus::message_t& m)
406 {
407     dbus::utility::DBusInterfacesMap interfacesProperties;
408     sdbusplus::message::object_path objPath;
409     m.read(objPath, interfacesProperties);
410     BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
411     for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>&
412              interface : interfacesProperties)
413     {
414         if (interface.first == "xyz.openbmc_project.Logging.Entry")
415         {
416             for (const std::pair<std::string, dbus::utility::DbusVariantType>&
417                      value : interface.second)
418             {
419                 if (value.first != "Message")
420                 {
421                     continue;
422                 }
423                 const std::string* type =
424                     std::get_if<std::string>(&value.second);
425                 if (type == nullptr)
426                 {
427                     // if this was our message, timeout will cover it
428                     return;
429                 }
430                 handleUpdateErrorType(asyncResp, url, *type);
431             }
432         }
433     }
434 }
435 
436 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
437 // then no asyncResp updates will occur
438 inline void monitorForSoftwareAvailable(
439     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
440     const crow::Request& req, const std::string& url,
441     int timeoutTimeSeconds = 25)
442 {
443     // Only allow one FW update at a time
444     if (fwUpdateInProgress)
445     {
446         if (asyncResp)
447         {
448             messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
449         }
450         return;
451     }
452 
453     if (req.ioService == nullptr)
454     {
455         messages::internalError(asyncResp->res);
456         return;
457     }
458 
459     fwAvailableTimer =
460         std::make_unique<boost::asio::steady_timer>(*req.ioService);
461 
462     fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds));
463 
464     fwAvailableTimer->async_wait(
465         std::bind_front(afterAvailbleTimerAsyncWait, asyncResp));
466 
467     task::Payload payload(req);
468     auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable {
469         BMCWEB_LOG_DEBUG("Match fired");
470         softwareInterfaceAdded(asyncResp, m, std::move(payload));
471     };
472 
473     fwUpdateInProgress = true;
474 
475     fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>(
476         *crow::connections::systemBus,
477         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
478         "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
479         callback);
480 
481     fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>(
482         *crow::connections::systemBus,
483         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
484         "member='InterfacesAdded',"
485         "path='/xyz/openbmc_project/logging'",
486         std::bind_front(afterUpdateErrorMatcher, asyncResp, url));
487 }
488 
489 inline std::optional<boost::urls::url> parseSimpleUpdateUrl(
490     std::string imageURI, std::optional<std::string> transferProtocol,
491     crow::Response& res)
492 {
493     if (imageURI.find("://") == std::string::npos)
494     {
495         if (imageURI.starts_with("/"))
496         {
497             messages::actionParameterValueTypeError(
498                 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
499             return std::nullopt;
500         }
501         if (!transferProtocol)
502         {
503             messages::actionParameterValueTypeError(
504                 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
505             return std::nullopt;
506         }
507         // OpenBMC currently only supports TFTP or HTTPS
508         if (*transferProtocol == "TFTP")
509         {
510             imageURI = "tftp://" + imageURI;
511         }
512         else if (*transferProtocol == "HTTPS")
513         {
514             imageURI = "https://" + imageURI;
515         }
516         else
517         {
518             messages::actionParameterNotSupported(res, "TransferProtocol",
519                                                   *transferProtocol);
520             BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}",
521                              *transferProtocol);
522             return std::nullopt;
523         }
524     }
525 
526     boost::system::result<boost::urls::url> url =
527         boost::urls::parse_absolute_uri(imageURI);
528     if (!url)
529     {
530         messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
531                                                 "UpdateService.SimpleUpdate");
532 
533         return std::nullopt;
534     }
535     url->normalize();
536 
537     if (url->scheme() == "tftp")
538     {
539         if (url->encoded_path().size() < 2)
540         {
541             messages::actionParameterNotSupported(res, "ImageURI",
542                                                   url->buffer());
543             return std::nullopt;
544         }
545     }
546     else if (url->scheme() == "https")
547     {
548         // Empty paths default to "/"
549         if (url->encoded_path().empty())
550         {
551             url->set_encoded_path("/");
552         }
553     }
554     else
555     {
556         messages::actionParameterNotSupported(res, "ImageURI", imageURI);
557         return std::nullopt;
558     }
559 
560     if (url->encoded_path().empty())
561     {
562         messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
563                                                 "UpdateService.SimpleUpdate");
564         return std::nullopt;
565     }
566 
567     return *url;
568 }
569 
570 inline void doHttpsUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
571                           const boost::urls::url_view_base& url)
572 {
573     messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
574                                           url.buffer());
575 }
576 
577 inline void handleUpdateServiceSimpleUpdateAction(
578     crow::App& app, const crow::Request& req,
579     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
580 {
581     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
582     {
583         return;
584     }
585 
586     std::optional<std::string> transferProtocol;
587     std::string imageURI;
588 
589     BMCWEB_LOG_DEBUG("Enter UpdateService.SimpleUpdate doPost");
590 
591     // User can pass in both TransferProtocol and ImageURI parameters or
592     // they can pass in just the ImageURI with the transfer protocol
593     // embedded within it.
594     // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin
595     // 2) ImageURI:tftp://1.1.1.1/myfile.bin
596 
597     if (!json_util::readJsonAction( //
598             req, asyncResp->res, //
599             "ImageURI", imageURI, //
600             "TransferProtocol", transferProtocol //
601             ))
602     {
603         BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter");
604         return;
605     }
606 
607     std::optional<boost::urls::url> url =
608         parseSimpleUpdateUrl(imageURI, transferProtocol, asyncResp->res);
609     if (!url)
610     {
611         return;
612     }
613     if (url->scheme() == "https")
614     {
615         doHttpsUpdate(asyncResp, *url);
616     }
617     else
618     {
619         messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
620                                               url->buffer());
621         return;
622     }
623 
624     BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost");
625 }
626 
627 inline void uploadImageFile(crow::Response& res, std::string_view body)
628 {
629     std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID());
630 
631     BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string());
632     std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
633                                     std::ofstream::trunc);
634     // set the permission of the file to 640
635     std::filesystem::perms permission =
636         std::filesystem::perms::owner_read | std::filesystem::perms::group_read;
637     std::filesystem::permissions(filepath, permission);
638     out << body;
639 
640     if (out.bad())
641     {
642         messages::internalError(res);
643         cleanUp();
644     }
645 }
646 
647 // Convert the Request Apply Time to the D-Bus value
648 inline bool convertApplyTime(crow::Response& res, const std::string& applyTime,
649                              std::string& applyTimeNewVal)
650 {
651     if (applyTime == "Immediate")
652     {
653         applyTimeNewVal =
654             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
655     }
656     else if (applyTime == "OnReset")
657     {
658         applyTimeNewVal =
659             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
660     }
661     else
662     {
663         BMCWEB_LOG_WARNING(
664             "ApplyTime value {} is not in the list of acceptable values",
665             applyTime);
666         messages::propertyValueNotInList(res, applyTime, "ApplyTime");
667         return false;
668     }
669     return true;
670 }
671 
672 inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
673                          const std::string& applyTime)
674 {
675     std::string applyTimeNewVal;
676     if (!convertApplyTime(asyncResp->res, applyTime, applyTimeNewVal))
677     {
678         return;
679     }
680 
681     setDbusProperty(asyncResp, "ApplyTime", "xyz.openbmc_project.Settings",
682                     sdbusplus::message::object_path(
683                         "/xyz/openbmc_project/software/apply_time"),
684                     "xyz.openbmc_project.Software.ApplyTime",
685                     "RequestedApplyTime", applyTimeNewVal);
686 }
687 
688 struct MultiPartUpdateParameters
689 {
690     std::optional<std::string> applyTime;
691     std::string uploadData;
692     std::vector<std::string> targets;
693 };
694 
695 inline std::optional<std::string>
696     processUrl(boost::system::result<boost::urls::url_view>& url)
697 {
698     if (!url)
699     {
700         return std::nullopt;
701     }
702     if (crow::utility::readUrlSegments(*url, "redfish", "v1", "Managers",
703                                        BMCWEB_REDFISH_MANAGER_URI_NAME))
704     {
705         return std::make_optional(std::string(BMCWEB_REDFISH_MANAGER_URI_NAME));
706     }
707     if constexpr (!BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
708     {
709         return std::nullopt;
710     }
711     std::string firmwareId;
712     if (!crow::utility::readUrlSegments(*url, "redfish", "v1", "UpdateService",
713                                         "FirmwareInventory",
714                                         std::ref(firmwareId)))
715     {
716         return std::nullopt;
717     }
718 
719     return std::make_optional(firmwareId);
720 }
721 
722 inline std::optional<MultiPartUpdateParameters>
723     extractMultipartUpdateParameters(
724         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
725         MultipartParser parser)
726 {
727     MultiPartUpdateParameters multiRet;
728     for (FormPart& formpart : parser.mime_fields)
729     {
730         boost::beast::http::fields::const_iterator it =
731             formpart.fields.find("Content-Disposition");
732         if (it == formpart.fields.end())
733         {
734             BMCWEB_LOG_ERROR("Couldn't find Content-Disposition");
735             return std::nullopt;
736         }
737         BMCWEB_LOG_INFO("Parsing value {}", it->value());
738 
739         // The construction parameters of param_list must start with `;`
740         size_t index = it->value().find(';');
741         if (index == std::string::npos)
742         {
743             continue;
744         }
745 
746         for (const auto& param :
747              boost::beast::http::param_list{it->value().substr(index)})
748         {
749             if (param.first != "name" || param.second.empty())
750             {
751                 continue;
752             }
753 
754             if (param.second == "UpdateParameters")
755             {
756                 std::vector<std::string> tempTargets;
757                 nlohmann::json content =
758                     nlohmann::json::parse(formpart.content, nullptr, false);
759                 if (content.is_discarded())
760                 {
761                     return std::nullopt;
762                 }
763                 nlohmann::json::object_t* obj =
764                     content.get_ptr<nlohmann::json::object_t*>();
765                 if (obj == nullptr)
766                 {
767                     messages::propertyValueTypeError(
768                         asyncResp->res, formpart.content, "UpdateParameters");
769                     return std::nullopt;
770                 }
771 
772                 if (!json_util::readJsonObject( //
773                         *obj, asyncResp->res, //
774                         "@Redfish.OperationApplyTime", multiRet.applyTime, //
775                         "Targets", tempTargets //
776                         ))
777                 {
778                     return std::nullopt;
779                 }
780 
781                 for (size_t urlIndex = 0; urlIndex < tempTargets.size();
782                      urlIndex++)
783                 {
784                     const std::string& target = tempTargets[urlIndex];
785                     boost::system::result<boost::urls::url_view> url =
786                         boost::urls::parse_origin_form(target);
787                     auto res = processUrl(url);
788                     if (!res.has_value())
789                     {
790                         messages::propertyValueFormatError(
791                             asyncResp->res, target,
792                             std::format("Targets/{}", urlIndex));
793                         return std::nullopt;
794                     }
795                     multiRet.targets.emplace_back(res.value());
796                 }
797                 if (multiRet.targets.size() != 1)
798                 {
799                     messages::propertyValueFormatError(
800                         asyncResp->res, multiRet.targets, "Targets");
801                     return std::nullopt;
802                 }
803             }
804             else if (param.second == "UpdateFile")
805             {
806                 multiRet.uploadData = std::move(formpart.content);
807             }
808         }
809     }
810 
811     if (multiRet.uploadData.empty())
812     {
813         BMCWEB_LOG_ERROR("Upload data is NULL");
814         messages::propertyMissing(asyncResp->res, "UpdateFile");
815         return std::nullopt;
816     }
817     if (multiRet.targets.empty())
818     {
819         messages::propertyMissing(asyncResp->res, "Targets");
820         return std::nullopt;
821     }
822     return multiRet;
823 }
824 
825 inline void handleStartUpdate(
826     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
827     const std::string& objectPath, const boost::system::error_code& ec,
828     const sdbusplus::message::object_path& retPath)
829 {
830     if (ec)
831     {
832         BMCWEB_LOG_ERROR("error_code = {}", ec);
833         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
834         messages::internalError(asyncResp->res);
835         return;
836     }
837 
838     BMCWEB_LOG_INFO("Call to StartUpdate on {} Success, retPath = {}",
839                     objectPath, retPath.str);
840     createTask(asyncResp, std::move(payload), retPath);
841 }
842 
843 inline void startUpdate(
844     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
845     const MemoryFileDescriptor& memfd, const std::string& applyTime,
846     const std::string& objectPath, const std::string& serviceName)
847 {
848     crow::connections::systemBus->async_method_call(
849         [asyncResp, payload = std::move(payload),
850          objectPath](const boost::system::error_code& ec1,
851                      const sdbusplus::message::object_path& retPath) mutable {
852             handleStartUpdate(asyncResp, std::move(payload), objectPath, ec1,
853                               retPath);
854         },
855         serviceName, objectPath, "xyz.openbmc_project.Software.Update",
856         "StartUpdate", sdbusplus::message::unix_fd(memfd.fd), applyTime);
857 }
858 
859 inline void getSwInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
860                       task::Payload payload, const MemoryFileDescriptor& memfd,
861                       const std::string& applyTime, const std::string& target,
862                       const boost::system::error_code& ec,
863                       const dbus::utility::MapperGetSubTreeResponse& subtree)
864 {
865     using SwInfoMap = std::unordered_map<
866         std::string, std::pair<sdbusplus::message::object_path, std::string>>;
867     SwInfoMap swInfoMap;
868 
869     if (ec)
870     {
871         BMCWEB_LOG_ERROR("error_code = {}", ec);
872         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
873         messages::internalError(asyncResp->res);
874         return;
875     }
876     BMCWEB_LOG_DEBUG("Found {} software version paths", subtree.size());
877 
878     for (const auto& entry : subtree)
879     {
880         sdbusplus::message::object_path path(entry.first);
881         std::string swId = path.filename();
882         swInfoMap.emplace(swId, make_pair(path, entry.second[0].first));
883     }
884 
885     auto swEntry = swInfoMap.find(target);
886     if (swEntry == swInfoMap.end())
887     {
888         BMCWEB_LOG_WARNING("No valid DBus path for Target URI {}", target);
889         messages::propertyValueFormatError(asyncResp->res, target, "Targets");
890         return;
891     }
892 
893     BMCWEB_LOG_DEBUG("Found software version path {} serviceName {}",
894                      swEntry->second.first.str, swEntry->second.second);
895 
896     startUpdate(asyncResp, std::move(payload), memfd, applyTime,
897                 swEntry->second.first.str, swEntry->second.second);
898 }
899 
900 inline void handleBMCUpdate(
901     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
902     const MemoryFileDescriptor& memfd, const std::string& applyTime,
903     const boost::system::error_code& ec,
904     const dbus::utility::MapperEndPoints& functionalSoftware)
905 {
906     if (ec)
907     {
908         BMCWEB_LOG_ERROR("error_code = {}", ec);
909         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
910         messages::internalError(asyncResp->res);
911         return;
912     }
913     if (functionalSoftware.size() != 1)
914     {
915         BMCWEB_LOG_ERROR("Found {} functional software endpoints",
916                          functionalSoftware.size());
917         messages::internalError(asyncResp->res);
918         return;
919     }
920 
921     startUpdate(asyncResp, std::move(payload), memfd, applyTime,
922                 functionalSoftware[0], "xyz.openbmc_project.Software.Manager");
923 }
924 
925 inline void processUpdateRequest(
926     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
927     task::Payload&& payload, std::string_view body,
928     const std::string& applyTime, std::vector<std::string>& targets)
929 {
930     MemoryFileDescriptor memfd("update-image");
931     if (memfd.fd == -1)
932     {
933         BMCWEB_LOG_ERROR("Failed to create image memfd");
934         messages::internalError(asyncResp->res);
935         return;
936     }
937     if (write(memfd.fd, body.data(), body.length()) !=
938         static_cast<ssize_t>(body.length()))
939     {
940         BMCWEB_LOG_ERROR("Failed to write to image memfd");
941         messages::internalError(asyncResp->res);
942         return;
943     }
944     if (!memfd.rewind())
945     {
946         messages::internalError(asyncResp->res);
947         return;
948     }
949 
950     if (!targets.empty() && targets[0] == BMCWEB_REDFISH_MANAGER_URI_NAME)
951     {
952         dbus::utility::getAssociationEndPoints(
953             "/xyz/openbmc_project/software/bmc/updateable",
954             [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
955              applyTime](
956                 const boost::system::error_code& ec,
957                 const dbus::utility::MapperEndPoints& objectPaths) mutable {
958                 handleBMCUpdate(asyncResp, std::move(payload), memfd, applyTime,
959                                 ec, objectPaths);
960             });
961     }
962     else
963     {
964         constexpr std::array<std::string_view, 1> interfaces = {
965             "xyz.openbmc_project.Software.Version"};
966         dbus::utility::getSubTree(
967             "/xyz/openbmc_project/software", 1, interfaces,
968             [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
969              applyTime, targets](const boost::system::error_code& ec,
970                                  const dbus::utility::MapperGetSubTreeResponse&
971                                      subtree) mutable {
972                 getSwInfo(asyncResp, std::move(payload), memfd, applyTime,
973                           targets[0], ec, subtree);
974             });
975     }
976 }
977 
978 inline void
979     updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
980                            const crow::Request& req, MultipartParser&& parser)
981 {
982     std::optional<MultiPartUpdateParameters> multipart =
983         extractMultipartUpdateParameters(asyncResp, std::move(parser));
984     if (!multipart)
985     {
986         return;
987     }
988     if (!multipart->applyTime)
989     {
990         multipart->applyTime = "OnReset";
991     }
992 
993     if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
994     {
995         std::string applyTimeNewVal;
996         if (!convertApplyTime(asyncResp->res, *multipart->applyTime,
997                               applyTimeNewVal))
998         {
999             return;
1000         }
1001         task::Payload payload(req);
1002 
1003         processUpdateRequest(asyncResp, std::move(payload),
1004                              multipart->uploadData, applyTimeNewVal,
1005                              multipart->targets);
1006     }
1007     else
1008     {
1009         setApplyTime(asyncResp, *multipart->applyTime);
1010 
1011         // Setup callback for when new software detected
1012         monitorForSoftwareAvailable(asyncResp, req,
1013                                     "/redfish/v1/UpdateService");
1014 
1015         uploadImageFile(asyncResp->res, multipart->uploadData);
1016     }
1017 }
1018 
1019 inline void doHTTPUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1020                          const crow::Request& req)
1021 {
1022     if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
1023     {
1024         task::Payload payload(req);
1025         // HTTP push only supports BMC updates (with ApplyTime as immediate) for
1026         // backwards compatibility. Specific component updates will be handled
1027         // through Multipart form HTTP push.
1028         std::vector<std::string> targets;
1029         targets.emplace_back(BMCWEB_REDFISH_MANAGER_URI_NAME);
1030 
1031         processUpdateRequest(
1032             asyncResp, std::move(payload), req.body(),
1033             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate",
1034             targets);
1035     }
1036     else
1037     {
1038         // Setup callback for when new software detected
1039         monitorForSoftwareAvailable(asyncResp, req,
1040                                     "/redfish/v1/UpdateService");
1041 
1042         uploadImageFile(asyncResp->res, req.body());
1043     }
1044 }
1045 
1046 inline void
1047     handleUpdateServicePost(App& app, const crow::Request& req,
1048                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1049 {
1050     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1051     {
1052         return;
1053     }
1054     std::string_view contentType = req.getHeaderValue("Content-Type");
1055 
1056     BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType);
1057 
1058     // Make sure that content type is application/octet-stream or
1059     // multipart/form-data
1060     if (bmcweb::asciiIEquals(contentType, "application/octet-stream"))
1061     {
1062         doHTTPUpdate(asyncResp, req);
1063     }
1064     else if (contentType.starts_with("multipart/form-data"))
1065     {
1066         MultipartParser parser;
1067 
1068         ParserError ec = parser.parse(req);
1069         if (ec != ParserError::PARSER_SUCCESS)
1070         {
1071             // handle error
1072             BMCWEB_LOG_ERROR("MIME parse failed, ec : {}",
1073                              static_cast<int>(ec));
1074             messages::internalError(asyncResp->res);
1075             return;
1076         }
1077 
1078         updateMultipartContext(asyncResp, req, std::move(parser));
1079     }
1080     else
1081     {
1082         BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType);
1083         asyncResp->res.result(boost::beast::http::status::bad_request);
1084     }
1085 }
1086 
1087 inline void
1088     handleUpdateServiceGet(App& app, const crow::Request& req,
1089                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1090 {
1091     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1092     {
1093         return;
1094     }
1095     asyncResp->res.jsonValue["@odata.type"] =
1096         "#UpdateService.v1_11_1.UpdateService";
1097     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
1098     asyncResp->res.jsonValue["Id"] = "UpdateService";
1099     asyncResp->res.jsonValue["Description"] = "Service for Software Update";
1100     asyncResp->res.jsonValue["Name"] = "Update Service";
1101 
1102     asyncResp->res.jsonValue["HttpPushUri"] =
1103         "/redfish/v1/UpdateService/update";
1104     asyncResp->res.jsonValue["MultipartHttpPushUri"] =
1105         "/redfish/v1/UpdateService/update";
1106 
1107     // UpdateService cannot be disabled
1108     asyncResp->res.jsonValue["ServiceEnabled"] = true;
1109     asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] =
1110         "/redfish/v1/UpdateService/FirmwareInventory";
1111     // Get the MaxImageSizeBytes
1112     asyncResp->res.jsonValue["MaxImageSizeBytes"] =
1113         BMCWEB_HTTP_BODY_LIMIT * 1024 * 1024;
1114 
1115     // Update Actions object.
1116     nlohmann::json& updateSvcSimpleUpdate =
1117         asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
1118     updateSvcSimpleUpdate["target"] =
1119         "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
1120 
1121     nlohmann::json::array_t allowed;
1122     allowed.emplace_back(update_service::TransferProtocolType::HTTPS);
1123 
1124     if constexpr (BMCWEB_INSECURE_PUSH_STYLE_NOTIFICATION)
1125     {
1126         allowed.emplace_back(update_service::TransferProtocolType::TFTP);
1127     }
1128 
1129     updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] =
1130         std::move(allowed);
1131 
1132     asyncResp->res
1133         .jsonValue["HttpPushUriOptions"]["HttpPushUriApplyTime"]["ApplyTime"] =
1134         update_service::ApplyTime::Immediate;
1135 }
1136 
1137 inline void handleUpdateServiceFirmwareInventoryCollectionGet(
1138     App& app, const crow::Request& req,
1139     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1140 {
1141     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1142     {
1143         return;
1144     }
1145     asyncResp->res.jsonValue["@odata.type"] =
1146         "#SoftwareInventoryCollection.SoftwareInventoryCollection";
1147     asyncResp->res.jsonValue["@odata.id"] =
1148         "/redfish/v1/UpdateService/FirmwareInventory";
1149     asyncResp->res.jsonValue["Name"] = "Software Inventory Collection";
1150     const std::array<const std::string_view, 1> iface = {
1151         "xyz.openbmc_project.Software.Version"};
1152 
1153     redfish::collection_util::getCollectionMembers(
1154         asyncResp,
1155         boost::urls::url("/redfish/v1/UpdateService/FirmwareInventory"), iface,
1156         "/xyz/openbmc_project/software");
1157 }
1158 
1159 /* Fill related item links (i.e. bmc, bios) in for inventory */
1160 inline void getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1161                             const std::string& purpose)
1162 {
1163     if (purpose == sw_util::bmcPurpose)
1164     {
1165         nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
1166         nlohmann::json::object_t item;
1167         item["@odata.id"] = boost::urls::format(
1168             "/redfish/v1/Managers/{}", BMCWEB_REDFISH_MANAGER_URI_NAME);
1169         relatedItem.emplace_back(std::move(item));
1170         asyncResp->res.jsonValue["RelatedItem@odata.count"] =
1171             relatedItem.size();
1172     }
1173     else if (purpose == sw_util::biosPurpose)
1174     {
1175         nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
1176         nlohmann::json::object_t item;
1177         item["@odata.id"] = std::format("/redfish/v1/Systems/{}/Bios",
1178                                         BMCWEB_REDFISH_SYSTEM_URI_NAME);
1179         relatedItem.emplace_back(std::move(item));
1180         asyncResp->res.jsonValue["RelatedItem@odata.count"] =
1181             relatedItem.size();
1182     }
1183     else
1184     {
1185         BMCWEB_LOG_DEBUG("Unknown software purpose {}", purpose);
1186     }
1187 }
1188 
1189 inline void
1190     getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1191                        const std::string& service, const std::string& path,
1192                        const std::string& swId)
1193 {
1194     sdbusplus::asio::getAllProperties(
1195         *crow::connections::systemBus, service, path,
1196         "xyz.openbmc_project.Software.Version",
1197         [asyncResp,
1198          swId](const boost::system::error_code& ec,
1199                const dbus::utility::DBusPropertiesMap& propertiesList) {
1200             if (ec)
1201             {
1202                 messages::internalError(asyncResp->res);
1203                 return;
1204             }
1205 
1206             const std::string* swInvPurpose = nullptr;
1207             const std::string* version = nullptr;
1208 
1209             const bool success = sdbusplus::unpackPropertiesNoThrow(
1210                 dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose",
1211                 swInvPurpose, "Version", version);
1212 
1213             if (!success)
1214             {
1215                 messages::internalError(asyncResp->res);
1216                 return;
1217             }
1218 
1219             if (swInvPurpose == nullptr)
1220             {
1221                 BMCWEB_LOG_DEBUG("Can't find property \"Purpose\"!");
1222                 messages::internalError(asyncResp->res);
1223                 return;
1224             }
1225 
1226             BMCWEB_LOG_DEBUG("swInvPurpose = {}", *swInvPurpose);
1227 
1228             if (version == nullptr)
1229             {
1230                 BMCWEB_LOG_DEBUG("Can't find property \"Version\"!");
1231 
1232                 messages::internalError(asyncResp->res);
1233 
1234                 return;
1235             }
1236             asyncResp->res.jsonValue["Version"] = *version;
1237             asyncResp->res.jsonValue["Id"] = swId;
1238 
1239             // swInvPurpose is of format:
1240             // xyz.openbmc_project.Software.Version.VersionPurpose.ABC
1241             // Translate this to "ABC image"
1242             size_t endDesc = swInvPurpose->rfind('.');
1243             if (endDesc == std::string::npos)
1244             {
1245                 messages::internalError(asyncResp->res);
1246                 return;
1247             }
1248             endDesc++;
1249             if (endDesc >= swInvPurpose->size())
1250             {
1251                 messages::internalError(asyncResp->res);
1252                 return;
1253             }
1254 
1255             std::string formatDesc = swInvPurpose->substr(endDesc);
1256             asyncResp->res.jsonValue["Description"] = formatDesc + " image";
1257             getRelatedItems(asyncResp, *swInvPurpose);
1258         });
1259 }
1260 
1261 inline void handleUpdateServiceFirmwareInventoryGet(
1262     App& app, const crow::Request& req,
1263     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1264     const std::string& param)
1265 {
1266     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1267     {
1268         return;
1269     }
1270     std::shared_ptr<std::string> swId = std::make_shared<std::string>(param);
1271 
1272     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
1273         "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId);
1274 
1275     constexpr std::array<std::string_view, 1> interfaces = {
1276         "xyz.openbmc_project.Software.Version"};
1277     dbus::utility::getSubTree(
1278         "/", 0, interfaces,
1279         [asyncResp,
1280          swId](const boost::system::error_code& ec,
1281                const dbus::utility::MapperGetSubTreeResponse& subtree) {
1282             BMCWEB_LOG_DEBUG("doGet callback...");
1283             if (ec)
1284             {
1285                 messages::internalError(asyncResp->res);
1286                 return;
1287             }
1288 
1289             // Ensure we find our input swId, otherwise return an error
1290             bool found = false;
1291             for (const std::pair<std::string,
1292                                  std::vector<std::pair<
1293                                      std::string, std::vector<std::string>>>>&
1294                      obj : subtree)
1295             {
1296                 if (!obj.first.ends_with(*swId))
1297                 {
1298                     continue;
1299                 }
1300 
1301                 if (obj.second.empty())
1302                 {
1303                     continue;
1304                 }
1305 
1306                 found = true;
1307                 sw_util::getSwStatus(asyncResp, swId, obj.second[0].first);
1308                 getSoftwareVersion(asyncResp, obj.second[0].first, obj.first,
1309                                    *swId);
1310             }
1311             if (!found)
1312             {
1313                 BMCWEB_LOG_WARNING("Input swID {} not found!", *swId);
1314                 messages::resourceMissingAtURI(
1315                     asyncResp->res,
1316                     boost::urls::format(
1317                         "/redfish/v1/UpdateService/FirmwareInventory/{}",
1318                         *swId));
1319                 return;
1320             }
1321             asyncResp->res.jsonValue["@odata.type"] =
1322                 "#SoftwareInventory.v1_1_0.SoftwareInventory";
1323             asyncResp->res.jsonValue["Name"] = "Software Inventory";
1324             asyncResp->res.jsonValue["Status"]["HealthRollup"] =
1325                 resource::Health::OK;
1326 
1327             asyncResp->res.jsonValue["Updateable"] = false;
1328             sw_util::getSwUpdatableStatus(asyncResp, swId);
1329         });
1330 }
1331 
1332 inline void requestRoutesUpdateService(App& app)
1333 {
1334     BMCWEB_ROUTE(
1335         app, "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/")
1336         .privileges(redfish::privileges::postUpdateService)
1337         .methods(boost::beast::http::verb::post)(std::bind_front(
1338             handleUpdateServiceSimpleUpdateAction, std::ref(app)));
1339 
1340     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/")
1341         .privileges(redfish::privileges::getSoftwareInventory)
1342         .methods(boost::beast::http::verb::get)(std::bind_front(
1343             handleUpdateServiceFirmwareInventoryGet, std::ref(app)));
1344 
1345     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
1346         .privileges(redfish::privileges::getUpdateService)
1347         .methods(boost::beast::http::verb::get)(
1348             std::bind_front(handleUpdateServiceGet, std::ref(app)));
1349 
1350     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/")
1351         .privileges(redfish::privileges::postUpdateService)
1352         .methods(boost::beast::http::verb::post)(
1353             std::bind_front(handleUpdateServicePost, std::ref(app)));
1354 
1355     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/")
1356         .privileges(redfish::privileges::getSoftwareInventoryCollection)
1357         .methods(boost::beast::http::verb::get)(std::bind_front(
1358             handleUpdateServiceFirmwareInventoryCollectionGet, std::ref(app)));
1359 }
1360 
1361 } // namespace redfish
1362