simpleupdate: Basic support of SimpleUpdate

This is TFTP only since that is all the OpenBMC back end currently
supports

Design Doc Ref:
https://gerrit.openbmc-project.xyz/c/openbmc/docs/+/20700

Tested:
1) Verified BMC image update via new SimpleUpdate interface worked
curl -k -H "X-Auth-Token: $TOKEN" -d '{"ImageURI":"XXX.XX.X.X/obmc-phosphor-image-witherspoon.ubi.mtd.tar","TransferProtocol":"TFTP"}' -X POST https://${BMC_IP}/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate
{
  "@Message.ExtendedInfo": [
    {
      "@odata.type": "/redfish/v1/$metadata#Message.v1_0_0.Message",
      "Message": "Successfully Completed Request",
      "MessageArgs": [],
      "MessageId": "Base.1.4.0.Success",
      "Resolution": "None",
      "Severity": "OK"
    }
  ]
}

2) Verified encoding the protocol in ImageURI worked
curl -k -H "X-Auth-Token: $TOKEN" -X POST https://${BMC_IP}/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate -d '{"ImageURI":"tftp://${TFTP_IP}/obmc-phosphor-image-witherspoon.ubi.mtd.tar"}'
{
  "@Message.ExtendedInfo": [
    {
      "@odata.type": "/redfish/v1/$metadata#Message.v1_0_0.Message",
      "Message": "Successfully Completed Request",
      "MessageArgs": [],
      "MessageId": "Base.1.4.0.Success",
      "Resolution": "None",
      "Severity": "OK"
    }
  ]
}
Jun 12 20:52:20 witherspoon bmcweb[2470]: (2019-06-12 20:52:20) [DEBUG   ] Enter UpdateService.SimpleUpdate doPost
Jun 12 20:52:20 witherspoon bmcweb[2470]: (2019-06-12 20:52:20) [DEBUG   ] Encoded transfer protocol tftp
Jun 12 20:52:20 witherspoon bmcweb[2470]: (2019-06-12 20:52:20) [DEBUG   ] Adjusted imageUri ${BMC_IP}/obmc-phosphor-image-witherspoon.ubi.mtd.tar
Jun 12 20:52:20 witherspoon bmcweb[2470]: (2019-06-12 20:52:20) [DEBUG   ] Server: ${TFTP_IP} File: obmc-phosphor-image-witherspoon.ubi.mtd.tar
Jun 12 20:52:20 witherspoon bmcweb[2470]: (2019-06-12 20:52:20) [DEBUG   ] Exit UpdateService.SimpleUpdate doPost

3) Verified if no transfer protocol, error returned
curl -k -H "X-Auth-Token: $TOKEN" -X POST https://${BMC_IP}/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate -d '{"ImageURI":”${TFTP_IP}/obmc-phosphor-image-witherspoon.ubi.mtd.tar"}'
{
  "error": {
    "@Message.ExtendedInfo": [
      {
        "@odata.type": "/redfish/v1/$metadata#Message.v1_0_0.Message",
        "Message": "The value ${TFTP_IP}/obmc-phosphor-image-witherspoon.ubi.mtd.tar for the parameter ImageURI in the action UpdateService.SimpleUpdate is of a different type than the parameter can accept.",
        "MessageArgs": [
          “${TFTP_IP}/obmc-phosphor-image-witherspoon.ubi.mtd.tar",
          "ImageURI",
          "UpdateService.SimpleUpdate"
        ],
        "MessageId": "Base.1.4.0.ActionParameterValueTypeError",
        "Resolution": "Correct the value for the parameter in the request body and resubmit the request if the operation failed.",
        "Severity": "Warning"
      }
    ],
    "code": "Base.1.4.0.ActionParameterValueTypeError",
    "message": "The value ${TFTP_IP}/obmc-phosphor-image-witherspoon.ubi.mtd.tar for the parameter ImageURI in the action UpdateService.SimpleUpdate is of a different type than the parameter can accept."
  }
}

4) Verified no new error in Redfish Validator

5) Verified error return when parameter missing:
curl -k -H "X-Auth-Token: $TOKEN" -d '{"TransferProtocol":"TFTP"}' -X POST https://${BMC_IP}/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate
{
  "ImageURI@Message.ExtendedInfo": [
    {
      "@odata.type": "/redfish/v1/$metadata#Message.v1_0_0.Message",
      "Message": "The property ImageURI is a required property and must be included in the request.",
      "MessageArgs": [
        "ImageURI"
      ],
      "MessageId": "Base.1.4.0.PropertyMissing",
      "Resolution": "Ensure that the property is in the request body and has a valid value and resubmit the request if the operation failed.",
      "Severity": "Warning"
    }
  ]
}

6) Verified that by default, the new Action is not available

Change-Id: I67ea91e181380e6da7ff63a37f02408a318602b7
Signed-off-by: Andrew Geissler <geissonator@yahoo.com>
diff --git a/redfish-core/lib/update_service.hpp b/redfish-core/lib/update_service.hpp
index 06c1965..7e08ec7 100644
--- a/redfish-core/lib/update_service.hpp
+++ b/redfish-core/lib/update_service.hpp
@@ -54,6 +54,9 @@
             "xyz.openbmc_project.Software.Activation.RequestedActivations."
             "Active"));
 }
+
+// Note that asyncResp can be either a valid pointer or nullptr. If nullptr
+// then no asyncResp updates will occur
 static void softwareInterfaceAdded(std::shared_ptr<AsyncResp> asyncResp,
                                    sdbusplus::message::message &m)
 {
@@ -87,7 +90,10 @@
                         BMCWEB_LOG_DEBUG << "error_code = " << error_code;
                         BMCWEB_LOG_DEBUG << "error msg = "
                                          << error_code.message();
-                        messages::internalError(asyncResp->res);
+                        if (asyncResp)
+                        {
+                            messages::internalError(asyncResp->res);
+                        }
                         cleanUp();
                         return;
                     }
@@ -96,7 +102,10 @@
                     {
                         BMCWEB_LOG_ERROR << "Invalid Object Size "
                                          << objInfo.size();
-                        messages::internalError(asyncResp->res);
+                        if (asyncResp)
+                        {
+                            messages::internalError(asyncResp->res);
+                        }
                         cleanUp();
                         return;
                     }
@@ -106,7 +115,10 @@
                     fwAvailableTimer = nullptr;
 
                     activateImage(objPath.str, objInfo[0].first);
-                    redfish::messages::success(asyncResp->res);
+                    if (asyncResp)
+                    {
+                        redfish::messages::success(asyncResp->res);
+                    }
                     fwUpdateInProgress = false;
                 },
                 "xyz.openbmc_project.ObjectMapper",
@@ -118,21 +130,28 @@
     }
 }
 
+// Note that asyncResp can be either a valid pointer or nullptr. If nullptr
+// then no asyncResp updates will occur
 static void monitorForSoftwareAvailable(std::shared_ptr<AsyncResp> asyncResp,
-                                        const crow::Request &req)
+                                        const crow::Request &req,
+                                        int timeoutTimeSeconds = 5)
 {
     // Only allow one FW update at a time
     if (fwUpdateInProgress != false)
     {
-        asyncResp->res.addHeader("Retry-After", "30");
-        messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
+        if (asyncResp)
+        {
+            asyncResp->res.addHeader("Retry-After", "30");
+            messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
+        }
         return;
     }
 
-    fwAvailableTimer = std::make_unique<boost::asio::deadline_timer>(
-        *req.ioService, boost::posix_time::seconds(5));
+    fwAvailableTimer =
+        std::make_unique<boost::asio::deadline_timer>(*req.ioService);
 
-    fwAvailableTimer->expires_from_now(boost::posix_time::seconds(5));
+    fwAvailableTimer->expires_from_now(
+        boost::posix_time::seconds(timeoutTimeSeconds));
 
     fwAvailableTimer->async_wait(
         [asyncResp](const boost::system::error_code &ec) {
@@ -151,8 +170,10 @@
                 BMCWEB_LOG_ERROR << "Async_wait failed" << ec;
                 return;
             }
-
-            redfish::messages::internalError(asyncResp->res);
+            if (asyncResp)
+            {
+                redfish::messages::internalError(asyncResp->res);
+            }
         });
 
     auto callback = [asyncResp](sdbusplus::message::message &m) {
@@ -169,6 +190,137 @@
         callback);
 }
 
+/**
+ * UpdateServiceActionsSimpleUpdate class supports handle POST method for
+ * SimpleUpdate action.
+ */
+class UpdateServiceActionsSimpleUpdate : public Node
+{
+  public:
+    UpdateServiceActionsSimpleUpdate(CrowApp &app) :
+        Node(app,
+             "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/")
+    {
+        entityPrivileges = {
+            {boost::beast::http::verb::get, {{"Login"}}},
+            {boost::beast::http::verb::head, {{"Login"}}},
+            {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
+            {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
+            {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
+    }
+
+  private:
+    void doPost(crow::Response &res, const crow::Request &req,
+                const std::vector<std::string> &params) override
+    {
+        std::optional<std::string> transferProtocol;
+        std::string imageURI;
+        std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
+
+        BMCWEB_LOG_DEBUG << "Enter UpdateService.SimpleUpdate doPost";
+
+        // User can pass in both TransferProtocol and ImageURI parameters or
+        // they can pass in just the ImageURI with the transfer protocl embedded
+        // within it.
+        // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin
+        // 2) ImageURI:tftp://1.1.1.1/myfile.bin
+
+        if (!json_util::readJson(req, asyncResp->res, "TransferProtocol",
+                                 transferProtocol, "ImageURI", imageURI))
+        {
+            BMCWEB_LOG_DEBUG
+                << "Missing TransferProtocol or ImageURI parameter";
+            return;
+        }
+        if (!transferProtocol)
+        {
+            // Must be option 2
+            // Verify ImageURI has transfer protocol in it
+            size_t separator = imageURI.find(":");
+            if ((separator == std::string::npos) ||
+                ((separator + 1) > imageURI.size()))
+            {
+                messages::actionParameterValueTypeError(
+                    asyncResp->res, imageURI, "ImageURI",
+                    "UpdateService.SimpleUpdate");
+                BMCWEB_LOG_ERROR << "ImageURI missing transfer protocol: "
+                                 << imageURI;
+                return;
+            }
+            transferProtocol = imageURI.substr(0, separator);
+            // Ensure protocol is upper case for a common comparison path below
+            boost::to_upper(*transferProtocol);
+            BMCWEB_LOG_DEBUG << "Encoded transfer protocol "
+                             << *transferProtocol;
+
+            // Adjust imageURI to not have the protocol on it for parsing
+            // below
+            // ex. tftp://1.1.1.1/myfile.bin -> 1.1.1.1/myfile.bin
+            imageURI = imageURI.substr(separator + 3);
+            BMCWEB_LOG_DEBUG << "Adjusted imageUri " << imageURI;
+        }
+
+        // OpenBMC currently only supports TFTP
+        if (*transferProtocol != "TFTP")
+        {
+            messages::actionParameterNotSupported(asyncResp->res,
+                                                  "TransferProtocol",
+                                                  "UpdateService.SimpleUpdate");
+            BMCWEB_LOG_ERROR << "Request incorrect protocol parameter: "
+                             << *transferProtocol;
+            return;
+        }
+
+        // Format should be <IP or Hostname>/<file> for imageURI
+        size_t separator = imageURI.find("/");
+        if ((separator == std::string::npos) ||
+            ((separator + 1) > imageURI.size()))
+        {
+            messages::actionParameterValueTypeError(
+                asyncResp->res, imageURI, "ImageURI",
+                "UpdateService.SimpleUpdate");
+            BMCWEB_LOG_ERROR << "Invalid ImageURI: " << imageURI;
+            return;
+        }
+
+        std::string tftpServer = imageURI.substr(0, separator);
+        std::string fwFile = imageURI.substr(separator + 1);
+        BMCWEB_LOG_DEBUG << "Server: " << tftpServer + " File: " << fwFile;
+
+        // Setup callback for when new software detected
+        // Give TFTP 2 minutes to complete
+        monitorForSoftwareAvailable(nullptr, req, 120);
+
+        // TFTP can take up to 2 minutes depending on image size and
+        // connection speed. Return to caller as soon as the TFTP operation
+        // has been started. The callback above will ensure the activate
+        // is started once the download has completed
+        redfish::messages::success(asyncResp->res);
+
+        // Call TFTP service
+        crow::connections::systemBus->async_method_call(
+            [](const boost::system::error_code ec) {
+                if (ec)
+                {
+                    // messages::internalError(asyncResp->res);
+                    cleanUp();
+                    BMCWEB_LOG_DEBUG << "error_code = " << ec;
+                    BMCWEB_LOG_DEBUG << "error msg = " << ec.message();
+                }
+                else
+                {
+                    BMCWEB_LOG_DEBUG << "Call to DownloaViaTFTP Success";
+                }
+            },
+            "xyz.openbmc_project.Software.Download",
+            "/xyz/openbmc_project/software", "xyz.openbmc_project.Common.TFTP",
+            "DownloadViaTFTP", fwFile, tftpServer);
+
+        BMCWEB_LOG_DEBUG << "Exit UpdateService.SimpleUpdate doPost";
+    }
+};
+
 class UpdateService : public Node
 {
   public:
@@ -199,6 +351,15 @@
         res.jsonValue["ServiceEnabled"] = true;
         res.jsonValue["FirmwareInventory"] = {
             {"@odata.id", "/redfish/v1/UpdateService/FirmwareInventory"}};
+#ifdef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE
+        // Update Actions object.
+        nlohmann::json &updateSvcSimpleUpdate =
+            res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
+        updateSvcSimpleUpdate["target"] =
+            "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
+        updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] = {
+            "TFTP"};
+#endif
         res.end();
     }