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 static 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(req, asyncResp->res, "TransferProtocol",
598                                    transferProtocol, "ImageURI", imageURI))
599     {
600         BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter");
601         return;
602     }
603 
604     std::optional<boost::urls::url> url =
605         parseSimpleUpdateUrl(imageURI, transferProtocol, asyncResp->res);
606     if (!url)
607     {
608         return;
609     }
610     if (url->scheme() == "https")
611     {
612         doHttpsUpdate(asyncResp, *url);
613     }
614     else
615     {
616         messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
617                                               url->buffer());
618         return;
619     }
620 
621     BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost");
622 }
623 
624 inline void uploadImageFile(crow::Response& res, std::string_view body)
625 {
626     std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID());
627 
628     BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string());
629     std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
630                                     std::ofstream::trunc);
631     // set the permission of the file to 640
632     std::filesystem::perms permission =
633         std::filesystem::perms::owner_read | std::filesystem::perms::group_read;
634     std::filesystem::permissions(filepath, permission);
635     out << body;
636 
637     if (out.bad())
638     {
639         messages::internalError(res);
640         cleanUp();
641     }
642 }
643 
644 // Convert the Request Apply Time to the D-Bus value
645 inline bool convertApplyTime(crow::Response& res, const std::string& applyTime,
646                              std::string& applyTimeNewVal)
647 {
648     if (applyTime == "Immediate")
649     {
650         applyTimeNewVal =
651             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
652     }
653     else if (applyTime == "OnReset")
654     {
655         applyTimeNewVal =
656             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
657     }
658     else
659     {
660         BMCWEB_LOG_WARNING(
661             "ApplyTime value {} is not in the list of acceptable values",
662             applyTime);
663         messages::propertyValueNotInList(res, applyTime, "ApplyTime");
664         return false;
665     }
666     return true;
667 }
668 
669 inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
670                          const std::string& applyTime)
671 {
672     std::string applyTimeNewVal;
673     if (!convertApplyTime(asyncResp->res, applyTime, applyTimeNewVal))
674     {
675         return;
676     }
677 
678     setDbusProperty(asyncResp, "ApplyTime", "xyz.openbmc_project.Settings",
679                     sdbusplus::message::object_path(
680                         "/xyz/openbmc_project/software/apply_time"),
681                     "xyz.openbmc_project.Software.ApplyTime",
682                     "RequestedApplyTime", applyTimeNewVal);
683 }
684 
685 struct MultiPartUpdateParameters
686 {
687     std::optional<std::string> applyTime;
688     std::string uploadData;
689     std::vector<std::string> targets;
690 };
691 
692 inline std::optional<std::string>
693     processUrl(boost::system::result<boost::urls::url_view>& url)
694 {
695     if (!url)
696     {
697         return std::nullopt;
698     }
699     if (crow::utility::readUrlSegments(*url, "redfish", "v1", "Managers",
700                                        BMCWEB_REDFISH_MANAGER_URI_NAME))
701     {
702         return std::make_optional(std::string(BMCWEB_REDFISH_MANAGER_URI_NAME));
703     }
704     if constexpr (!BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
705     {
706         return std::nullopt;
707     }
708     std::string firmwareId;
709     if (!crow::utility::readUrlSegments(*url, "redfish", "v1", "UpdateService",
710                                         "FirmwareInventory",
711                                         std::ref(firmwareId)))
712     {
713         return std::nullopt;
714     }
715 
716     return std::make_optional(firmwareId);
717 }
718 
719 inline std::optional<MultiPartUpdateParameters>
720     extractMultipartUpdateParameters(
721         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
722         MultipartParser parser)
723 {
724     MultiPartUpdateParameters multiRet;
725     for (FormPart& formpart : parser.mime_fields)
726     {
727         boost::beast::http::fields::const_iterator it =
728             formpart.fields.find("Content-Disposition");
729         if (it == formpart.fields.end())
730         {
731             BMCWEB_LOG_ERROR("Couldn't find Content-Disposition");
732             return std::nullopt;
733         }
734         BMCWEB_LOG_INFO("Parsing value {}", it->value());
735 
736         // The construction parameters of param_list must start with `;`
737         size_t index = it->value().find(';');
738         if (index == std::string::npos)
739         {
740             continue;
741         }
742 
743         for (const auto& param :
744              boost::beast::http::param_list{it->value().substr(index)})
745         {
746             if (param.first != "name" || param.second.empty())
747             {
748                 continue;
749             }
750 
751             if (param.second == "UpdateParameters")
752             {
753                 std::vector<std::string> tempTargets;
754                 nlohmann::json content =
755                     nlohmann::json::parse(formpart.content, nullptr, false);
756                 if (content.is_discarded())
757                 {
758                     return std::nullopt;
759                 }
760                 nlohmann::json::object_t* obj =
761                     content.get_ptr<nlohmann::json::object_t*>();
762                 if (obj == nullptr)
763                 {
764                     messages::propertyValueTypeError(
765                         asyncResp->res, formpart.content, "UpdateParameters");
766                     return std::nullopt;
767                 }
768 
769                 if (!json_util::readJsonObject(
770                         *obj, asyncResp->res, "Targets", tempTargets,
771                         "@Redfish.OperationApplyTime", multiRet.applyTime))
772                 {
773                     return std::nullopt;
774                 }
775 
776                 for (size_t urlIndex = 0; urlIndex < tempTargets.size();
777                      urlIndex++)
778                 {
779                     const std::string& target = tempTargets[urlIndex];
780                     boost::system::result<boost::urls::url_view> url =
781                         boost::urls::parse_origin_form(target);
782                     auto res = processUrl(url);
783                     if (!res.has_value())
784                     {
785                         messages::propertyValueFormatError(
786                             asyncResp->res, target,
787                             std::format("Targets/{}", urlIndex));
788                         return std::nullopt;
789                     }
790                     multiRet.targets.emplace_back(res.value());
791                 }
792                 if (multiRet.targets.size() != 1)
793                 {
794                     messages::propertyValueFormatError(
795                         asyncResp->res, multiRet.targets, "Targets");
796                     return std::nullopt;
797                 }
798             }
799             else if (param.second == "UpdateFile")
800             {
801                 multiRet.uploadData = std::move(formpart.content);
802             }
803         }
804     }
805 
806     if (multiRet.uploadData.empty())
807     {
808         BMCWEB_LOG_ERROR("Upload data is NULL");
809         messages::propertyMissing(asyncResp->res, "UpdateFile");
810         return std::nullopt;
811     }
812     if (multiRet.targets.empty())
813     {
814         messages::propertyMissing(asyncResp->res, "Targets");
815         return std::nullopt;
816     }
817     return multiRet;
818 }
819 
820 inline void handleStartUpdate(
821     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
822     const std::string& objectPath, const boost::system::error_code& ec,
823     const sdbusplus::message::object_path& retPath)
824 {
825     if (ec)
826     {
827         BMCWEB_LOG_ERROR("error_code = {}", ec);
828         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
829         messages::internalError(asyncResp->res);
830         return;
831     }
832 
833     BMCWEB_LOG_INFO("Call to StartUpdate on {} Success, retPath = {}",
834                     objectPath, retPath.str);
835     createTask(asyncResp, std::move(payload), retPath);
836 }
837 
838 inline void startUpdate(
839     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
840     const MemoryFileDescriptor& memfd, const std::string& applyTime,
841     const std::string& objectPath, const std::string& serviceName)
842 {
843     crow::connections::systemBus->async_method_call(
844         [asyncResp, payload = std::move(payload),
845          objectPath](const boost::system::error_code& ec1,
846                      const sdbusplus::message::object_path& retPath) mutable {
847             handleStartUpdate(asyncResp, std::move(payload), objectPath, ec1,
848                               retPath);
849         },
850         serviceName, objectPath, "xyz.openbmc_project.Software.Update",
851         "StartUpdate", sdbusplus::message::unix_fd(memfd.fd), applyTime);
852 }
853 
854 inline void getSwInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
855                       task::Payload payload, const MemoryFileDescriptor& memfd,
856                       const std::string& applyTime, const std::string& target,
857                       const boost::system::error_code& ec,
858                       const dbus::utility::MapperGetSubTreeResponse& subtree)
859 {
860     using SwInfoMap = std::unordered_map<
861         std::string, std::pair<sdbusplus::message::object_path, std::string>>;
862     SwInfoMap swInfoMap;
863 
864     if (ec)
865     {
866         BMCWEB_LOG_ERROR("error_code = {}", ec);
867         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
868         messages::internalError(asyncResp->res);
869         return;
870     }
871     BMCWEB_LOG_DEBUG("Found {} software version paths", subtree.size());
872 
873     for (const auto& entry : subtree)
874     {
875         sdbusplus::message::object_path path(entry.first);
876         std::string swId = path.filename();
877         swInfoMap.emplace(swId, make_pair(path, entry.second[0].first));
878     }
879 
880     auto swEntry = swInfoMap.find(target);
881     if (swEntry == swInfoMap.end())
882     {
883         BMCWEB_LOG_WARNING("No valid DBus path for Target URI {}", target);
884         messages::propertyValueFormatError(asyncResp->res, target, "Targets");
885         return;
886     }
887 
888     BMCWEB_LOG_DEBUG("Found software version path {} serviceName {}",
889                      swEntry->second.first.str, swEntry->second.second);
890 
891     startUpdate(asyncResp, std::move(payload), memfd, applyTime,
892                 swEntry->second.first.str, swEntry->second.second);
893 }
894 
895 inline void handleBMCUpdate(
896     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
897     const MemoryFileDescriptor& memfd, const std::string& applyTime,
898     const boost::system::error_code& ec,
899     const dbus::utility::MapperEndPoints& functionalSoftware)
900 {
901     if (ec)
902     {
903         BMCWEB_LOG_ERROR("error_code = {}", ec);
904         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
905         messages::internalError(asyncResp->res);
906         return;
907     }
908     if (functionalSoftware.size() != 1)
909     {
910         BMCWEB_LOG_ERROR("Found {} functional software endpoints",
911                          functionalSoftware.size());
912         messages::internalError(asyncResp->res);
913         return;
914     }
915 
916     startUpdate(asyncResp, std::move(payload), memfd, applyTime,
917                 functionalSoftware[0], "xyz.openbmc_project.Software.Manager");
918 }
919 
920 inline void processUpdateRequest(
921     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
922     task::Payload&& payload, std::string_view body,
923     const std::string& applyTime, std::vector<std::string>& targets)
924 {
925     MemoryFileDescriptor memfd("update-image");
926     if (memfd.fd == -1)
927     {
928         BMCWEB_LOG_ERROR("Failed to create image memfd");
929         messages::internalError(asyncResp->res);
930         return;
931     }
932     if (write(memfd.fd, body.data(), body.length()) !=
933         static_cast<ssize_t>(body.length()))
934     {
935         BMCWEB_LOG_ERROR("Failed to write to image memfd");
936         messages::internalError(asyncResp->res);
937         return;
938     }
939     if (!memfd.rewind())
940     {
941         messages::internalError(asyncResp->res);
942         return;
943     }
944 
945     if (!targets.empty() && targets[0] == BMCWEB_REDFISH_MANAGER_URI_NAME)
946     {
947         dbus::utility::getAssociationEndPoints(
948             "/xyz/openbmc_project/software/bmc/functional",
949             [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
950              applyTime](
951                 const boost::system::error_code& ec,
952                 const dbus::utility::MapperEndPoints& objectPaths) mutable {
953                 handleBMCUpdate(asyncResp, std::move(payload), memfd, applyTime,
954                                 ec, objectPaths);
955             });
956     }
957     else
958     {
959         constexpr std::array<std::string_view, 1> interfaces = {
960             "xyz.openbmc_project.Software.Version"};
961         dbus::utility::getSubTree(
962             "/xyz/openbmc_project/software", 1, interfaces,
963             [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
964              applyTime, targets](const boost::system::error_code& ec,
965                                  const dbus::utility::MapperGetSubTreeResponse&
966                                      subtree) mutable {
967                 getSwInfo(asyncResp, std::move(payload), memfd, applyTime,
968                           targets[0], ec, subtree);
969             });
970     }
971 }
972 
973 inline void
974     updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
975                            const crow::Request& req, MultipartParser&& parser)
976 {
977     std::optional<MultiPartUpdateParameters> multipart =
978         extractMultipartUpdateParameters(asyncResp, std::move(parser));
979     if (!multipart)
980     {
981         return;
982     }
983     if (!multipart->applyTime)
984     {
985         multipart->applyTime = "OnReset";
986     }
987 
988     if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
989     {
990         std::string applyTimeNewVal;
991         if (!convertApplyTime(asyncResp->res, *multipart->applyTime,
992                               applyTimeNewVal))
993         {
994             return;
995         }
996         task::Payload payload(req);
997 
998         processUpdateRequest(asyncResp, std::move(payload),
999                              multipart->uploadData, applyTimeNewVal,
1000                              multipart->targets);
1001     }
1002     else
1003     {
1004         setApplyTime(asyncResp, *multipart->applyTime);
1005 
1006         // Setup callback for when new software detected
1007         monitorForSoftwareAvailable(asyncResp, req,
1008                                     "/redfish/v1/UpdateService");
1009 
1010         uploadImageFile(asyncResp->res, multipart->uploadData);
1011     }
1012 }
1013 
1014 inline void doHTTPUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1015                          const crow::Request& req)
1016 {
1017     if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
1018     {
1019         task::Payload payload(req);
1020         // HTTP push only supports BMC updates (with ApplyTime as immediate) for
1021         // backwards compatibility. Specific component updates will be handled
1022         // through Multipart form HTTP push.
1023         std::vector<std::string> targets;
1024         targets.emplace_back(BMCWEB_REDFISH_MANAGER_URI_NAME);
1025 
1026         processUpdateRequest(
1027             asyncResp, std::move(payload), req.body(),
1028             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate",
1029             targets);
1030     }
1031     else
1032     {
1033         // Setup callback for when new software detected
1034         monitorForSoftwareAvailable(asyncResp, req,
1035                                     "/redfish/v1/UpdateService");
1036 
1037         uploadImageFile(asyncResp->res, req.body());
1038     }
1039 }
1040 
1041 inline void
1042     handleUpdateServicePost(App& app, const crow::Request& req,
1043                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1044 {
1045     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1046     {
1047         return;
1048     }
1049     std::string_view contentType = req.getHeaderValue("Content-Type");
1050 
1051     BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType);
1052 
1053     // Make sure that content type is application/octet-stream or
1054     // multipart/form-data
1055     if (bmcweb::asciiIEquals(contentType, "application/octet-stream"))
1056     {
1057         doHTTPUpdate(asyncResp, req);
1058     }
1059     else if (contentType.starts_with("multipart/form-data"))
1060     {
1061         MultipartParser parser;
1062 
1063         ParserError ec = parser.parse(req);
1064         if (ec != ParserError::PARSER_SUCCESS)
1065         {
1066             // handle error
1067             BMCWEB_LOG_ERROR("MIME parse failed, ec : {}",
1068                              static_cast<int>(ec));
1069             messages::internalError(asyncResp->res);
1070             return;
1071         }
1072 
1073         updateMultipartContext(asyncResp, req, std::move(parser));
1074     }
1075     else
1076     {
1077         BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType);
1078         asyncResp->res.result(boost::beast::http::status::bad_request);
1079     }
1080 }
1081 
1082 inline void
1083     handleUpdateServiceGet(App& app, const crow::Request& req,
1084                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1085 {
1086     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1087     {
1088         return;
1089     }
1090     asyncResp->res.jsonValue["@odata.type"] =
1091         "#UpdateService.v1_11_1.UpdateService";
1092     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
1093     asyncResp->res.jsonValue["Id"] = "UpdateService";
1094     asyncResp->res.jsonValue["Description"] = "Service for Software Update";
1095     asyncResp->res.jsonValue["Name"] = "Update Service";
1096 
1097     asyncResp->res.jsonValue["HttpPushUri"] =
1098         "/redfish/v1/UpdateService/update";
1099     asyncResp->res.jsonValue["MultipartHttpPushUri"] =
1100         "/redfish/v1/UpdateService/update";
1101 
1102     // UpdateService cannot be disabled
1103     asyncResp->res.jsonValue["ServiceEnabled"] = true;
1104     asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] =
1105         "/redfish/v1/UpdateService/FirmwareInventory";
1106     // Get the MaxImageSizeBytes
1107     asyncResp->res.jsonValue["MaxImageSizeBytes"] =
1108         BMCWEB_HTTP_BODY_LIMIT * 1024 * 1024;
1109 
1110     // Update Actions object.
1111     nlohmann::json& updateSvcSimpleUpdate =
1112         asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
1113     updateSvcSimpleUpdate["target"] =
1114         "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
1115 
1116     nlohmann::json::array_t allowed;
1117     allowed.emplace_back(update_service::TransferProtocolType::HTTPS);
1118 
1119     if constexpr (BMCWEB_INSECURE_PUSH_STYLE_NOTIFICATION)
1120     {
1121         allowed.emplace_back(update_service::TransferProtocolType::TFTP);
1122     }
1123 
1124     updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] =
1125         std::move(allowed);
1126 
1127     asyncResp->res
1128         .jsonValue["HttpPushUriOptions"]["HttpPushUriApplyTime"]["ApplyTime"] =
1129         update_service::ApplyTime::Immediate;
1130 }
1131 
1132 inline void handleUpdateServiceFirmwareInventoryCollectionGet(
1133     App& app, const crow::Request& req,
1134     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1135 {
1136     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1137     {
1138         return;
1139     }
1140     asyncResp->res.jsonValue["@odata.type"] =
1141         "#SoftwareInventoryCollection.SoftwareInventoryCollection";
1142     asyncResp->res.jsonValue["@odata.id"] =
1143         "/redfish/v1/UpdateService/FirmwareInventory";
1144     asyncResp->res.jsonValue["Name"] = "Software Inventory Collection";
1145     const std::array<const std::string_view, 1> iface = {
1146         "xyz.openbmc_project.Software.Version"};
1147 
1148     redfish::collection_util::getCollectionMembers(
1149         asyncResp,
1150         boost::urls::url("/redfish/v1/UpdateService/FirmwareInventory"), iface,
1151         "/xyz/openbmc_project/software");
1152 }
1153 
1154 /* Fill related item links (i.e. bmc, bios) in for inventory */
1155 inline void getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1156                             const std::string& purpose)
1157 {
1158     if (purpose == sw_util::bmcPurpose)
1159     {
1160         nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
1161         nlohmann::json::object_t item;
1162         item["@odata.id"] = boost::urls::format(
1163             "/redfish/v1/Managers/{}", BMCWEB_REDFISH_MANAGER_URI_NAME);
1164         relatedItem.emplace_back(std::move(item));
1165         asyncResp->res.jsonValue["RelatedItem@odata.count"] =
1166             relatedItem.size();
1167     }
1168     else if (purpose == sw_util::biosPurpose)
1169     {
1170         nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
1171         nlohmann::json::object_t item;
1172         item["@odata.id"] = std::format("/redfish/v1/Systems/{}/Bios",
1173                                         BMCWEB_REDFISH_SYSTEM_URI_NAME);
1174         relatedItem.emplace_back(std::move(item));
1175         asyncResp->res.jsonValue["RelatedItem@odata.count"] =
1176             relatedItem.size();
1177     }
1178     else
1179     {
1180         BMCWEB_LOG_DEBUG("Unknown software purpose {}", purpose);
1181     }
1182 }
1183 
1184 inline void
1185     getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1186                        const std::string& service, const std::string& path,
1187                        const std::string& swId)
1188 {
1189     sdbusplus::asio::getAllProperties(
1190         *crow::connections::systemBus, service, path,
1191         "xyz.openbmc_project.Software.Version",
1192         [asyncResp,
1193          swId](const boost::system::error_code& ec,
1194                const dbus::utility::DBusPropertiesMap& propertiesList) {
1195             if (ec)
1196             {
1197                 messages::internalError(asyncResp->res);
1198                 return;
1199             }
1200 
1201             const std::string* swInvPurpose = nullptr;
1202             const std::string* version = nullptr;
1203 
1204             const bool success = sdbusplus::unpackPropertiesNoThrow(
1205                 dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose",
1206                 swInvPurpose, "Version", version);
1207 
1208             if (!success)
1209             {
1210                 messages::internalError(asyncResp->res);
1211                 return;
1212             }
1213 
1214             if (swInvPurpose == nullptr)
1215             {
1216                 BMCWEB_LOG_DEBUG("Can't find property \"Purpose\"!");
1217                 messages::internalError(asyncResp->res);
1218                 return;
1219             }
1220 
1221             BMCWEB_LOG_DEBUG("swInvPurpose = {}", *swInvPurpose);
1222 
1223             if (version == nullptr)
1224             {
1225                 BMCWEB_LOG_DEBUG("Can't find property \"Version\"!");
1226 
1227                 messages::internalError(asyncResp->res);
1228 
1229                 return;
1230             }
1231             asyncResp->res.jsonValue["Version"] = *version;
1232             asyncResp->res.jsonValue["Id"] = swId;
1233 
1234             // swInvPurpose is of format:
1235             // xyz.openbmc_project.Software.Version.VersionPurpose.ABC
1236             // Translate this to "ABC image"
1237             size_t endDesc = swInvPurpose->rfind('.');
1238             if (endDesc == std::string::npos)
1239             {
1240                 messages::internalError(asyncResp->res);
1241                 return;
1242             }
1243             endDesc++;
1244             if (endDesc >= swInvPurpose->size())
1245             {
1246                 messages::internalError(asyncResp->res);
1247                 return;
1248             }
1249 
1250             std::string formatDesc = swInvPurpose->substr(endDesc);
1251             asyncResp->res.jsonValue["Description"] = formatDesc + " image";
1252             getRelatedItems(asyncResp, *swInvPurpose);
1253         });
1254 }
1255 
1256 inline void handleUpdateServiceFirmwareInventoryGet(
1257     App& app, const crow::Request& req,
1258     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1259     const std::string& param)
1260 {
1261     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1262     {
1263         return;
1264     }
1265     std::shared_ptr<std::string> swId = std::make_shared<std::string>(param);
1266 
1267     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
1268         "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId);
1269 
1270     constexpr std::array<std::string_view, 1> interfaces = {
1271         "xyz.openbmc_project.Software.Version"};
1272     dbus::utility::getSubTree(
1273         "/", 0, interfaces,
1274         [asyncResp,
1275          swId](const boost::system::error_code& ec,
1276                const dbus::utility::MapperGetSubTreeResponse& subtree) {
1277             BMCWEB_LOG_DEBUG("doGet callback...");
1278             if (ec)
1279             {
1280                 messages::internalError(asyncResp->res);
1281                 return;
1282             }
1283 
1284             // Ensure we find our input swId, otherwise return an error
1285             bool found = false;
1286             for (const std::pair<std::string,
1287                                  std::vector<std::pair<
1288                                      std::string, std::vector<std::string>>>>&
1289                      obj : subtree)
1290             {
1291                 if (!obj.first.ends_with(*swId))
1292                 {
1293                     continue;
1294                 }
1295 
1296                 if (obj.second.empty())
1297                 {
1298                     continue;
1299                 }
1300 
1301                 found = true;
1302                 sw_util::getSwStatus(asyncResp, swId, obj.second[0].first);
1303                 getSoftwareVersion(asyncResp, obj.second[0].first, obj.first,
1304                                    *swId);
1305             }
1306             if (!found)
1307             {
1308                 BMCWEB_LOG_WARNING("Input swID {} not found!", *swId);
1309                 messages::resourceMissingAtURI(
1310                     asyncResp->res,
1311                     boost::urls::format(
1312                         "/redfish/v1/UpdateService/FirmwareInventory/{}",
1313                         *swId));
1314                 return;
1315             }
1316             asyncResp->res.jsonValue["@odata.type"] =
1317                 "#SoftwareInventory.v1_1_0.SoftwareInventory";
1318             asyncResp->res.jsonValue["Name"] = "Software Inventory";
1319             asyncResp->res.jsonValue["Status"]["HealthRollup"] =
1320                 resource::Health::OK;
1321 
1322             asyncResp->res.jsonValue["Updateable"] = false;
1323             sw_util::getSwUpdatableStatus(asyncResp, swId);
1324         });
1325 }
1326 
1327 inline void requestRoutesUpdateService(App& app)
1328 {
1329     BMCWEB_ROUTE(
1330         app, "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/")
1331         .privileges(redfish::privileges::postUpdateService)
1332         .methods(boost::beast::http::verb::post)(std::bind_front(
1333             handleUpdateServiceSimpleUpdateAction, std::ref(app)));
1334 
1335     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/")
1336         .privileges(redfish::privileges::getSoftwareInventory)
1337         .methods(boost::beast::http::verb::get)(std::bind_front(
1338             handleUpdateServiceFirmwareInventoryGet, std::ref(app)));
1339 
1340     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
1341         .privileges(redfish::privileges::getUpdateService)
1342         .methods(boost::beast::http::verb::get)(
1343             std::bind_front(handleUpdateServiceGet, std::ref(app)));
1344 
1345     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/")
1346         .privileges(redfish::privileges::postUpdateService)
1347         .methods(boost::beast::http::verb::post)(
1348             std::bind_front(handleUpdateServicePost, std::ref(app)));
1349 
1350     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/")
1351         .privileges(redfish::privileges::getSoftwareInventoryCollection)
1352         .methods(boost::beast::http::verb::get)(std::bind_front(
1353             handleUpdateServiceFirmwareInventoryCollectionGet, std::ref(app)));
1354 }
1355 
1356 } // namespace redfish
1357