tools: blob: implement open blob

Implement the host-side tool's open blob command.

Change-Id: Iee432eae0539015e87969159a3d03761df9f8fb5
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/test/blob_interface_mock.hpp b/test/blob_interface_mock.hpp
index 8218ed5..fc1b284 100644
--- a/test/blob_interface_mock.hpp
+++ b/test/blob_interface_mock.hpp
@@ -1,9 +1,14 @@
 #include "blob_interface.hpp"
 
+#include <gmock/gmock.h>
+
 class BlobInterfaceMock : public BlobInterface
 {
   public:
     virtual ~BlobInterfaceMock() = default;
     MOCK_METHOD0(getBlobList, std::vector<std::string>());
     MOCK_METHOD1(getStat, StatResponse(const std::string&));
+    MOCK_METHOD2(openBlob,
+                 std::uint16_t(const std::string&,
+                               blobs::FirmwareBlobHandler::UpdateFlags));
 };
diff --git a/test/tools_blob_unittest.cpp b/test/tools_blob_unittest.cpp
index 0d5787e..e7e0ed6 100644
--- a/test/tools_blob_unittest.cpp
+++ b/test/tools_blob_unittest.cpp
@@ -140,3 +140,23 @@
     std::vector<std::uint8_t> metadata = {};
     EXPECT_EQ(metadata, meta.metadata);
 }
+
+TEST(BlobHandler, openBlobSucceeds)
+{
+    /* The open blob succeeds. */
+    IpmiInterfaceMock ipmiMock;
+    BlobHandler blob(&ipmiMock);
+
+    std::vector<std::uint8_t> request = {
+        0xcf, 0xc2, 0x00, BlobHandler::BlobOEMCommands::bmcBlobOpen,
+        0x00, 0x00, 0x02, 0x04,
+        'a',  'b',  'c',  'd'};
+
+    std::vector<std::uint8_t> resp = {0xcf, 0xc2, 0x00, 0x00, 0x00, 0xfe, 0xed};
+
+    EXPECT_CALL(ipmiMock, sendPacket(Eq(request))).WillOnce(Return(resp));
+
+    auto session =
+        blob.openBlob("abcd", blobs::FirmwareBlobHandler::UpdateFlags::lpc);
+    EXPECT_EQ(0xedfe, session);
+}
diff --git a/tools/blob_errors.hpp b/tools/blob_errors.hpp
new file mode 100644
index 0000000..b737b7b
--- /dev/null
+++ b/tools/blob_errors.hpp
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <exception>
+#include <string>
+
+class BlobException : public std::exception
+{
+  public:
+    explicit BlobException(const std::string& message) : message(message){};
+
+    virtual const char* what() const noexcept override
+    {
+        return message.c_str();
+    }
+
+  private:
+    std::string message;
+};
diff --git a/tools/blob_handler.cpp b/tools/blob_handler.cpp
index a30eb9e..dd4a97c 100644
--- a/tools/blob_handler.cpp
+++ b/tools/blob_handler.cpp
@@ -16,6 +16,7 @@
 
 #include "blob_handler.hpp"
 
+#include "blob_errors.hpp"
 #include "crc.hpp"
 #include "ipmi_errors.hpp"
 
@@ -169,3 +170,26 @@
 
     return meta;
 }
+
+std::uint16_t
+    BlobHandler::openBlob(const std::string& id,
+                          blobs::FirmwareBlobHandler::UpdateFlags handlerFlags)
+{
+    std::uint16_t session;
+    std::vector<std::uint8_t> request;
+    std::uint16_t flags =
+        blobs::FirmwareBlobHandler::UpdateFlags::openWrite | handlerFlags;
+    auto addrFlags = reinterpret_cast<std::uint8_t*>(&flags);
+    std::copy(addrFlags, addrFlags + sizeof(flags),
+              std::back_inserter(request));
+    std::copy(id.begin(), id.end(), std::back_inserter(request));
+
+    auto resp = sendIpmiPayload(BlobOEMCommands::bmcBlobOpen, request);
+    if (resp.size() != sizeof(session))
+    {
+        throw BlobException("Did not receive session.");
+    }
+
+    std::memcpy(&session, resp.data(), sizeof(session));
+    return session;
+}
diff --git a/tools/blob_handler.hpp b/tools/blob_handler.hpp
index fbc6fcb..bcac0a8 100644
--- a/tools/blob_handler.hpp
+++ b/tools/blob_handler.hpp
@@ -52,6 +52,9 @@
 
     std::vector<std::string> getBlobList() override;
     StatResponse getStat(const std::string& id) override;
+    std::uint16_t
+        openBlob(const std::string& id,
+                 blobs::FirmwareBlobHandler::UpdateFlags handlerFlags) override;
 
   private:
     IpmiInterface* ipmi;
diff --git a/tools/blob_interface.hpp b/tools/blob_interface.hpp
index 815874f..012550e 100644
--- a/tools/blob_interface.hpp
+++ b/tools/blob_interface.hpp
@@ -1,5 +1,7 @@
 #pragma once
 
+#include "firmware_handler.hpp"
+
 #include <cstdint>
 #include <string>
 #include <vector>
@@ -30,4 +32,16 @@
      * @return metadata structure.
      */
     virtual StatResponse getStat(const std::string& id) = 0;
+
+    /**
+     * Attempt to open the file using the specific data interface flag.
+     *
+     * @param[in] blob - the blob_id to open.
+     * @param[in] handlerFlags - the data interface flag, if relevant.
+     * @return the session id on success.
+     * @throws BlobException on failure.
+     */
+    virtual std::uint16_t
+        openBlob(const std::string& id,
+                 blobs::FirmwareBlobHandler::UpdateFlags handlerFlags) = 0;
 };
diff --git a/tools/updater.cpp b/tools/updater.cpp
index a7f9c96..5061e82 100644
--- a/tools/updater.cpp
+++ b/tools/updater.cpp
@@ -16,6 +16,8 @@
 
 #include "updater.hpp"
 
+#include "blob_errors.hpp"
+
 #include <algorithm>
 #include <memory>
 
@@ -50,5 +52,19 @@
         return -1; /* throw custom exception. */
     }
 
+    /* Yay, our data handler is supported. */
+    std::uint16_t session;
+    try
+    {
+        session = blob->openBlob(goalFirmware, handler->supportedType());
+    }
+    catch (const BlobException& b)
+    {
+        std::fprintf(stderr, "blob exception received: %s\n", b.what());
+        return -1;
+    }
+
+    std::fprintf(stderr, "using session: %d\n", session);
+
     return 0;
 }