xref: /openbmc/bmcweb/features/redfish/lib/update_service.hpp (revision 6b0f66bdaa12ef09bf628f2610ea48ac14888b6f)
1729dae72SJennifer Lee /*
2729dae72SJennifer Lee // Copyright (c) 2018 Intel Corporation
3729dae72SJennifer Lee //
4729dae72SJennifer Lee // Licensed under the Apache License, Version 2.0 (the "License");
5729dae72SJennifer Lee // you may not use this file except in compliance with the License.
6729dae72SJennifer Lee // You may obtain a copy of the License at
7729dae72SJennifer Lee //
8729dae72SJennifer Lee //      http://www.apache.org/licenses/LICENSE-2.0
9729dae72SJennifer Lee //
10729dae72SJennifer Lee // Unless required by applicable law or agreed to in writing, software
11729dae72SJennifer Lee // distributed under the License is distributed on an "AS IS" BASIS,
12729dae72SJennifer Lee // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13729dae72SJennifer Lee // See the License for the specific language governing permissions and
14729dae72SJennifer Lee // limitations under the License.
15729dae72SJennifer Lee */
16729dae72SJennifer Lee #pragma once
17729dae72SJennifer Lee 
18d61e5194STejas Patil #include "bmcweb_config.h"
19d61e5194STejas Patil 
203ccb3adbSEd Tanous #include "app.hpp"
213ccb3adbSEd Tanous #include "dbus_utility.hpp"
220ed80c8cSGeorge Liu #include "multipart_parser.hpp"
232c6ffdb0SEd Tanous #include "ossl_random.hpp"
243ccb3adbSEd Tanous #include "query.hpp"
253ccb3adbSEd Tanous #include "registries/privilege_registry.hpp"
26a8e884fcSEd Tanous #include "task.hpp"
2708d81adaSJohn Edward Broadbent #include "utils/collection.hpp"
283ccb3adbSEd Tanous #include "utils/dbus_utils.hpp"
293ccb3adbSEd Tanous #include "utils/sw_utils.hpp"
303ccb3adbSEd Tanous 
31e99073f5SGeorge Liu #include <boost/system/error_code.hpp>
32ef4c65b7SEd Tanous #include <boost/url/format.hpp>
331e1e598dSJonathan Doman #include <sdbusplus/asio/property.hpp>
343ccb3adbSEd Tanous #include <sdbusplus/bus/match.hpp>
35d1bde9e5SKrzysztof Grobelny #include <sdbusplus/unpack_properties.hpp>
361214b7e7SGunnar Mills 
372b73119cSGeorge Liu #include <array>
380ed80c8cSGeorge Liu #include <filesystem>
397cb59f65SEd Tanous #include <optional>
407cb59f65SEd Tanous #include <string>
412b73119cSGeorge Liu #include <string_view>
422b73119cSGeorge Liu 
431abe55efSEd Tanous namespace redfish
441abe55efSEd Tanous {
4527826b5fSEd Tanous 
460e7de46fSAndrew Geissler // Match signals added on software path
47cf9e417dSEd Tanous // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
4859d494eeSPatrick Williams static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateMatcher;
49cf9e417dSEd Tanous // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
5059d494eeSPatrick Williams static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateErrorMatcher;
510e7de46fSAndrew Geissler // Only allow one update at a time
52cf9e417dSEd Tanous // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
530e7de46fSAndrew Geissler static bool fwUpdateInProgress = false;
5486adcd6dSAndrew Geissler // Timer for software available
55cf9e417dSEd Tanous // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
56271584abSEd Tanous static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer;
5786adcd6dSAndrew Geissler 
58df254f2cSEd Tanous inline void cleanUp()
5986adcd6dSAndrew Geissler {
6086adcd6dSAndrew Geissler     fwUpdateInProgress = false;
6186adcd6dSAndrew Geissler     fwUpdateMatcher = nullptr;
624cde5d90SJames Feist     fwUpdateErrorMatcher = nullptr;
6386adcd6dSAndrew Geissler }
64df254f2cSEd Tanous 
65df254f2cSEd Tanous inline void activateImage(const std::string& objPath,
6686adcd6dSAndrew Geissler                           const std::string& service)
6786adcd6dSAndrew Geissler {
6862598e31SEd Tanous     BMCWEB_LOG_DEBUG("Activate image for {} {}", objPath, service);
699ae226faSGeorge Liu     sdbusplus::asio::setProperty(
709ae226faSGeorge Liu         *crow::connections::systemBus, service, objPath,
719ae226faSGeorge Liu         "xyz.openbmc_project.Software.Activation", "RequestedActivation",
729ae226faSGeorge Liu         "xyz.openbmc_project.Software.Activation.RequestedActivations.Active",
738b24275dSEd Tanous         [](const boost::system::error_code& ec) {
748b24275dSEd Tanous         if (ec)
7586adcd6dSAndrew Geissler         {
7662598e31SEd Tanous             BMCWEB_LOG_DEBUG("error_code = {}", ec);
7762598e31SEd Tanous             BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
7886adcd6dSAndrew Geissler         }
799ae226faSGeorge Liu     });
8086adcd6dSAndrew Geissler }
810554c984SAndrew Geissler 
820554c984SAndrew Geissler // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
830554c984SAndrew Geissler // then no asyncResp updates will occur
848d1b46d7Szhanghch05 static void
858d1b46d7Szhanghch05     softwareInterfaceAdded(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
8659d494eeSPatrick Williams                            sdbusplus::message_t& m, task::Payload&& payload)
8786adcd6dSAndrew Geissler {
8880f79a40SMichael Shen     dbus::utility::DBusInterfacesMap interfacesProperties;
8986adcd6dSAndrew Geissler 
9086adcd6dSAndrew Geissler     sdbusplus::message::object_path objPath;
9186adcd6dSAndrew Geissler 
9286adcd6dSAndrew Geissler     m.read(objPath, interfacesProperties);
9386adcd6dSAndrew Geissler 
9462598e31SEd Tanous     BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
95e3eb3d63SEd Tanous     for (const auto& interface : interfacesProperties)
9686adcd6dSAndrew Geissler     {
9762598e31SEd Tanous         BMCWEB_LOG_DEBUG("interface = {}", interface.first);
9886adcd6dSAndrew Geissler 
9986adcd6dSAndrew Geissler         if (interface.first == "xyz.openbmc_project.Software.Activation")
10086adcd6dSAndrew Geissler         {
10186adcd6dSAndrew Geissler             // Retrieve service and activate
1022b73119cSGeorge Liu             constexpr std::array<std::string_view, 1> interfaces = {
1032b73119cSGeorge Liu                 "xyz.openbmc_project.Software.Activation"};
1042b73119cSGeorge Liu             dbus::utility::getDbusObject(
1052b73119cSGeorge Liu                 objPath.str, interfaces,
106a3e65892SEd Tanous                 [objPath, asyncResp, payload(std::move(payload))](
1078b24275dSEd Tanous                     const boost::system::error_code& ec,
108a3e65892SEd Tanous                     const std::vector<
109a3e65892SEd Tanous                         std::pair<std::string, std::vector<std::string>>>&
110a3e65892SEd Tanous                         objInfo) mutable {
1118b24275dSEd Tanous                 if (ec)
11286adcd6dSAndrew Geissler                 {
11362598e31SEd Tanous                     BMCWEB_LOG_DEBUG("error_code = {}", ec);
11462598e31SEd Tanous                     BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
1150554c984SAndrew Geissler                     if (asyncResp)
1160554c984SAndrew Geissler                     {
11786adcd6dSAndrew Geissler                         messages::internalError(asyncResp->res);
1180554c984SAndrew Geissler                     }
11986adcd6dSAndrew Geissler                     cleanUp();
12086adcd6dSAndrew Geissler                     return;
12186adcd6dSAndrew Geissler                 }
12286adcd6dSAndrew Geissler                 // Ensure we only got one service back
12386adcd6dSAndrew Geissler                 if (objInfo.size() != 1)
12486adcd6dSAndrew Geissler                 {
12562598e31SEd Tanous                     BMCWEB_LOG_ERROR("Invalid Object Size {}", objInfo.size());
1260554c984SAndrew Geissler                     if (asyncResp)
1270554c984SAndrew Geissler                     {
12886adcd6dSAndrew Geissler                         messages::internalError(asyncResp->res);
1290554c984SAndrew Geissler                     }
13086adcd6dSAndrew Geissler                     cleanUp();
13186adcd6dSAndrew Geissler                     return;
13286adcd6dSAndrew Geissler                 }
13386adcd6dSAndrew Geissler                 // cancel timer only when
13486adcd6dSAndrew Geissler                 // xyz.openbmc_project.Software.Activation interface
13586adcd6dSAndrew Geissler                 // is added
13686adcd6dSAndrew Geissler                 fwAvailableTimer = nullptr;
13786adcd6dSAndrew Geissler 
13886adcd6dSAndrew Geissler                 activateImage(objPath.str, objInfo[0].first);
1390554c984SAndrew Geissler                 if (asyncResp)
1400554c984SAndrew Geissler                 {
14132898ceaSJames Feist                     std::shared_ptr<task::TaskData> task =
14232898ceaSJames Feist                         task::TaskData::createTask(
1438b24275dSEd Tanous                             [](const boost::system::error_code& ec2,
14459d494eeSPatrick Williams                                sdbusplus::message_t& msg,
1451214b7e7SGunnar Mills                                const std::shared_ptr<task::TaskData>&
1461214b7e7SGunnar Mills                                    taskData) {
1478b24275dSEd Tanous                         if (ec2)
14832898ceaSJames Feist                         {
14932898ceaSJames Feist                             return task::completed;
15032898ceaSJames Feist                         }
15132898ceaSJames Feist 
15232898ceaSJames Feist                         std::string iface;
153b9d36b47SEd Tanous                         dbus::utility::DBusPropertiesMap values;
154fd9ab9e1SJames Feist 
155002d39b4SEd Tanous                         std::string index = std::to_string(taskData->index);
15632898ceaSJames Feist                         msg.read(iface, values);
157fd9ab9e1SJames Feist 
158002d39b4SEd Tanous                         if (iface == "xyz.openbmc_project.Software.Activation")
159fd9ab9e1SJames Feist                         {
1600fb5b505SGayathri Leburu                             const std::string* state = nullptr;
161b9d36b47SEd Tanous                             for (const auto& property : values)
16232898ceaSJames Feist                             {
163b9d36b47SEd Tanous                                 if (property.first == "Activation")
164b9d36b47SEd Tanous                                 {
1650fb5b505SGayathri Leburu                                     state = std::get_if<std::string>(
166b9d36b47SEd Tanous                                         &property.second);
1670fb5b505SGayathri Leburu                                     if (state == nullptr)
168b9d36b47SEd Tanous                                     {
169002d39b4SEd Tanous                                         taskData->messages.emplace_back(
170002d39b4SEd Tanous                                             messages::internalError());
171b9d36b47SEd Tanous                                         return task::completed;
172b9d36b47SEd Tanous                                     }
173b9d36b47SEd Tanous                                 }
174b9d36b47SEd Tanous                             }
17532898ceaSJames Feist 
17632898ceaSJames Feist                             if (state == nullptr)
17732898ceaSJames Feist                             {
178b9d36b47SEd Tanous                                 return !task::completed;
17932898ceaSJames Feist                             }
18032898ceaSJames Feist 
18111ba3979SEd Tanous                             if (state->ends_with("Invalid") ||
18211ba3979SEd Tanous                                 state->ends_with("Failed"))
18332898ceaSJames Feist                             {
18432898ceaSJames Feist                                 taskData->state = "Exception";
18532898ceaSJames Feist                                 taskData->status = "Warning";
18632898ceaSJames Feist                                 taskData->messages.emplace_back(
187e5d5006bSJames Feist                                     messages::taskAborted(index));
18832898ceaSJames Feist                                 return task::completed;
18932898ceaSJames Feist                             }
19032898ceaSJames Feist 
19111ba3979SEd Tanous                             if (state->ends_with("Staged"))
19232898ceaSJames Feist                             {
193fd9ab9e1SJames Feist                                 taskData->state = "Stopping";
194fd9ab9e1SJames Feist                                 taskData->messages.emplace_back(
195fd9ab9e1SJames Feist                                     messages::taskPaused(index));
196fd9ab9e1SJames Feist 
197fd9ab9e1SJames Feist                                 // its staged, set a long timer to
198fd9ab9e1SJames Feist                                 // allow them time to complete the
199fd9ab9e1SJames Feist                                 // update (probably cycle the
200fd9ab9e1SJames Feist                                 // system) if this expires then
2018ece0e45SEd Tanous                                 // task will be canceled
202002d39b4SEd Tanous                                 taskData->extendTimer(std::chrono::hours(5));
20332898ceaSJames Feist                                 return !task::completed;
20432898ceaSJames Feist                             }
20532898ceaSJames Feist 
20611ba3979SEd Tanous                             if (state->ends_with("Active"))
20732898ceaSJames Feist                             {
20832898ceaSJames Feist                                 taskData->messages.emplace_back(
209002d39b4SEd Tanous                                     messages::taskCompletedOK(index));
21032898ceaSJames Feist                                 taskData->state = "Completed";
21132898ceaSJames Feist                                 return task::completed;
21232898ceaSJames Feist                             }
213fd9ab9e1SJames Feist                         }
2140fda0f12SGeorge Liu                         else if (
2150fda0f12SGeorge Liu                             iface ==
2160fda0f12SGeorge Liu                             "xyz.openbmc_project.Software.ActivationProgress")
217fd9ab9e1SJames Feist                         {
218b9d36b47SEd Tanous                             const uint8_t* progress = nullptr;
219b9d36b47SEd Tanous                             for (const auto& property : values)
220fd9ab9e1SJames Feist                             {
221b9d36b47SEd Tanous                                 if (property.first == "Progress")
222b9d36b47SEd Tanous                                 {
2230fb5b505SGayathri Leburu                                     progress =
2240fb5b505SGayathri Leburu                                         std::get_if<uint8_t>(&property.second);
2250fb5b505SGayathri Leburu                                     if (progress == nullptr)
226b9d36b47SEd Tanous                                     {
227002d39b4SEd Tanous                                         taskData->messages.emplace_back(
228002d39b4SEd Tanous                                             messages::internalError());
229b9d36b47SEd Tanous                                         return task::completed;
230fd9ab9e1SJames Feist                                     }
231b9d36b47SEd Tanous                                 }
232b9d36b47SEd Tanous                             }
233fd9ab9e1SJames Feist 
234fd9ab9e1SJames Feist                             if (progress == nullptr)
235fd9ab9e1SJames Feist                             {
236b9d36b47SEd Tanous                                 return !task::completed;
237fd9ab9e1SJames Feist                             }
2380fb5b505SGayathri Leburu                             taskData->percentComplete = *progress;
239fd9ab9e1SJames Feist                             taskData->messages.emplace_back(
2400fb5b505SGayathri Leburu                                 messages::taskProgressChanged(index,
2410fb5b505SGayathri Leburu                                                               *progress));
242fd9ab9e1SJames Feist 
243fd9ab9e1SJames Feist                             // if we're getting status updates it's
244fd9ab9e1SJames Feist                             // still alive, update timer
245002d39b4SEd Tanous                             taskData->extendTimer(std::chrono::minutes(5));
246fd9ab9e1SJames Feist                         }
24732898ceaSJames Feist 
24832898ceaSJames Feist                         // as firmware update often results in a
24932898ceaSJames Feist                         // reboot, the task  may never "complete"
25032898ceaSJames Feist                         // unless it is an error
25132898ceaSJames Feist 
25232898ceaSJames Feist                         return !task::completed;
25332898ceaSJames Feist                     },
2540fda0f12SGeorge Liu                             "type='signal',interface='org.freedesktop.DBus.Properties',"
255fd9ab9e1SJames Feist                             "member='PropertiesChanged',path='" +
25632898ceaSJames Feist                                 objPath.str + "'");
25732898ceaSJames Feist                     task->startTimer(std::chrono::minutes(5));
25832898ceaSJames Feist                     task->populateResp(asyncResp->res);
259a3e65892SEd Tanous                     task->payload.emplace(std::move(payload));
2600554c984SAndrew Geissler                 }
26186adcd6dSAndrew Geissler                 fwUpdateInProgress = false;
2622b73119cSGeorge Liu             });
26362bafc01SPatrick Williams 
26462bafc01SPatrick Williams             break;
26586adcd6dSAndrew Geissler         }
26686adcd6dSAndrew Geissler     }
26786adcd6dSAndrew Geissler }
26886adcd6dSAndrew Geissler 
2698549b951SMyung Bae inline void afterAvailbleTimerAsyncWait(
2708549b951SMyung Bae     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2718549b951SMyung Bae     const boost::system::error_code& ec)
2728549b951SMyung Bae {
2738549b951SMyung Bae     cleanUp();
2748549b951SMyung Bae     if (ec == boost::asio::error::operation_aborted)
2758549b951SMyung Bae     {
2768549b951SMyung Bae         // expected, we were canceled before the timer completed.
2778549b951SMyung Bae         return;
2788549b951SMyung Bae     }
2798549b951SMyung Bae     BMCWEB_LOG_ERROR("Timed out waiting for firmware object being created");
2808549b951SMyung Bae     BMCWEB_LOG_ERROR("FW image may has already been uploaded to server");
2818549b951SMyung Bae     if (ec)
2828549b951SMyung Bae     {
2838549b951SMyung Bae         BMCWEB_LOG_ERROR("Async_wait failed{}", ec);
2848549b951SMyung Bae         return;
2858549b951SMyung Bae     }
2868549b951SMyung Bae     if (asyncResp)
2878549b951SMyung Bae     {
2888549b951SMyung Bae         redfish::messages::internalError(asyncResp->res);
2898549b951SMyung Bae     }
2908549b951SMyung Bae }
2918549b951SMyung Bae 
2928549b951SMyung Bae inline void
2938549b951SMyung Bae     handleUpdateErrorType(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2948549b951SMyung Bae                           const std::string& url, const std::string& type)
2958549b951SMyung Bae {
2968549b951SMyung Bae     if (type == "xyz.openbmc_project.Software.Image.Error.UnTarFailure")
2978549b951SMyung Bae     {
2988549b951SMyung Bae         redfish::messages::invalidUpload(asyncResp->res, url,
2998549b951SMyung Bae                                          "Invalid archive");
3008549b951SMyung Bae     }
3018549b951SMyung Bae     else if (type ==
3028549b951SMyung Bae              "xyz.openbmc_project.Software.Image.Error.ManifestFileFailure")
3038549b951SMyung Bae     {
3048549b951SMyung Bae         redfish::messages::invalidUpload(asyncResp->res, url,
3058549b951SMyung Bae                                          "Invalid manifest");
3068549b951SMyung Bae     }
3078549b951SMyung Bae     else if (type == "xyz.openbmc_project.Software.Image.Error.ImageFailure")
3088549b951SMyung Bae     {
3098549b951SMyung Bae         redfish::messages::invalidUpload(asyncResp->res, url,
3108549b951SMyung Bae                                          "Invalid image format");
3118549b951SMyung Bae     }
3128549b951SMyung Bae     else if (type == "xyz.openbmc_project.Software.Version.Error.AlreadyExists")
3138549b951SMyung Bae     {
3148549b951SMyung Bae         redfish::messages::invalidUpload(asyncResp->res, url,
3158549b951SMyung Bae                                          "Image version already exists");
3168549b951SMyung Bae 
3178549b951SMyung Bae         redfish::messages::resourceAlreadyExists(
3188549b951SMyung Bae             asyncResp->res, "UpdateService", "Version", "uploaded version");
3198549b951SMyung Bae     }
3208549b951SMyung Bae     else if (type == "xyz.openbmc_project.Software.Image.Error.BusyFailure")
3218549b951SMyung Bae     {
3228549b951SMyung Bae         redfish::messages::resourceExhaustion(asyncResp->res, url);
3238549b951SMyung Bae     }
3244034a652SMyung Bae     else if (type == "xyz.openbmc_project.Software.Version.Error.Incompatible")
3258549b951SMyung Bae     {
3264034a652SMyung Bae         redfish::messages::invalidUpload(asyncResp->res, url,
3274034a652SMyung Bae                                          "Incompatible image version");
3284034a652SMyung Bae     }
3294034a652SMyung Bae     else if (type ==
3304034a652SMyung Bae              "xyz.openbmc_project.Software.Version.Error.ExpiredAccessKey")
3314034a652SMyung Bae     {
3324034a652SMyung Bae         redfish::messages::invalidUpload(asyncResp->res, url,
3334034a652SMyung Bae                                          "Update Access Key Expired");
3344034a652SMyung Bae     }
3354034a652SMyung Bae     else if (type ==
3364034a652SMyung Bae              "xyz.openbmc_project.Software.Version.Error.InvalidSignature")
3374034a652SMyung Bae     {
3384034a652SMyung Bae         redfish::messages::invalidUpload(asyncResp->res, url,
3394034a652SMyung Bae                                          "Invalid image signature");
3404034a652SMyung Bae     }
3414034a652SMyung Bae     else if (type ==
3424034a652SMyung Bae                  "xyz.openbmc_project.Software.Image.Error.InternalFailure" ||
3434034a652SMyung Bae              type == "xyz.openbmc_project.Software.Version.Error.HostFile")
3444034a652SMyung Bae     {
3454034a652SMyung Bae         BMCWEB_LOG_ERROR("Software Image Error type={}", type);
3468549b951SMyung Bae         redfish::messages::internalError(asyncResp->res);
3478549b951SMyung Bae     }
3484034a652SMyung Bae     else
3494034a652SMyung Bae     {
3504034a652SMyung Bae         // Unrelated error types. Ignored
3514034a652SMyung Bae         BMCWEB_LOG_INFO("Non-Software-related Error type={}. Ignored", type);
3524034a652SMyung Bae         return;
3534034a652SMyung Bae     }
3544034a652SMyung Bae     // Clear the timer
3554034a652SMyung Bae     fwAvailableTimer = nullptr;
3568549b951SMyung Bae }
3578549b951SMyung Bae 
3588549b951SMyung Bae inline void
3598549b951SMyung Bae     afterUpdateErrorMatcher(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3608549b951SMyung Bae                             const std::string& url, sdbusplus::message_t& m)
3618549b951SMyung Bae {
36280f79a40SMichael Shen     dbus::utility::DBusInterfacesMap interfacesProperties;
3638549b951SMyung Bae     sdbusplus::message::object_path objPath;
3648549b951SMyung Bae     m.read(objPath, interfacesProperties);
3658549b951SMyung Bae     BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
3668549b951SMyung Bae     for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>&
3678549b951SMyung Bae              interface : interfacesProperties)
3688549b951SMyung Bae     {
3698549b951SMyung Bae         if (interface.first == "xyz.openbmc_project.Logging.Entry")
3708549b951SMyung Bae         {
3718549b951SMyung Bae             for (const std::pair<std::string, dbus::utility::DbusVariantType>&
3728549b951SMyung Bae                      value : interface.second)
3738549b951SMyung Bae             {
3748549b951SMyung Bae                 if (value.first != "Message")
3758549b951SMyung Bae                 {
3768549b951SMyung Bae                     continue;
3778549b951SMyung Bae                 }
3788549b951SMyung Bae                 const std::string* type =
3798549b951SMyung Bae                     std::get_if<std::string>(&value.second);
3808549b951SMyung Bae                 if (type == nullptr)
3818549b951SMyung Bae                 {
3828549b951SMyung Bae                     // if this was our message, timeout will cover it
3838549b951SMyung Bae                     return;
3848549b951SMyung Bae                 }
3858549b951SMyung Bae                 handleUpdateErrorType(asyncResp, url, *type);
3868549b951SMyung Bae             }
3878549b951SMyung Bae         }
3888549b951SMyung Bae     }
3898549b951SMyung Bae }
3908549b951SMyung Bae 
3910554c984SAndrew Geissler // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
3920554c984SAndrew Geissler // then no asyncResp updates will occur
393f5139334SEd Tanous inline void monitorForSoftwareAvailable(
3948d1b46d7Szhanghch05     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3958d1b46d7Szhanghch05     const crow::Request& req, const std::string& url,
3965d138943SGunnar Mills     int timeoutTimeSeconds = 25)
39786adcd6dSAndrew Geissler {
39886adcd6dSAndrew Geissler     // Only allow one FW update at a time
399e05aec50SEd Tanous     if (fwUpdateInProgress)
40086adcd6dSAndrew Geissler     {
4010554c984SAndrew Geissler         if (asyncResp)
4020554c984SAndrew Geissler         {
40386adcd6dSAndrew Geissler             messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
4040554c984SAndrew Geissler         }
40586adcd6dSAndrew Geissler         return;
40686adcd6dSAndrew Geissler     }
40786adcd6dSAndrew Geissler 
4088e8245dbSEd Tanous     if (req.ioService == nullptr)
4098e8245dbSEd Tanous     {
4108e8245dbSEd Tanous         messages::internalError(asyncResp->res);
4118e8245dbSEd Tanous         return;
4128e8245dbSEd Tanous     }
4138e8245dbSEd Tanous 
4140554c984SAndrew Geissler     fwAvailableTimer =
415271584abSEd Tanous         std::make_unique<boost::asio::steady_timer>(*req.ioService);
41686adcd6dSAndrew Geissler 
417271584abSEd Tanous     fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds));
41886adcd6dSAndrew Geissler 
41986adcd6dSAndrew Geissler     fwAvailableTimer->async_wait(
4208549b951SMyung Bae         std::bind_front(afterAvailbleTimerAsyncWait, asyncResp));
4218549b951SMyung Bae 
422a3e65892SEd Tanous     task::Payload payload(req);
42359d494eeSPatrick Williams     auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable {
42462598e31SEd Tanous         BMCWEB_LOG_DEBUG("Match fired");
425a3e65892SEd Tanous         softwareInterfaceAdded(asyncResp, m, std::move(payload));
42686adcd6dSAndrew Geissler     };
42786adcd6dSAndrew Geissler 
42886adcd6dSAndrew Geissler     fwUpdateInProgress = true;
42986adcd6dSAndrew Geissler 
43059d494eeSPatrick Williams     fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>(
43186adcd6dSAndrew Geissler         *crow::connections::systemBus,
43286adcd6dSAndrew Geissler         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
43386adcd6dSAndrew Geissler         "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
43486adcd6dSAndrew Geissler         callback);
4354cde5d90SJames Feist 
43659d494eeSPatrick Williams     fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>(
4374cde5d90SJames Feist         *crow::connections::systemBus,
438e1cc4828SBrian Ma         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
439e1cc4828SBrian Ma         "member='InterfacesAdded',"
440e1cc4828SBrian Ma         "path='/xyz/openbmc_project/logging'",
4418549b951SMyung Bae         std::bind_front(afterUpdateErrorMatcher, asyncResp, url));
44286adcd6dSAndrew Geissler }
443729dae72SJennifer Lee 
444f86bcc87SEd Tanous struct TftpUrl
445f86bcc87SEd Tanous {
446f86bcc87SEd Tanous     std::string fwFile;
447f86bcc87SEd Tanous     std::string tftpServer;
448f86bcc87SEd Tanous };
449f86bcc87SEd Tanous 
450f86bcc87SEd Tanous inline std::optional<TftpUrl>
451f86bcc87SEd Tanous     parseTftpUrl(std::string imageURI,
452f86bcc87SEd Tanous                  std::optional<std::string> transferProtocol,
453f86bcc87SEd Tanous                  crow::Response& res)
454f86bcc87SEd Tanous {
455f86bcc87SEd Tanous     if (imageURI.find("://") == std::string::npos)
456f86bcc87SEd Tanous     {
457f86bcc87SEd Tanous         if (imageURI.starts_with("/"))
458f86bcc87SEd Tanous         {
459f86bcc87SEd Tanous             messages::actionParameterValueTypeError(
460f86bcc87SEd Tanous                 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
461f86bcc87SEd Tanous             return std::nullopt;
462f86bcc87SEd Tanous         }
463f86bcc87SEd Tanous         if (!transferProtocol)
464f86bcc87SEd Tanous         {
465f86bcc87SEd Tanous             messages::actionParameterValueTypeError(
466f86bcc87SEd Tanous                 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
467f86bcc87SEd Tanous             return std::nullopt;
468f86bcc87SEd Tanous         }
469f86bcc87SEd Tanous         // OpenBMC currently only supports TFTP
470f86bcc87SEd Tanous         if (*transferProtocol != "TFTP")
471f86bcc87SEd Tanous         {
472f86bcc87SEd Tanous             messages::actionParameterNotSupported(res, "TransferProtocol",
473f86bcc87SEd Tanous                                                   *transferProtocol);
474f86bcc87SEd Tanous             BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}",
475f86bcc87SEd Tanous                              *transferProtocol);
476f86bcc87SEd Tanous             return std::nullopt;
477f86bcc87SEd Tanous         }
478f86bcc87SEd Tanous         imageURI = "tftp://" + imageURI;
479f86bcc87SEd Tanous     }
480f86bcc87SEd Tanous 
481f86bcc87SEd Tanous     boost::system::result<boost::urls::url> url =
482f86bcc87SEd Tanous         boost::urls::parse_absolute_uri(imageURI);
483f86bcc87SEd Tanous     if (!url)
484f86bcc87SEd Tanous     {
485f86bcc87SEd Tanous         messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
486f86bcc87SEd Tanous                                                 "UpdateService.SimpleUpdate");
487f86bcc87SEd Tanous 
488f86bcc87SEd Tanous         return std::nullopt;
489f86bcc87SEd Tanous     }
490f86bcc87SEd Tanous     url->normalize();
491f86bcc87SEd Tanous 
492f86bcc87SEd Tanous     if (url->scheme() != "tftp")
493f86bcc87SEd Tanous     {
494f86bcc87SEd Tanous         messages::actionParameterNotSupported(res, "ImageURI", imageURI);
495f86bcc87SEd Tanous         return std::nullopt;
496f86bcc87SEd Tanous     }
497f86bcc87SEd Tanous     std::string path(url->encoded_path());
498f86bcc87SEd Tanous     if (path.size() < 2)
499f86bcc87SEd Tanous     {
500f86bcc87SEd Tanous         messages::actionParameterNotSupported(res, "ImageURI", imageURI);
501f86bcc87SEd Tanous         return std::nullopt;
502f86bcc87SEd Tanous     }
503f86bcc87SEd Tanous     path.erase(0, 1);
504f86bcc87SEd Tanous     std::string host(url->encoded_host_and_port());
505f86bcc87SEd Tanous     return TftpUrl{path, host};
506f86bcc87SEd Tanous }
507f86bcc87SEd Tanous 
508*6b0f66bdSEd Tanous inline void doTftpUpdate(const crow::Request& req,
509*6b0f66bdSEd Tanous                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
510*6b0f66bdSEd Tanous                          const TftpUrl& tftpUrl)
511*6b0f66bdSEd Tanous {
512*6b0f66bdSEd Tanous #ifndef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE
513*6b0f66bdSEd Tanous     messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
514*6b0f66bdSEd Tanous                                           tftpUrl.tftpServer);
515*6b0f66bdSEd Tanous     return;
516*6b0f66bdSEd Tanous #endif
517*6b0f66bdSEd Tanous     BMCWEB_LOG_DEBUG("Server: {} File: {}", tftpUrl.tftpServer, tftpUrl.fwFile);
518*6b0f66bdSEd Tanous 
519*6b0f66bdSEd Tanous     // Setup callback for when new software detected
520*6b0f66bdSEd Tanous     // Give TFTP 10 minutes to complete
521*6b0f66bdSEd Tanous     monitorForSoftwareAvailable(
522*6b0f66bdSEd Tanous         asyncResp, req,
523*6b0f66bdSEd Tanous         "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate", 600);
524*6b0f66bdSEd Tanous 
525*6b0f66bdSEd Tanous     // TFTP can take up to 10 minutes depending on image size and
526*6b0f66bdSEd Tanous     // connection speed. Return to caller as soon as the TFTP operation
527*6b0f66bdSEd Tanous     // has been started. The callback above will ensure the activate
528*6b0f66bdSEd Tanous     // is started once the download has completed
529*6b0f66bdSEd Tanous     redfish::messages::success(asyncResp->res);
530*6b0f66bdSEd Tanous 
531*6b0f66bdSEd Tanous     // Call TFTP service
532*6b0f66bdSEd Tanous     crow::connections::systemBus->async_method_call(
533*6b0f66bdSEd Tanous         [](const boost::system::error_code& ec) {
534*6b0f66bdSEd Tanous         if (ec)
535*6b0f66bdSEd Tanous         {
536*6b0f66bdSEd Tanous             // messages::internalError(asyncResp->res);
537*6b0f66bdSEd Tanous             cleanUp();
538*6b0f66bdSEd Tanous             BMCWEB_LOG_DEBUG("error_code = {}", ec);
539*6b0f66bdSEd Tanous             BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
540*6b0f66bdSEd Tanous         }
541*6b0f66bdSEd Tanous         else
542*6b0f66bdSEd Tanous         {
543*6b0f66bdSEd Tanous             BMCWEB_LOG_DEBUG("Call to DownloaViaTFTP Success");
544*6b0f66bdSEd Tanous         }
545*6b0f66bdSEd Tanous     },
546*6b0f66bdSEd Tanous         "xyz.openbmc_project.Software.Download",
547*6b0f66bdSEd Tanous         "/xyz/openbmc_project/software", "xyz.openbmc_project.Common.TFTP",
548*6b0f66bdSEd Tanous         "DownloadViaTFTP", tftpUrl.fwFile, tftpUrl.tftpServer);
549*6b0f66bdSEd Tanous }
550*6b0f66bdSEd Tanous 
551f5139334SEd Tanous inline void handleUpdateServiceSimpleUpdateAction(
552f5139334SEd Tanous     crow::App& app, const crow::Request& req,
553f5139334SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
5540554c984SAndrew Geissler {
5553ba00073SCarson Labrado     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
55645ca1b86SEd Tanous     {
55745ca1b86SEd Tanous         return;
55845ca1b86SEd Tanous     }
55945ca1b86SEd Tanous 
5600554c984SAndrew Geissler     std::optional<std::string> transferProtocol;
5610554c984SAndrew Geissler     std::string imageURI;
5620554c984SAndrew Geissler 
56362598e31SEd Tanous     BMCWEB_LOG_DEBUG("Enter UpdateService.SimpleUpdate doPost");
5640554c984SAndrew Geissler 
5650554c984SAndrew Geissler     // User can pass in both TransferProtocol and ImageURI parameters or
5664e0453b1SGunnar Mills     // they can pass in just the ImageURI with the transfer protocol
5674e0453b1SGunnar Mills     // embedded within it.
5680554c984SAndrew Geissler     // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin
5690554c984SAndrew Geissler     // 2) ImageURI:tftp://1.1.1.1/myfile.bin
5700554c984SAndrew Geissler 
571002d39b4SEd Tanous     if (!json_util::readJsonAction(req, asyncResp->res, "TransferProtocol",
572002d39b4SEd Tanous                                    transferProtocol, "ImageURI", imageURI))
5730554c984SAndrew Geissler     {
57462598e31SEd Tanous         BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter");
5750554c984SAndrew Geissler         return;
5760554c984SAndrew Geissler     }
577f5139334SEd Tanous 
578f86bcc87SEd Tanous     std::optional<TftpUrl> ret = parseTftpUrl(imageURI, transferProtocol,
579f86bcc87SEd Tanous                                               asyncResp->res);
580f86bcc87SEd Tanous     if (!ret)
5810554c984SAndrew Geissler     {
5820554c984SAndrew Geissler         return;
5830554c984SAndrew Geissler     }
5840554c984SAndrew Geissler 
585f86bcc87SEd Tanous     BMCWEB_LOG_DEBUG("Server: {} File: {}", ret->tftpServer, ret->fwFile);
586*6b0f66bdSEd Tanous     doTftpUpdate(req, asyncResp, *ret);
5870554c984SAndrew Geissler 
58862598e31SEd Tanous     BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost");
589729dae72SJennifer Lee }
590729dae72SJennifer Lee 
5910ed80c8cSGeorge Liu inline void uploadImageFile(crow::Response& res, std::string_view body)
5920ed80c8cSGeorge Liu {
5932c6ffdb0SEd Tanous     std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID());
5942c6ffdb0SEd Tanous 
59562598e31SEd Tanous     BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string());
5960ed80c8cSGeorge Liu     std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
5970ed80c8cSGeorge Liu                                     std::ofstream::trunc);
5980ed80c8cSGeorge Liu     // set the permission of the file to 640
59989492a15SPatrick Williams     std::filesystem::perms permission = std::filesystem::perms::owner_read |
60089492a15SPatrick Williams                                         std::filesystem::perms::group_read;
6010ed80c8cSGeorge Liu     std::filesystem::permissions(filepath, permission);
6020ed80c8cSGeorge Liu     out << body;
6030ed80c8cSGeorge Liu 
6040ed80c8cSGeorge Liu     if (out.bad())
6050ed80c8cSGeorge Liu     {
6060ed80c8cSGeorge Liu         messages::internalError(res);
6070ed80c8cSGeorge Liu         cleanUp();
6080ed80c8cSGeorge Liu     }
6090ed80c8cSGeorge Liu }
6100ed80c8cSGeorge Liu 
6110ed80c8cSGeorge Liu inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
6120ed80c8cSGeorge Liu                          const std::string& applyTime)
6130ed80c8cSGeorge Liu {
6140ed80c8cSGeorge Liu     std::string applyTimeNewVal;
6150ed80c8cSGeorge Liu     if (applyTime == "Immediate")
6160ed80c8cSGeorge Liu     {
6170ed80c8cSGeorge Liu         applyTimeNewVal =
6180ed80c8cSGeorge Liu             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
6190ed80c8cSGeorge Liu     }
6200ed80c8cSGeorge Liu     else if (applyTime == "OnReset")
6210ed80c8cSGeorge Liu     {
6220ed80c8cSGeorge Liu         applyTimeNewVal =
6230ed80c8cSGeorge Liu             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
6240ed80c8cSGeorge Liu     }
6250ed80c8cSGeorge Liu     else
6260ed80c8cSGeorge Liu     {
62762598e31SEd Tanous         BMCWEB_LOG_INFO(
62862598e31SEd Tanous             "ApplyTime value is not in the list of acceptable values");
6290ed80c8cSGeorge Liu         messages::propertyValueNotInList(asyncResp->res, applyTime,
6300ed80c8cSGeorge Liu                                          "ApplyTime");
6310ed80c8cSGeorge Liu         return;
6320ed80c8cSGeorge Liu     }
6330ed80c8cSGeorge Liu 
634d02aad39SEd Tanous     setDbusProperty(asyncResp, "xyz.openbmc_project.Settings",
635d02aad39SEd Tanous                     sdbusplus::message::object_path(
636d02aad39SEd Tanous                         "/xyz/openbmc_project/software/apply_time"),
637d02aad39SEd Tanous                     "xyz.openbmc_project.Software.ApplyTime",
638d02aad39SEd Tanous                     "RequestedApplyTime", "ApplyTime", applyTimeNewVal);
6390ed80c8cSGeorge Liu }
6400ed80c8cSGeorge Liu 
6410ed80c8cSGeorge Liu inline void
6420ed80c8cSGeorge Liu     updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
6430ed80c8cSGeorge Liu                            const MultipartParser& parser)
6440ed80c8cSGeorge Liu {
6450ed80c8cSGeorge Liu     const std::string* uploadData = nullptr;
6460ed80c8cSGeorge Liu     std::optional<std::string> applyTime = "OnReset";
6470ed80c8cSGeorge Liu     bool targetFound = false;
6480ed80c8cSGeorge Liu     for (const FormPart& formpart : parser.mime_fields)
6490ed80c8cSGeorge Liu     {
6500ed80c8cSGeorge Liu         boost::beast::http::fields::const_iterator it =
6510ed80c8cSGeorge Liu             formpart.fields.find("Content-Disposition");
6520ed80c8cSGeorge Liu         if (it == formpart.fields.end())
6530ed80c8cSGeorge Liu         {
65462598e31SEd Tanous             BMCWEB_LOG_ERROR("Couldn't find Content-Disposition");
6550ed80c8cSGeorge Liu             return;
6560ed80c8cSGeorge Liu         }
65762598e31SEd Tanous         BMCWEB_LOG_INFO("Parsing value {}", it->value());
6580ed80c8cSGeorge Liu 
6590ed80c8cSGeorge Liu         // The construction parameters of param_list must start with `;`
6600ed80c8cSGeorge Liu         size_t index = it->value().find(';');
6610ed80c8cSGeorge Liu         if (index == std::string::npos)
6620ed80c8cSGeorge Liu         {
6630ed80c8cSGeorge Liu             continue;
6640ed80c8cSGeorge Liu         }
6650ed80c8cSGeorge Liu 
66689492a15SPatrick Williams         for (const auto& param :
6670ed80c8cSGeorge Liu              boost::beast::http::param_list{it->value().substr(index)})
6680ed80c8cSGeorge Liu         {
6690ed80c8cSGeorge Liu             if (param.first != "name" || param.second.empty())
6700ed80c8cSGeorge Liu             {
6710ed80c8cSGeorge Liu                 continue;
6720ed80c8cSGeorge Liu             }
6730ed80c8cSGeorge Liu 
6740ed80c8cSGeorge Liu             if (param.second == "UpdateParameters")
6750ed80c8cSGeorge Liu             {
6760ed80c8cSGeorge Liu                 std::vector<std::string> targets;
6770ed80c8cSGeorge Liu                 nlohmann::json content =
6780ed80c8cSGeorge Liu                     nlohmann::json::parse(formpart.content);
6797cb59f65SEd Tanous                 nlohmann::json::object_t* obj =
6807cb59f65SEd Tanous                     content.get_ptr<nlohmann::json::object_t*>();
6817cb59f65SEd Tanous                 if (obj == nullptr)
6827cb59f65SEd Tanous                 {
6837cb59f65SEd Tanous                     messages::propertyValueFormatError(asyncResp->res, targets,
6847cb59f65SEd Tanous                                                        "UpdateParameters");
6857cb59f65SEd Tanous                     return;
6867cb59f65SEd Tanous                 }
6877cb59f65SEd Tanous 
6887cb59f65SEd Tanous                 if (!json_util::readJsonObject(
6897cb59f65SEd Tanous                         *obj, asyncResp->res, "Targets", targets,
6907cb59f65SEd Tanous                         "@Redfish.OperationApplyTime", applyTime))
6910ed80c8cSGeorge Liu                 {
6920ed80c8cSGeorge Liu                     return;
6930ed80c8cSGeorge Liu                 }
6940ed80c8cSGeorge Liu                 if (targets.size() != 1)
6950ed80c8cSGeorge Liu                 {
6967cb59f65SEd Tanous                     messages::propertyValueFormatError(asyncResp->res, targets,
6977cb59f65SEd Tanous                                                        "Targets");
6980ed80c8cSGeorge Liu                     return;
6990ed80c8cSGeorge Liu                 }
7000ed80c8cSGeorge Liu                 if (targets[0] != "/redfish/v1/Managers/bmc")
7010ed80c8cSGeorge Liu                 {
7027cb59f65SEd Tanous                     messages::propertyValueNotInList(asyncResp->res, targets[0],
7037cb59f65SEd Tanous                                                      "Targets/0");
7040ed80c8cSGeorge Liu                     return;
7050ed80c8cSGeorge Liu                 }
7060ed80c8cSGeorge Liu                 targetFound = true;
7070ed80c8cSGeorge Liu             }
7080ed80c8cSGeorge Liu             else if (param.second == "UpdateFile")
7090ed80c8cSGeorge Liu             {
7100ed80c8cSGeorge Liu                 uploadData = &(formpart.content);
7110ed80c8cSGeorge Liu             }
7120ed80c8cSGeorge Liu         }
7130ed80c8cSGeorge Liu     }
7140ed80c8cSGeorge Liu 
7150ed80c8cSGeorge Liu     if (uploadData == nullptr)
7160ed80c8cSGeorge Liu     {
71762598e31SEd Tanous         BMCWEB_LOG_ERROR("Upload data is NULL");
7180ed80c8cSGeorge Liu         messages::propertyMissing(asyncResp->res, "UpdateFile");
7190ed80c8cSGeorge Liu         return;
7200ed80c8cSGeorge Liu     }
7210ed80c8cSGeorge Liu     if (!targetFound)
7220ed80c8cSGeorge Liu     {
7230ed80c8cSGeorge Liu         messages::propertyMissing(asyncResp->res, "targets");
7240ed80c8cSGeorge Liu         return;
7250ed80c8cSGeorge Liu     }
7260ed80c8cSGeorge Liu 
7270ed80c8cSGeorge Liu     setApplyTime(asyncResp, *applyTime);
7280ed80c8cSGeorge Liu 
7290ed80c8cSGeorge Liu     uploadImageFile(asyncResp->res, *uploadData);
7300ed80c8cSGeorge Liu }
7310ed80c8cSGeorge Liu 
732c2051d11SEd Tanous inline void
733c2051d11SEd Tanous     handleUpdateServicePost(App& app, const crow::Request& req,
734c2051d11SEd Tanous                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
735c2051d11SEd Tanous {
7363ba00073SCarson Labrado     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
737c2051d11SEd Tanous     {
738c2051d11SEd Tanous         return;
739c2051d11SEd Tanous     }
740b33a4327SNinad Palsule     std::string_view contentType = req.getHeaderValue("Content-Type");
741b33a4327SNinad Palsule 
74262598e31SEd Tanous     BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType);
743b33a4327SNinad Palsule 
744b33a4327SNinad Palsule     // Make sure that content type is application/octet-stream or
745b33a4327SNinad Palsule     // multipart/form-data
74618f8f608SEd Tanous     if (bmcweb::asciiIEquals(contentType, "application/octet-stream"))
747b33a4327SNinad Palsule     {
748b33a4327SNinad Palsule         // Setup callback for when new software detected
749b33a4327SNinad Palsule         monitorForSoftwareAvailable(asyncResp, req,
750b33a4327SNinad Palsule                                     "/redfish/v1/UpdateService");
751b33a4327SNinad Palsule 
752b33a4327SNinad Palsule         uploadImageFile(asyncResp->res, req.body());
753b33a4327SNinad Palsule     }
754b33a4327SNinad Palsule     else if (contentType.starts_with("multipart/form-data"))
755b33a4327SNinad Palsule     {
756b33a4327SNinad Palsule         MultipartParser parser;
757c2051d11SEd Tanous 
758c2051d11SEd Tanous         // Setup callback for when new software detected
759b33a4327SNinad Palsule         monitorForSoftwareAvailable(asyncResp, req,
760b33a4327SNinad Palsule                                     "/redfish/v1/UpdateService");
761c2051d11SEd Tanous 
7620ed80c8cSGeorge Liu         ParserError ec = parser.parse(req);
7630ed80c8cSGeorge Liu         if (ec != ParserError::PARSER_SUCCESS)
7640ed80c8cSGeorge Liu         {
7650ed80c8cSGeorge Liu             // handle error
76662598e31SEd Tanous             BMCWEB_LOG_ERROR("MIME parse failed, ec : {}",
76762598e31SEd Tanous                              static_cast<int>(ec));
7680ed80c8cSGeorge Liu             messages::internalError(asyncResp->res);
7690ed80c8cSGeorge Liu             return;
7700ed80c8cSGeorge Liu         }
7710ed80c8cSGeorge Liu         updateMultipartContext(asyncResp, parser);
772c2051d11SEd Tanous     }
773b33a4327SNinad Palsule     else
774b33a4327SNinad Palsule     {
77562598e31SEd Tanous         BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType);
776b33a4327SNinad Palsule         asyncResp->res.result(boost::beast::http::status::bad_request);
777b33a4327SNinad Palsule     }
778b33a4327SNinad Palsule }
779c2051d11SEd Tanous 
780f5139334SEd Tanous inline void
781f5139334SEd Tanous     handleUpdateServiceGet(App& app, const crow::Request& req,
782f5139334SEd Tanous                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
7831abe55efSEd Tanous {
7843ba00073SCarson Labrado     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
78545ca1b86SEd Tanous     {
78645ca1b86SEd Tanous         return;
78745ca1b86SEd Tanous     }
7888d1b46d7Szhanghch05     asyncResp->res.jsonValue["@odata.type"] =
7890ed80c8cSGeorge Liu         "#UpdateService.v1_11_1.UpdateService";
7908d1b46d7Szhanghch05     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
7918d1b46d7Szhanghch05     asyncResp->res.jsonValue["Id"] = "UpdateService";
792002d39b4SEd Tanous     asyncResp->res.jsonValue["Description"] = "Service for Software Update";
7938d1b46d7Szhanghch05     asyncResp->res.jsonValue["Name"] = "Update Service";
7944dc23f3fSEd Tanous 
7957e860f15SJohn Edward Broadbent     asyncResp->res.jsonValue["HttpPushUri"] =
7964dc23f3fSEd Tanous         "/redfish/v1/UpdateService/update";
7970ed80c8cSGeorge Liu     asyncResp->res.jsonValue["MultipartHttpPushUri"] =
7980ed80c8cSGeorge Liu         "/redfish/v1/UpdateService/update";
7994dc23f3fSEd Tanous 
8000f74e643SEd Tanous     // UpdateService cannot be disabled
8018d1b46d7Szhanghch05     asyncResp->res.jsonValue["ServiceEnabled"] = true;
8021476687dSEd Tanous     asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] =
8031476687dSEd Tanous         "/redfish/v1/UpdateService/FirmwareInventory";
804d61e5194STejas Patil     // Get the MaxImageSizeBytes
805f5139334SEd Tanous     asyncResp->res.jsonValue["MaxImageSizeBytes"] = bmcwebHttpReqBodyLimitMb *
806f5139334SEd Tanous                                                     1024 * 1024;
807d61e5194STejas Patil 
8080554c984SAndrew Geissler #ifdef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE
8090554c984SAndrew Geissler     // Update Actions object.
8100554c984SAndrew Geissler     nlohmann::json& updateSvcSimpleUpdate =
811002d39b4SEd Tanous         asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
8120554c984SAndrew Geissler     updateSvcSimpleUpdate["target"] =
8130554c984SAndrew Geissler         "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
814002d39b4SEd Tanous     updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] = {
815002d39b4SEd Tanous         "TFTP"};
8160554c984SAndrew Geissler #endif
817274dfe62SJayashankar Padath     // Get the current ApplyTime value
8181e1e598dSJonathan Doman     sdbusplus::asio::getProperty<std::string>(
8191e1e598dSJonathan Doman         *crow::connections::systemBus, "xyz.openbmc_project.Settings",
8201e1e598dSJonathan Doman         "/xyz/openbmc_project/software/apply_time",
8211e1e598dSJonathan Doman         "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime",
8225e7e2dc5SEd Tanous         [asyncResp](const boost::system::error_code& ec,
8231e1e598dSJonathan Doman                     const std::string& applyTime) {
824274dfe62SJayashankar Padath         if (ec)
825274dfe62SJayashankar Padath         {
82662598e31SEd Tanous             BMCWEB_LOG_DEBUG("DBUS response error {}", ec);
8278d1b46d7Szhanghch05             messages::internalError(asyncResp->res);
828274dfe62SJayashankar Padath             return;
829274dfe62SJayashankar Padath         }
830274dfe62SJayashankar Padath 
831274dfe62SJayashankar Padath         // Store the ApplyTime Value
8321e1e598dSJonathan Doman         if (applyTime == "xyz.openbmc_project.Software.ApplyTime."
8331e1e598dSJonathan Doman                          "RequestedApplyTimes.Immediate")
834274dfe62SJayashankar Padath         {
835002d39b4SEd Tanous             asyncResp->res.jsonValue["HttpPushUriOptions"]
8367e860f15SJohn Edward Broadbent                                     ["HttpPushUriApplyTime"]["ApplyTime"] =
8377e860f15SJohn Edward Broadbent                 "Immediate";
838274dfe62SJayashankar Padath         }
839002d39b4SEd Tanous         else if (applyTime == "xyz.openbmc_project.Software.ApplyTime."
8401e1e598dSJonathan Doman                               "RequestedApplyTimes.OnReset")
841274dfe62SJayashankar Padath         {
842002d39b4SEd Tanous             asyncResp->res.jsonValue["HttpPushUriOptions"]
8437e860f15SJohn Edward Broadbent                                     ["HttpPushUriApplyTime"]["ApplyTime"] =
8447e860f15SJohn Edward Broadbent                 "OnReset";
845274dfe62SJayashankar Padath         }
8461e1e598dSJonathan Doman     });
847f5139334SEd Tanous }
848f5139334SEd Tanous 
849f5139334SEd Tanous inline void handleUpdateServicePatch(
850f5139334SEd Tanous     App& app, const crow::Request& req,
851f5139334SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
852f5139334SEd Tanous {
8533ba00073SCarson Labrado     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
85445ca1b86SEd Tanous     {
85545ca1b86SEd Tanous         return;
85645ca1b86SEd Tanous     }
85762598e31SEd Tanous     BMCWEB_LOG_DEBUG("doPatch...");
858fa1a5a38SJayashankar Padath 
859274dfe62SJayashankar Padath     std::optional<std::string> applyTime;
8607cb59f65SEd Tanous     if (!json_util::readJsonPatch(
8617cb59f65SEd Tanous             req, asyncResp->res,
8627cb59f65SEd Tanous             "HttpPushUriOptions/HttpPushUriApplyTime/ApplyTime", applyTime))
863274dfe62SJayashankar Padath     {
864274dfe62SJayashankar Padath         return;
865274dfe62SJayashankar Padath     }
866274dfe62SJayashankar Padath 
867274dfe62SJayashankar Padath     if (applyTime)
868fa1a5a38SJayashankar Padath     {
8690ed80c8cSGeorge Liu         setApplyTime(asyncResp, *applyTime);
870fa1a5a38SJayashankar Padath     }
871729dae72SJennifer Lee }
872729dae72SJennifer Lee 
873f5139334SEd Tanous inline void handleUpdateServiceFirmwareInventoryCollectionGet(
874f5139334SEd Tanous     App& app, const crow::Request& req,
875f5139334SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
8761abe55efSEd Tanous {
8773ba00073SCarson Labrado     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
87845ca1b86SEd Tanous     {
87945ca1b86SEd Tanous         return;
88045ca1b86SEd Tanous     }
8818d1b46d7Szhanghch05     asyncResp->res.jsonValue["@odata.type"] =
8820f74e643SEd Tanous         "#SoftwareInventoryCollection.SoftwareInventoryCollection";
8838d1b46d7Szhanghch05     asyncResp->res.jsonValue["@odata.id"] =
8840f74e643SEd Tanous         "/redfish/v1/UpdateService/FirmwareInventory";
885002d39b4SEd Tanous     asyncResp->res.jsonValue["Name"] = "Software Inventory Collection";
88608d81adaSJohn Edward Broadbent     const std::array<const std::string_view, 1> iface = {
887e99073f5SGeorge Liu         "xyz.openbmc_project.Software.Version"};
8886c4eb9deSJennifer Lee 
88908d81adaSJohn Edward Broadbent     redfish::collection_util::getCollectionMembers(
89008d81adaSJohn Edward Broadbent         asyncResp,
891f5139334SEd Tanous         boost::urls::url("/redfish/v1/UpdateService/FirmwareInventory"), iface,
892f5139334SEd Tanous         "/xyz/openbmc_project/software");
893729dae72SJennifer Lee }
894f5139334SEd Tanous 
89587d84729SAndrew Geissler /* Fill related item links (i.e. bmc, bios) in for inventory */
896f5139334SEd Tanous inline void getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
89787d84729SAndrew Geissler                             const std::string& purpose)
89887d84729SAndrew Geissler {
899eee0013eSWilly Tu     if (purpose == sw_util::bmcPurpose)
90087d84729SAndrew Geissler     {
901ac106bf6SEd Tanous         nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
9021476687dSEd Tanous         nlohmann::json::object_t item;
9031476687dSEd Tanous         item["@odata.id"] = "/redfish/v1/Managers/bmc";
904b2ba3072SPatrick Williams         relatedItem.emplace_back(std::move(item));
905ac106bf6SEd Tanous         asyncResp->res.jsonValue["RelatedItem@odata.count"] =
906ac106bf6SEd Tanous             relatedItem.size();
90787d84729SAndrew Geissler     }
908eee0013eSWilly Tu     else if (purpose == sw_util::biosPurpose)
90987d84729SAndrew Geissler     {
910ac106bf6SEd Tanous         nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
9111476687dSEd Tanous         nlohmann::json::object_t item;
9121476687dSEd Tanous         item["@odata.id"] = "/redfish/v1/Systems/system/Bios";
913b2ba3072SPatrick Williams         relatedItem.emplace_back(std::move(item));
914ac106bf6SEd Tanous         asyncResp->res.jsonValue["RelatedItem@odata.count"] =
915ac106bf6SEd Tanous             relatedItem.size();
91687d84729SAndrew Geissler     }
91787d84729SAndrew Geissler     else
91887d84729SAndrew Geissler     {
919bf2ddedeSCarson Labrado         BMCWEB_LOG_DEBUG("Unknown software purpose {}", purpose);
92087d84729SAndrew Geissler     }
92187d84729SAndrew Geissler }
92287d84729SAndrew Geissler 
923af24660dSWilly Tu inline void
924af24660dSWilly Tu     getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
925af24660dSWilly Tu                        const std::string& service, const std::string& path,
926af24660dSWilly Tu                        const std::string& swId)
927af24660dSWilly Tu {
928d1bde9e5SKrzysztof Grobelny     sdbusplus::asio::getAllProperties(
929d1bde9e5SKrzysztof Grobelny         *crow::connections::systemBus, service, path,
930d1bde9e5SKrzysztof Grobelny         "xyz.openbmc_project.Software.Version",
931af24660dSWilly Tu         [asyncResp,
9328b24275dSEd Tanous          swId](const boost::system::error_code& ec,
933af24660dSWilly Tu                const dbus::utility::DBusPropertiesMap& propertiesList) {
9348b24275dSEd Tanous         if (ec)
935af24660dSWilly Tu         {
936af24660dSWilly Tu             messages::internalError(asyncResp->res);
937af24660dSWilly Tu             return;
938af24660dSWilly Tu         }
939d1bde9e5SKrzysztof Grobelny 
940af24660dSWilly Tu         const std::string* swInvPurpose = nullptr;
941af24660dSWilly Tu         const std::string* version = nullptr;
942d1bde9e5SKrzysztof Grobelny 
943d1bde9e5SKrzysztof Grobelny         const bool success = sdbusplus::unpackPropertiesNoThrow(
944d1bde9e5SKrzysztof Grobelny             dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose",
945d1bde9e5SKrzysztof Grobelny             swInvPurpose, "Version", version);
946d1bde9e5SKrzysztof Grobelny 
947d1bde9e5SKrzysztof Grobelny         if (!success)
948af24660dSWilly Tu         {
949d1bde9e5SKrzysztof Grobelny             messages::internalError(asyncResp->res);
950d1bde9e5SKrzysztof Grobelny             return;
951af24660dSWilly Tu         }
952af24660dSWilly Tu 
953af24660dSWilly Tu         if (swInvPurpose == nullptr)
954af24660dSWilly Tu         {
95562598e31SEd Tanous             BMCWEB_LOG_DEBUG("Can't find property \"Purpose\"!");
956af24660dSWilly Tu             messages::internalError(asyncResp->res);
957af24660dSWilly Tu             return;
958af24660dSWilly Tu         }
959af24660dSWilly Tu 
96062598e31SEd Tanous         BMCWEB_LOG_DEBUG("swInvPurpose = {}", *swInvPurpose);
961af24660dSWilly Tu 
962af24660dSWilly Tu         if (version == nullptr)
963af24660dSWilly Tu         {
96462598e31SEd Tanous             BMCWEB_LOG_DEBUG("Can't find property \"Version\"!");
965af24660dSWilly Tu 
966af24660dSWilly Tu             messages::internalError(asyncResp->res);
967af24660dSWilly Tu 
968af24660dSWilly Tu             return;
969af24660dSWilly Tu         }
970af24660dSWilly Tu         asyncResp->res.jsonValue["Version"] = *version;
971af24660dSWilly Tu         asyncResp->res.jsonValue["Id"] = swId;
972af24660dSWilly Tu 
973af24660dSWilly Tu         // swInvPurpose is of format:
974af24660dSWilly Tu         // xyz.openbmc_project.Software.Version.VersionPurpose.ABC
975af24660dSWilly Tu         // Translate this to "ABC image"
976af24660dSWilly Tu         size_t endDesc = swInvPurpose->rfind('.');
977af24660dSWilly Tu         if (endDesc == std::string::npos)
978af24660dSWilly Tu         {
979af24660dSWilly Tu             messages::internalError(asyncResp->res);
980af24660dSWilly Tu             return;
981af24660dSWilly Tu         }
982af24660dSWilly Tu         endDesc++;
983af24660dSWilly Tu         if (endDesc >= swInvPurpose->size())
984af24660dSWilly Tu         {
985af24660dSWilly Tu             messages::internalError(asyncResp->res);
986af24660dSWilly Tu             return;
987af24660dSWilly Tu         }
988af24660dSWilly Tu 
989af24660dSWilly Tu         std::string formatDesc = swInvPurpose->substr(endDesc);
990af24660dSWilly Tu         asyncResp->res.jsonValue["Description"] = formatDesc + " image";
991af24660dSWilly Tu         getRelatedItems(asyncResp, *swInvPurpose);
992d1bde9e5SKrzysztof Grobelny     });
993af24660dSWilly Tu }
994af24660dSWilly Tu 
995f5139334SEd Tanous inline void handleUpdateServiceFirmwareInventoryGet(
996f5139334SEd Tanous     App& app, const crow::Request& req,
99745ca1b86SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
998f5139334SEd Tanous     const std::string& param)
999f5139334SEd Tanous {
10003ba00073SCarson Labrado     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
100145ca1b86SEd Tanous     {
100245ca1b86SEd Tanous         return;
100345ca1b86SEd Tanous     }
1004f5139334SEd Tanous     std::shared_ptr<std::string> swId = std::make_shared<std::string>(param);
1005c711bf86SEd Tanous 
1006ef4c65b7SEd Tanous     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
1007ef4c65b7SEd Tanous         "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId);
1008c711bf86SEd Tanous 
1009e99073f5SGeorge Liu     constexpr std::array<std::string_view, 1> interfaces = {
1010e99073f5SGeorge Liu         "xyz.openbmc_project.Software.Version"};
1011e99073f5SGeorge Liu     dbus::utility::getSubTree(
1012e99073f5SGeorge Liu         "/", 0, interfaces,
1013b9d36b47SEd Tanous         [asyncResp,
1014e99073f5SGeorge Liu          swId](const boost::system::error_code& ec,
1015b9d36b47SEd Tanous                const dbus::utility::MapperGetSubTreeResponse& subtree) {
101662598e31SEd Tanous         BMCWEB_LOG_DEBUG("doGet callback...");
10171abe55efSEd Tanous         if (ec)
10181abe55efSEd Tanous         {
1019f12894f8SJason M. Bills             messages::internalError(asyncResp->res);
10206c4eb9deSJennifer Lee             return;
10216c4eb9deSJennifer Lee         }
10226c4eb9deSJennifer Lee 
10236913228dSAndrew Geissler         // Ensure we find our input swId, otherwise return an error
10246913228dSAndrew Geissler         bool found = false;
1025f5139334SEd Tanous         for (const std::pair<
1026f5139334SEd Tanous                  std::string,
1027f5139334SEd Tanous                  std::vector<std::pair<std::string, std::vector<std::string>>>>&
1028002d39b4SEd Tanous                  obj : subtree)
10291abe55efSEd Tanous         {
103011ba3979SEd Tanous             if (!obj.first.ends_with(*swId))
10311abe55efSEd Tanous             {
1032acb7cfb4SJennifer Lee                 continue;
1033acb7cfb4SJennifer Lee             }
1034acb7cfb4SJennifer Lee 
103526f6976fSEd Tanous             if (obj.second.empty())
10361abe55efSEd Tanous             {
1037acb7cfb4SJennifer Lee                 continue;
1038acb7cfb4SJennifer Lee             }
10396c4eb9deSJennifer Lee 
10406913228dSAndrew Geissler             found = true;
1041eee0013eSWilly Tu             sw_util::getSwStatus(asyncResp, swId, obj.second[0].first);
1042af24660dSWilly Tu             getSoftwareVersion(asyncResp, obj.second[0].first, obj.first,
1043af24660dSWilly Tu                                *swId);
10446c4eb9deSJennifer Lee         }
10456913228dSAndrew Geissler         if (!found)
10466913228dSAndrew Geissler         {
104762598e31SEd Tanous             BMCWEB_LOG_WARNING("Input swID {} not found!", *swId);
10486913228dSAndrew Geissler             messages::resourceMissingAtURI(
1049ef4c65b7SEd Tanous                 asyncResp->res,
1050ef4c65b7SEd Tanous                 boost::urls::format(
1051f5139334SEd Tanous                     "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId));
10526913228dSAndrew Geissler             return;
10536913228dSAndrew Geissler         }
10544e68c45bSAyushi Smriti         asyncResp->res.jsonValue["@odata.type"] =
10554e68c45bSAyushi Smriti             "#SoftwareInventory.v1_1_0.SoftwareInventory";
10564e68c45bSAyushi Smriti         asyncResp->res.jsonValue["Name"] = "Software Inventory";
10574e68c45bSAyushi Smriti         asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
10583f8a743aSAppaRao Puli 
10593f8a743aSAppaRao Puli         asyncResp->res.jsonValue["Updateable"] = false;
1060eee0013eSWilly Tu         sw_util::getSwUpdatableStatus(asyncResp, swId);
1061e99073f5SGeorge Liu     });
1062f5139334SEd Tanous }
1063f5139334SEd Tanous 
1064f5139334SEd Tanous inline void requestRoutesUpdateService(App& app)
1065f5139334SEd Tanous {
1066f5139334SEd Tanous     BMCWEB_ROUTE(
1067f5139334SEd Tanous         app, "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/")
1068f5139334SEd Tanous         .privileges(redfish::privileges::postUpdateService)
1069f5139334SEd Tanous         .methods(boost::beast::http::verb::post)(std::bind_front(
1070f5139334SEd Tanous             handleUpdateServiceSimpleUpdateAction, std::ref(app)));
1071f5139334SEd Tanous 
1072f5139334SEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/")
1073f5139334SEd Tanous         .privileges(redfish::privileges::getSoftwareInventory)
1074f5139334SEd Tanous         .methods(boost::beast::http::verb::get)(std::bind_front(
1075f5139334SEd Tanous             handleUpdateServiceFirmwareInventoryGet, std::ref(app)));
1076f5139334SEd Tanous 
1077f5139334SEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
1078f5139334SEd Tanous         .privileges(redfish::privileges::getUpdateService)
1079f5139334SEd Tanous         .methods(boost::beast::http::verb::get)(
1080f5139334SEd Tanous             std::bind_front(handleUpdateServiceGet, std::ref(app)));
1081f5139334SEd Tanous 
1082f5139334SEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
1083f5139334SEd Tanous         .privileges(redfish::privileges::patchUpdateService)
1084f5139334SEd Tanous         .methods(boost::beast::http::verb::patch)(
1085f5139334SEd Tanous             std::bind_front(handleUpdateServicePatch, std::ref(app)));
1086f5139334SEd Tanous 
1087f5139334SEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/")
1088f5139334SEd Tanous         .privileges(redfish::privileges::postUpdateService)
1089f5139334SEd Tanous         .methods(boost::beast::http::verb::post)(
1090f5139334SEd Tanous             std::bind_front(handleUpdateServicePost, std::ref(app)));
1091f5139334SEd Tanous 
1092f5139334SEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/")
1093f5139334SEd Tanous         .privileges(redfish::privileges::getSoftwareInventoryCollection)
1094f5139334SEd Tanous         .methods(boost::beast::http::verb::get)(std::bind_front(
1095f5139334SEd Tanous             handleUpdateServiceFirmwareInventoryCollectionGet, std::ref(app)));
10966c4eb9deSJennifer Lee }
1097729dae72SJennifer Lee 
1098729dae72SJennifer Lee } // namespace redfish
1099