tools: add update handler

Add an UpdateHandler to handle each step of the update process.

This code was already in place but is now handled via an UpdateHandler
object.

Tested: Not yet tested.
Signed-off-by: Patrick Venture <venture@google.com>
Change-Id: I36cd1b94d8e2c0788d09805d935738d86e6e33de
diff --git a/tools/main.cpp b/tools/main.cpp
index 5868d11..536cde1 100644
--- a/tools/main.cpp
+++ b/tools/main.cpp
@@ -175,8 +175,8 @@
         /* The parameters are all filled out. */
         try
         {
-            host_tool::updaterMain(&blob, handler.get(), imagePath,
-                                   signaturePath);
+            host_tool::UpdateHandler updater(&blob, handler.get());
+            host_tool::updaterMain(&updater, imagePath, signaturePath);
         }
         catch (const host_tool::ToolException& e)
         {
diff --git a/tools/updater.cpp b/tools/updater.cpp
index 975ee0f..92afe5d 100644
--- a/tools/updater.cpp
+++ b/tools/updater.cpp
@@ -26,10 +26,84 @@
 #include <memory>
 #include <string>
 #include <thread>
+#include <vector>
 
 namespace host_tool
 {
 
+bool UpdateHandler::checkAvailable(const std::string& goalFirmware)
+{
+    std::vector<std::string> blobs = blob->getBlobList();
+
+    auto blobInst = std::find_if(
+        blobs.begin(), blobs.end(), [&goalFirmware](const std::string& iter) {
+            /* Running into weird scenarios where the string comparison doesn't
+             * work.  TODO: revisit.
+             */
+            return (0 == std::memcmp(goalFirmware.c_str(), iter.c_str(),
+                                     goalFirmware.length()));
+            // return (goalFirmware.compare(iter));
+        });
+    if (blobInst == blobs.end())
+    {
+        std::fprintf(stderr, "%s not found\n", goalFirmware.c_str());
+        return false;
+    }
+
+    /* Call stat on /flash/image (or /flash/tarball) and check if data interface
+     * is supported.
+     */
+    ipmiblob::StatResponse stat;
+
+    try
+    {
+        stat = blob->getStat(goalFirmware);
+    }
+    catch (const ipmiblob::BlobException& b)
+    {
+        std::fprintf(stderr, "Received exception '%s' on getStat\n", b.what());
+        return false;
+    }
+
+    auto supported = handler->supportedType();
+    if ((stat.blob_state & supported) == 0)
+    {
+        std::fprintf(stderr, "data interface selected not supported.\n");
+        return false;
+    }
+
+    return true;
+}
+
+void UpdateHandler::sendFile(const std::string& target, const std::string& path)
+{
+    std::uint16_t session;
+    auto supported = handler->supportedType();
+
+    try
+    {
+        session = blob->openBlob(
+            target, static_cast<std::uint16_t>(supported) |
+                        static_cast<std::uint16_t>(blobs::OpenFlags::write));
+    }
+    catch (const ipmiblob::BlobException& b)
+    {
+        throw ToolException("blob exception received: " +
+                            std::string(b.what()));
+    }
+
+    if (!handler->sendContents(path, session))
+    {
+        /* Need to close the session on failure, or it's stuck open (until the
+         * blob handler timeout is implemented, and even then, why make it wait.
+         */
+        blob->closeBlob(session);
+        throw ToolException("Failed to send contents of " + path);
+    }
+
+    blob->closeBlob(session);
+}
+
 /* Poll an open verification session.  Handling closing the session is not yet
  * owned by this method. */
 bool pollVerificationStatus(std::uint16_t session,
@@ -111,120 +185,15 @@
             blobs::FirmwareBlobHandler::VerifyCheckResponses::success);
 }
 
-void updaterMain(ipmiblob::BlobInterface* blob, DataInterface* handler,
-                 const std::string& imagePath, const std::string& signaturePath)
+bool UpdateHandler::verifyFile(const std::string& target)
 {
-    /* TODO(venture): Add optional parameter to specify the flash type, default
-     * to legacy for now.
-     *
-     * TODO(venture): Move the strings from the FirmwareHandler object to a
-     * boring utils object so it will be more happly linked cleanly to both the
-     * BMC and host-side.
-     */
-    std::string goalFirmware = "/flash/image";
-    std::string hashFilename = "/flash/hash";
-    std::string verifyFilename = "/flash/verify";
-
-    /* Get list of blob_ids, check for /flash/image, or /flash/tarball.
-     * TODO(venture) the mechanism doesn't care, but the caller of burn_my_bmc
-     * will have in mind which they're sending and we need to verify it's
-     * available and use it.
-     */
-    std::vector<std::string> blobs = blob->getBlobList();
-    auto blobInst = std::find_if(
-        blobs.begin(), blobs.end(), [&goalFirmware](const std::string& iter) {
-            /* Running into weird scenarios where the string comparison doesn't
-             * work.  TODO: revisit.
-             */
-            return (0 == std::memcmp(goalFirmware.c_str(), iter.c_str(),
-                                     goalFirmware.length()));
-            // return (goalFirmware.compare(iter));
-        });
-    if (blobInst == blobs.end())
-    {
-        throw ToolException(goalFirmware + " not found");
-    }
-
-    /* Call stat on /flash/image (or /flash/tarball) and check if data interface
-     * is supported.
-     */
-    ipmiblob::StatResponse stat;
-    try
-    {
-        stat = blob->getStat(goalFirmware);
-    }
-    catch (const ipmiblob::BlobException& b)
-    {
-        throw ToolException("blob exception received: " +
-                            std::string(b.what()));
-    }
-
-    auto supported = handler->supportedType();
-    if ((stat.blob_state & supported) == 0)
-    {
-        throw ToolException("data interface selected not supported.");
-    }
-
-    /* Yay, our data handler is supported. */
-
-    /* Send over the firmware image. */
-    std::fprintf(stderr, "Sending over the firmware image.\n");
     std::uint16_t session;
+    bool success = false;
+
     try
     {
         session = blob->openBlob(
-            goalFirmware,
-            static_cast<std::uint16_t>(supported) |
-                static_cast<std::uint16_t>(blobs::OpenFlags::write));
-    }
-    catch (const ipmiblob::BlobException& b)
-    {
-        throw ToolException("blob exception received: " +
-                            std::string(b.what()));
-    }
-
-    if (!handler->sendContents(imagePath, session))
-    {
-        /* Need to close the session on failure, or it's stuck open (until the
-         * blob handler timeout is implemented, and even then, why make it wait.
-         */
-        blob->closeBlob(session);
-        throw ToolException("Failed to send contents of " + imagePath);
-    }
-
-    blob->closeBlob(session);
-
-    /* Send over the hash contents. */
-    std::fprintf(stderr, "Sending over the hash file.\n");
-    try
-    {
-        session = blob->openBlob(
-            hashFilename,
-            static_cast<std::uint16_t>(supported) |
-                static_cast<std::uint16_t>(blobs::OpenFlags::write));
-    }
-    catch (const ipmiblob::BlobException& b)
-    {
-        throw ToolException("blob exception received: " +
-                            std::string(b.what()));
-    }
-
-    if (!handler->sendContents(signaturePath, session))
-    {
-        blob->closeBlob(session);
-        throw ToolException("Failed to send contents of " + signaturePath);
-    }
-
-    blob->closeBlob(session);
-
-    /* Trigger the verification by opening the verify file. */
-    std::fprintf(stderr, "Opening the verification file\n");
-    try
-    {
-        session = blob->openBlob(
-            verifyFilename,
-            static_cast<std::uint16_t>(supported) |
-                static_cast<std::uint16_t>(blobs::OpenFlags::write));
+            target, static_cast<std::uint16_t>(blobs::OpenFlags::write));
     }
     catch (const ipmiblob::BlobException& b)
     {
@@ -235,6 +204,7 @@
     std::fprintf(
         stderr,
         "Committing to verification file to trigger verification service\n");
+
     try
     {
         blob->commit(session, {});
@@ -251,6 +221,7 @@
     if (pollVerificationStatus(session, blob))
     {
         std::fprintf(stderr, "Verification returned success\n");
+        success = true;
     }
     else
     {
@@ -258,12 +229,50 @@
                              "be running (unlikely))\n");
     }
 
-    /* DO NOT CLOSE the verification session until it's done.
-     * TODO: Evaluate what closing verification should do?  If the process is
-     * complete, nothing bad, maybe reset the entire state machine?  This will
-     * benefit from a diagram.
-     */
     blob->closeBlob(session);
+    return (success == true);
+}
+
+void updaterMain(UpdateHandler* updater, const std::string& imagePath,
+                 const std::string& signaturePath)
+{
+    /* TODO(venture): Add optional parameter to specify the flash type, default
+     * to legacy for now.
+     *
+     * TODO(venture): Move the strings from the FirmwareHandler object to a
+     * boring utils object so it will be more happly linked cleanly to both the
+     * BMC and host-side.
+     */
+    std::string goalFirmware = "/flash/image";
+    std::string hashFilename = "/flash/hash";
+    std::string verifyFilename = "/flash/verify";
+
+    bool goalSupported = updater->checkAvailable(goalFirmware);
+    if (!goalSupported)
+    {
+        throw ToolException("Goal firmware or interface not supported");
+    }
+
+    /* Yay, our data handler is supported. */
+
+    /* Send over the firmware image. */
+    std::fprintf(stderr, "Sending over the firmware image.\n");
+    updater->sendFile(goalFirmware, imagePath);
+
+    /* Send over the hash contents. */
+    std::fprintf(stderr, "Sending over the hash file.\n");
+    updater->sendFile(hashFilename, signaturePath);
+
+    /* Trigger the verification by opening the verify file. */
+    std::fprintf(stderr, "Opening the verification file\n");
+    if (updater->verifyFile(verifyFilename))
+    {
+        std::fprintf(stderr, "succeeded\n");
+    }
+    else
+    {
+        std::fprintf(stderr, "failed\n");
+    }
 }
 
 } // namespace host_tool
diff --git a/tools/updater.hpp b/tools/updater.hpp
index 062f704..4674e91 100644
--- a/tools/updater.hpp
+++ b/tools/updater.hpp
@@ -8,6 +8,50 @@
 namespace host_tool
 {
 
+/** Object that actually handles the update itself. */
+class UpdateHandler
+{
+  public:
+    UpdateHandler(ipmiblob::BlobInterface* blob, DataInterface* handler) :
+        blob(blob), handler(handler)
+    {
+    }
+
+    virtual ~UpdateHandler() = default;
+
+    /**
+     * Check if the goal firmware is listed in the blob_list and that the
+     * handler's supported data type is available.
+     *
+     * @param[in] goalFirmware - the firmware to check /flash/image
+     * /flash/tarball, etc.
+     */
+    virtual bool checkAvailable(const std::string& goalFirmware);
+
+    /**
+     * Send the file contents at path to the blob id, target.
+     *
+     * @param[in] target - the blob id
+     * @param[in] path - the source file path
+     * @throw ToolException on failure.
+     */
+    virtual void sendFile(const std::string& target, const std::string& path);
+
+    /**
+     * Trigger verification.
+     *
+     * @param[in] target - the verification blob id (may support multiple in the
+     * future.
+     * @return true if verified, false if verification errors.
+     * @throw ToolException on failure (TODO: throw on timeout.)
+     */
+    virtual bool verifyFile(const std::string& target);
+
+  private:
+    ipmiblob::BlobInterface* blob;
+    DataInterface* handler;
+};
+
 /**
  * Poll an open verification session.
  *
@@ -21,14 +65,12 @@
 /**
  * Attempt to update the BMC's firmware using the interface provided.
  *
- * @param[in] blob - pointer to blob interface implementation object.
- * @param[in] handler - pointer to the data interface implementation object.
+ * @param[in] updater - update handler object.
  * @param[in] imagePath - the path to the image file.
  * @param[in] signaturePath - the path to the signature file.
  * @throws ToolException on failures.
  */
-void updaterMain(ipmiblob::BlobInterface* blob, DataInterface* handler,
-                 const std::string& imagePath,
+void updaterMain(UpdateHandler* updater, const std::string& imagePath,
                  const std::string& signaturePath);
 
 } // namespace host_tool