bmc: add ActionPack notion to bundle actions

Each firmware type will provide its own set of action implementations
for each step, preparation, verification, and update.

Signed-off-by: Patrick Venture <venture@google.com>
Change-Id: Id6409ac356a74e9094272b37709861e2a33d9862
diff --git a/bmc/test/firmware_multiplebundle_unittest.cpp b/bmc/test/firmware_multiplebundle_unittest.cpp
new file mode 100644
index 0000000..b8d738d
--- /dev/null
+++ b/bmc/test/firmware_multiplebundle_unittest.cpp
@@ -0,0 +1,155 @@
+/* The goal of these tests is to verify that once a host-client has started the
+ * process with one blob bundle, they cannot pivot to upload data to another.
+ *
+ * This prevent someone from starting to upload a BMC firmware, and then midway
+ * through start uploading a BIOS image.
+ */
+#include "firmware_handler.hpp"
+#include "flags.hpp"
+#include "image_mock.hpp"
+#include "status.hpp"
+#include "triggerable_mock.hpp"
+#include "util.hpp"
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+
+using ::testing::Return;
+
+class IpmiOnlyTwoFirmwaresTest : public ::testing::Test
+{
+  protected:
+    void SetUp() override
+    {
+        blobs = {
+            {hashBlobId, &hashImageMock},
+            {staticLayoutBlobId, &staticImageMock},
+            {biosId, &biosImageMock},
+        };
+
+        std::unique_ptr<TriggerableActionInterface> bmcPrepareMock =
+            std::make_unique<TriggerMock>();
+        bmcPrepareMockPtr =
+            reinterpret_cast<TriggerMock*>(bmcPrepareMock.get());
+
+        std::unique_ptr<TriggerableActionInterface> bmcVerifyMock =
+            std::make_unique<TriggerMock>();
+        bmcVerifyMockPtr = reinterpret_cast<TriggerMock*>(bmcVerifyMock.get());
+
+        std::unique_ptr<TriggerableActionInterface> bmcUpdateMock =
+            std::make_unique<TriggerMock>();
+        bmcUpdateMockPtr = reinterpret_cast<TriggerMock*>(bmcUpdateMock.get());
+
+        std::unique_ptr<TriggerableActionInterface> biosPrepareMock =
+            std::make_unique<TriggerMock>();
+        biosPrepareMockPtr =
+            reinterpret_cast<TriggerMock*>(biosPrepareMock.get());
+
+        std::unique_ptr<TriggerableActionInterface> biosVerifyMock =
+            std::make_unique<TriggerMock>();
+        biosVerifyMockPtr =
+            reinterpret_cast<TriggerMock*>(biosVerifyMock.get());
+
+        std::unique_ptr<TriggerableActionInterface> biosUpdateMock =
+            std::make_unique<TriggerMock>();
+        biosUpdateMockPtr =
+            reinterpret_cast<TriggerMock*>(biosUpdateMock.get());
+
+        ActionMap packs;
+
+        std::unique_ptr<ActionPack> bmcPack = std::make_unique<ActionPack>();
+        bmcPack->preparation = std::move(bmcPrepareMock);
+        bmcPack->verification = std::move(bmcVerifyMock);
+        bmcPack->update = std::move(bmcUpdateMock);
+
+        std::unique_ptr<ActionPack> biosPack = std::make_unique<ActionPack>();
+        biosPack->preparation = std::move(biosPrepareMock);
+        biosPack->verification = std::move(biosVerifyMock);
+        biosPack->update = std::move(biosUpdateMock);
+
+        packs[staticLayoutBlobId] = std::move(bmcPack);
+        packs[biosId] = std::move(biosPack);
+
+        handler = FirmwareBlobHandler::CreateFirmwareBlobHandler(
+            blobs, data, std::move(packs));
+    }
+
+    void expectedState(FirmwareBlobHandler::UpdateState state)
+    {
+        auto realHandler = dynamic_cast<FirmwareBlobHandler*>(handler.get());
+        EXPECT_EQ(state, realHandler->getCurrentState());
+    }
+
+    ImageHandlerMock hashImageMock, staticImageMock, biosImageMock;
+
+    std::vector<HandlerPack> blobs;
+    std::vector<DataHandlerPack> data = {
+        {FirmwareFlags::UpdateFlags::ipmi, nullptr}};
+
+    std::unique_ptr<blobs::GenericBlobInterface> handler;
+
+    TriggerMock *bmcPrepareMockPtr, *bmcVerifyMockPtr, *bmcUpdateMockPtr;
+    TriggerMock *biosPrepareMockPtr, *biosVerifyMockPtr, *biosUpdateMockPtr;
+
+    std::uint16_t session = 1;
+    std::uint16_t flags =
+        blobs::OpenFlags::write | FirmwareFlags::UpdateFlags::ipmi;
+
+    const std::string biosId = "/flash/bios";
+};
+
+TEST_F(IpmiOnlyTwoFirmwaresTest, OpeningBiosAfterBlobFails)
+{
+    /* You can only have one file open at a time, and the first firmware file
+     * you open locks it down
+     */
+    EXPECT_CALL(staticImageMock, open(staticLayoutBlobId))
+        .WillOnce(Return(true));
+    EXPECT_CALL(*bmcPrepareMockPtr, trigger()).WillOnce(Return(true));
+
+    EXPECT_TRUE(handler->open(session, flags, staticLayoutBlobId));
+    expectedState(FirmwareBlobHandler::UpdateState::uploadInProgress);
+
+    EXPECT_CALL(staticImageMock, close()).WillOnce(Return());
+    handler->close(session);
+
+    expectedState(FirmwareBlobHandler::UpdateState::verificationPending);
+
+    EXPECT_CALL(biosImageMock, open(biosId)).Times(0);
+    EXPECT_FALSE(handler->open(session, flags, biosId));
+}
+
+TEST_F(IpmiOnlyTwoFirmwaresTest, OpeningHashBeforeBiosSucceeds)
+{
+    /* Opening the hash blob does nothing special in this regard. */
+    EXPECT_CALL(hashImageMock, open(hashBlobId)).WillOnce(Return(true));
+    EXPECT_TRUE(handler->open(session, flags, hashBlobId));
+
+    expectedState(FirmwareBlobHandler::UpdateState::uploadInProgress);
+
+    EXPECT_CALL(hashImageMock, close()).WillOnce(Return());
+    handler->close(session);
+
+    expectedState(FirmwareBlobHandler::UpdateState::verificationPending);
+    ASSERT_FALSE(handler->canHandleBlob(verifyBlobId));
+
+    EXPECT_CALL(biosImageMock, open(biosId)).WillOnce(Return(true));
+    EXPECT_TRUE(handler->open(session, flags, biosId));
+
+    expectedState(FirmwareBlobHandler::UpdateState::uploadInProgress);
+
+    EXPECT_CALL(biosImageMock, close()).WillOnce(Return());
+    handler->close(session);
+}
+
+} // namespace
+} // namespace ipmi_flash