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