tools: implement firmware verification polling

The verification process is asynchronous, therefore poll the BMC
firmware state.

Tested: Verified it behaves as intended, before moving it into a
sub-routine:

Opening the verification file
Committing to verification file to trigger verification service
Calling stat on verification session to check status
stat received:
	blob_state: 0x206
	size: 0x0
	metadata(1): 0x3
	other
stat received:
	blob_state: 0x206
	size: 0x0
	metadata(1): 0x0
	running
stat received:
	blob_state: 0x206
	size: 0x0
	metadata(1): 0x0
	running
stat received:
	blob_state: 0x206
	size: 0x0
	metadata(1): 0x1
	success
stat received:
	blob_state: 0x206
	size: 0x0
	metadata(1): 0x1
	success

Signed-off-by: Patrick Venture <venture@google.com>
Change-Id: I3cb13d1a966c2c833fd4fee5654332f34e80355a
diff --git a/test/tools_updater_unittest.cpp b/test/tools_updater_unittest.cpp
index 04720e0..805ce20 100644
--- a/test/tools_updater_unittest.cpp
+++ b/test/tools_updater_unittest.cpp
@@ -66,6 +66,15 @@
 
     EXPECT_CALL(blobMock, commit(Eq(session), _)).WillOnce(Return());
 
+    ipmiblob::StatResponse verificationResponse;
+    verificationResponse.blob_state = supported | blobs::StateFlags::committing;
+    verificationResponse.size = 0;
+    verificationResponse.metadata.push_back(static_cast<std::uint8_t>(
+        blobs::FirmwareBlobHandler::VerifyCheckResponses::success));
+
+    EXPECT_CALL(blobMock, getStat(TypedEq<std::uint16_t>(session)))
+        .WillOnce(Return(verificationResponse));
+
     updaterMain(&blobMock, &handlerMock, firmwareImage, signatureFile);
 }
 
diff --git a/tools/updater.cpp b/tools/updater.cpp
index fd72136..7c78765 100644
--- a/tools/updater.cpp
+++ b/tools/updater.cpp
@@ -16,6 +16,7 @@
 
 #include "updater.hpp"
 
+#include "firmware_handler.hpp"
 #include "tool_errors.hpp"
 
 #include <algorithm>
@@ -24,10 +25,92 @@
 #include <ipmiblob/blob_errors.hpp>
 #include <memory>
 #include <string>
+#include <thread>
 
 namespace host_tool
 {
 
+/* Poll an open verification session.  Handling closing the session is not yet
+ * owned by this method. */
+bool pollVerificationStatus(std::uint16_t session,
+                            ipmiblob::BlobInterface* blob)
+{
+    using namespace std::chrono_literals;
+
+    static constexpr auto verificationSleep = 5s;
+    static constexpr int commandAttempts = 20;
+    int attempts = 0;
+    bool exitLoop = false;
+    blobs::FirmwareBlobHandler::VerifyCheckResponses result =
+        blobs::FirmwareBlobHandler::VerifyCheckResponses::other;
+
+    try
+    {
+        /* Reach back the current status from the verification service output.
+         */
+        while (attempts++ < commandAttempts)
+        {
+            ipmiblob::StatResponse resp = blob->getStat(session);
+
+            if (resp.metadata.size() != sizeof(std::uint8_t))
+            {
+                /* TODO: How do we want to handle the verification failures,
+                 * because closing the session to the verify blob has a special
+                 * as-of-yet not fully defined behavior.
+                 */
+                std::fprintf(stderr, "Received invalid metadata response!!!\n");
+            }
+
+            result =
+                static_cast<blobs::FirmwareBlobHandler::VerifyCheckResponses>(
+                    resp.metadata[0]);
+
+            switch (result)
+            {
+                case blobs::FirmwareBlobHandler::VerifyCheckResponses::failed:
+                    std::fprintf(stderr, "failed\n");
+                    exitLoop = true;
+                    break;
+                case blobs::FirmwareBlobHandler::VerifyCheckResponses::other:
+                    std::fprintf(stderr, "other\n");
+                    break;
+                case blobs::FirmwareBlobHandler::VerifyCheckResponses::running:
+                    std::fprintf(stderr, "running\n");
+                    break;
+                case blobs::FirmwareBlobHandler::VerifyCheckResponses::success:
+                    std::fprintf(stderr, "success\n");
+                    exitLoop = true;
+                    break;
+                default:
+                    std::fprintf(stderr, "wat\n");
+            }
+
+            if (exitLoop)
+            {
+                break;
+            }
+            std::this_thread::sleep_for(verificationSleep);
+        }
+    }
+    catch (const ipmiblob::BlobException& b)
+    {
+        throw ToolException("blob exception received: " +
+                            std::string(b.what()));
+    }
+
+    /* TODO: If this is reached and it's not success, it may be worth just
+     * throwing a ToolException with a timeout message specifying the final
+     * read's value.
+     *
+     * TODO: Given that excepting from certain points leaves the BMC update
+     * state machine in an inconsistent state, we need to carefully evaluate
+     * which exceptions from the lower layers allow one to try and delete the
+     * blobs to rollback the state and progress.
+     */
+    return (result ==
+            blobs::FirmwareBlobHandler::VerifyCheckResponses::success);
+}
+
 void updaterMain(ipmiblob::BlobInterface* blob, DataInterface* handler,
                  const std::string& imagePath, const std::string& signaturePath)
 {
@@ -162,9 +245,24 @@
                             std::string(b.what()));
     }
 
-    /* TODO: Check the verification via stat(session). */
+    std::fprintf(stderr,
+                 "Calling stat on verification session to check status\n");
 
-    /* DO NOT CLOSE the verification session until it's done. */
+    if (pollVerificationStatus(session, blob))
+    {
+        std::fprintf(stderr, "Verification returned success\n");
+    }
+    else
+    {
+        std::fprintf(stderr, "Verification returned non-success (could still "
+                             "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;
diff --git a/tools/updater.hpp b/tools/updater.hpp
index 0a66f34..062f704 100644
--- a/tools/updater.hpp
+++ b/tools/updater.hpp
@@ -9,6 +9,16 @@
 {
 
 /**
+ * Poll an open verification session.
+ *
+ * @param[in] session - the open verification session
+ * @param[in] blob - pointer to blob interface implementation object.
+ * @return true if the verification was successul.
+ */
+bool pollVerificationStatus(std::uint16_t session,
+                            ipmiblob::BlobInterface* blob);
+
+/**
  * Attempt to update the BMC's firmware using the interface provided.
  *
  * @param[in] blob - pointer to blob interface implementation object.