move files around to create a common convenience library
Problem: plan is to add another blob handler into ipmi-flash
(ipmi-flash-version). This new handler will re-use much of the
ipmi-flash (firmware-handler) code. The common code should be presented
as a convenience library to reduce code duplication.
Solution: move anticipated firmware-handler specific code into the
subdirectory bmc/firmware-handler and leave common code in bmc/.
The end goal is to have version-handler re-use as
much code as possible.
Tested:
rebuilt everything and ran unit tests.
Signed-off-by: Jason Ling <jasonling@google.com>
Change-Id: I2128da629b0ddf27b89f1faee358d1941f1dff38
diff --git a/bmc/firmware-handler/Makefile.am b/bmc/firmware-handler/Makefile.am
new file mode 100644
index 0000000..c4fe392
--- /dev/null
+++ b/bmc/firmware-handler/Makefile.am
@@ -0,0 +1,106 @@
+AM_DEFAULT_SOURCE_EXT = .cpp
+
+pkgdatadir = $(datadir)/phosphor-ipmi-flash
+dist_pkgdata_DATA =
+
+# reboot update is only effective for static layout
+# static layout is also possible with a tarball+bmc-code-mgmt
+#
+# the bmc-code-mgmt will reboot for us or if we need to support this
+# variation (via the ApplyTime to immediate) (only currently supported for
+# ubi).
+if ENABLE_STATIC_LAYOUT
+if ENABLE_REBOOT_UPDATE
+dist_pkgdata_DATA += config-static-bmc-reboot.json
+else
+if ENABLE_UPDATE_STATUS
+dist_pkgdata_DATA += config-static-bmc-with-update-status.json
+else
+dist_pkgdata_DATA += config-static-bmc.json
+endif
+endif
+endif
+
+if ENABLE_HOST_BIOS
+dist_pkgdata_DATA += config-bios.json
+endif
+
+if HAVE_SYSTEMD
+systemdsystemunit_DATA = \
+ phosphor-ipmi-flash-bmc-prepare.target \
+ phosphor-ipmi-flash-bmc-verify.target \
+ phosphor-ipmi-flash-bmc-update.target
+if ENABLE_HOST_BIOS
+systemdsystemunit_DATA += \
+ phosphor-ipmi-flash-bios-prepare.target \
+ phosphor-ipmi-flash-bios-verify.target \
+ phosphor-ipmi-flash-bios-update.target
+endif
+endif
+# Convenience libraries that are discarded after build
+noinst_LTLIBRARIES = libfirmwareblob_common.la
+
+# firmware blob handler specific
+libfirmwareblob_common_la_SOURCES = \
+ buildjson.cpp \
+ firmware_handler.cpp \
+ lpc_handler.cpp
+
+if ENABLE_ASPEED_LPC
+libfirmwareblob_common_la_SOURCES += lpc_aspeed.cpp
+endif
+
+if ENABLE_NUVOTON_LPC
+libfirmwareblob_common_la_SOURCES += lpc_nuvoton.cpp
+endif
+
+if ENABLE_ASPEED_P2A
+libfirmwareblob_common_la_SOURCES += pci_handler.cpp
+endif
+
+if ENABLE_NUVOTON_P2A_VGA
+libfirmwareblob_common_la_SOURCES += pci_nuvoton_handler.cpp
+endif
+
+if ENABLE_NUVOTON_P2A_MBOX
+libfirmwareblob_common_la_SOURCES += pci_nuvoton_handler.cpp
+endif
+
+if ENABLE_NET_BRIDGE
+libfirmwareblob_common_la_SOURCES += net_handler.cpp
+endif
+
+libfirmwareblob_common_la_CXXFLAGS = \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/bmc \
+ $(SDBUSPLUS_CFLAGS) \
+ $(PHOSPHOR_LOGGING_CFLAGS) \
+ $(CODE_COVERAGE_CXXFLAGS) \
+ -flto
+libfirmwareblob_common_la_LDFLAGS = \
+ $(SDBUSPLUS_LIBS) \
+ $(PHOSPHOR_LOGGING_LIBS) \
+ $(CODE_COVERAGE_LIBS) \
+ -lstdc++fs
+libfirmwareblob_common_la_LIBADD = $(top_builddir)/libfirmware_common.la
+libfirmwareblob_common_la_LIBADD += $(top_builddir)/bmc/libbmc_common.la
+
+libfirmwareblobdir = ${libdir}/ipmid-providers
+libfirmwareblob_LTLIBRARIES = libfirmwareblob.la
+libfirmwareblob_la_SOURCES = main.cpp
+libfirmwareblob_la_LIBADD = libfirmwareblob_common.la
+libfirmwareblob_la_LDFLAGS = \
+ $(SDBUSPLUS_LIBS) \
+ $(PHOSPHOR_LOGGING_LIBS) \
+ $(CODE_COVERAGE_LIBS) \
+ -lstdc++fs \
+ -version-info 0:0:0 -shared
+libfirmwareblob_la_CXXFLAGS = \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/bmc \
+ $(SDBUSPLUS_CFLAGS) \
+ $(PHOSPHOR_LOGGING_CFLAGS) \
+ $(CODE_COVERAGE_CXXFLAGS) \
+ -flto
+
+SUBDIRS = . test
diff --git a/bmc/firmware-handler/buildjson.cpp b/bmc/firmware-handler/buildjson.cpp
new file mode 100644
index 0000000..095161c
--- /dev/null
+++ b/bmc/firmware-handler/buildjson.cpp
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "buildjson.hpp"
+
+#include "file_handler.hpp"
+#include "fs.hpp"
+#include "general_systemd.hpp"
+#include "skip_action.hpp"
+
+#include <nlohmann/json.hpp>
+#include <sdbusplus/bus.hpp>
+
+#include <algorithm>
+#include <cstdio>
+#include <exception>
+#include <fstream>
+#include <regex>
+#include <string>
+#include <vector>
+
+namespace ipmi_flash
+{
+
+std::unique_ptr<TriggerableActionInterface>
+ buildFileSystemd(const nlohmann::json& data)
+{
+ /* This type of action requires a path and unit, and optionally a mode. */
+ const auto& path = data.at("path");
+ const auto& unit = data.at("unit");
+
+ /* the mode parameter is optional. */
+ std::string systemdMode = "replace";
+ const auto& mode = data.find("mode");
+ if (mode != data.end())
+ {
+ systemdMode = data.at("mode").get<std::string>();
+ }
+
+ return SystemdWithStatusFile::CreateSystemdWithStatusFile(
+ sdbusplus::bus::new_default(), path, unit, systemdMode);
+}
+
+std::unique_ptr<TriggerableActionInterface>
+ buildSystemd(const nlohmann::json& data)
+{
+ /* This type of action requires a unit, and optionally a mode. */
+ const auto& unit = data.at("unit");
+
+ /* the mode parameter is optional. */
+ std::string systemdMode = "replace";
+ const auto& mode = data.find("mode");
+ if (mode != data.end())
+ {
+ systemdMode = data.at("mode").get<std::string>();
+ }
+
+ return SystemdNoFile::CreateSystemdNoFile(sdbusplus::bus::new_default(),
+ unit, systemdMode);
+}
+
+std::vector<HandlerConfig> buildHandlerFromJson(const nlohmann::json& data)
+{
+ std::vector<HandlerConfig> handlers;
+
+ for (const auto& item : data)
+ {
+ try
+ {
+ HandlerConfig output;
+
+ /* at() throws an exception when the key is not present. */
+ item.at("blob").get_to(output.blobId);
+
+ /* name must be: /flash/... */
+ if (!std::regex_match(output.blobId, std::regex("^\\/flash\\/.+")))
+ {
+ throw std::runtime_error("Invalid blob name: '" +
+ output.blobId +
+ "' must start with /flash/");
+ }
+
+ /* handler is required. */
+ const auto& h = item.at("handler");
+ const std::string handlerType = h.at("type");
+ if (handlerType == "file")
+ {
+ const auto& path = h.at("path");
+ output.handler = std::make_unique<FileHandler>(path);
+ }
+ else
+ {
+ throw std::runtime_error("Invalid handler type: " +
+ handlerType);
+ }
+
+ /* actions are required (presently). */
+ const auto& a = item.at("actions");
+ std::unique_ptr<ActionPack> pack = std::make_unique<ActionPack>();
+
+ /* to make an action optional, assign type "skip" */
+ const auto& prep = a.at("preparation");
+ const std::string prepareType = prep.at("type");
+ if (prepareType == "systemd")
+ {
+ pack->preparation = std::move(buildSystemd(prep));
+ }
+ else if (prepareType == "skip")
+ {
+ pack->preparation = SkipAction::CreateSkipAction();
+ }
+ else
+ {
+ throw std::runtime_error("Invalid preparation type: " +
+ prepareType);
+ }
+
+ const auto& verify = a.at("verification");
+ const std::string verifyType = verify.at("type");
+ if (verifyType == "fileSystemdVerify")
+ {
+ pack->verification = std::move(buildFileSystemd(verify));
+ }
+ else if (verifyType == "systemd")
+ {
+ pack->verification = std::move(buildSystemd(verify));
+ }
+ else if (verifyType == "skip")
+ {
+ pack->verification = SkipAction::CreateSkipAction();
+ }
+ else
+ {
+ throw std::runtime_error("Invalid verification type:" +
+ verifyType);
+ }
+
+ const auto& update = a.at("update");
+ const std::string updateType = update.at("type");
+ if (updateType == "reboot")
+ {
+ pack->update = SystemdNoFile::CreateSystemdNoFile(
+ sdbusplus::bus::new_default(), "reboot.target",
+ "replace-irreversibly");
+ }
+ else if (updateType == "fileSystemdUpdate")
+ {
+ pack->update = std::move(buildFileSystemd(update));
+ }
+ else if (updateType == "systemd")
+ {
+ pack->update = std::move(buildSystemd(update));
+ }
+ else if (updateType == "skip")
+ {
+ pack->update = SkipAction::CreateSkipAction();
+ }
+ else
+ {
+ throw std::runtime_error("Invalid update type: " + updateType);
+ }
+
+ output.actions = std::move(pack);
+ handlers.push_back(std::move(output));
+ }
+ catch (const std::exception& e)
+ {
+ /* TODO: Once phosphor-logging supports unit-test injection, fix
+ * this to log.
+ */
+ std::fprintf(stderr,
+ "Excepted building HandlerConfig from json: %s\n",
+ e.what());
+ }
+ }
+
+ return handlers;
+}
+
+std::vector<HandlerConfig> BuildHandlerConfigs(const std::string& directory)
+{
+ std::vector<HandlerConfig> output;
+
+ std::vector<std::string> jsonPaths = GetJsonList(directory);
+
+ for (const auto& path : jsonPaths)
+ {
+ std::ifstream jsonFile(path);
+ if (!jsonFile.is_open())
+ {
+ std::fprintf(stderr, "Unable to open json file: %s\n",
+ path.c_str());
+ continue;
+ }
+
+ auto data = nlohmann::json::parse(jsonFile, nullptr, false);
+ if (data.is_discarded())
+ {
+ std::fprintf(stderr, "Parsing json failed: %s\n", path.c_str());
+ }
+
+ std::vector<HandlerConfig> configs = buildHandlerFromJson(data);
+ std::move(configs.begin(), configs.end(), std::back_inserter(output));
+ }
+
+ return output;
+}
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/buildjson.hpp b/bmc/firmware-handler/buildjson.hpp
new file mode 100644
index 0000000..0af4241
--- /dev/null
+++ b/bmc/firmware-handler/buildjson.hpp
@@ -0,0 +1,66 @@
+#pragma once
+
+#include "firmware_handler.hpp"
+#include "image_handler.hpp"
+
+#include <nlohmann/json.hpp>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace ipmi_flash
+{
+
+class HandlerConfig
+{
+ public:
+ HandlerConfig() = default;
+ ~HandlerConfig() = default;
+ HandlerConfig(const HandlerConfig&) = delete;
+ HandlerConfig& operator=(const HandlerConfig&) = delete;
+ HandlerConfig(HandlerConfig&&) = default;
+ HandlerConfig& operator=(HandlerConfig&&) = default;
+
+ /* A string in the form: /flash/{unique}, s.t. unique is something like,
+ * flash, ubitar, statictar, or bios
+ */
+ std::string blobId;
+
+ /* This owns a handler interface, this is typically going to be a file
+ * writer object.
+ */
+ std::unique_ptr<ImageHandlerInterface> handler;
+
+ /* Only the hashBlobId doesn't have an action pack, otherwise it's required.
+ */
+ std::unique_ptr<ActionPack> actions;
+};
+
+/**
+ * Given a list of handlers as json data, construct the appropriate
+ * HandlerConfig objects. This method is meant to be called per json
+ * configuration file found.
+ *
+ * The list will only contain validly build HandlerConfig objects. Any invalid
+ * configuration is skipped. The hope is that the BMC firmware update
+ * configuration will never be invalid, but if another aspect is invalid, it can
+ * be fixed with a BMC firmware update once the bug is identified.
+ *
+ * This code does not validate that the blob specified is unique, that should
+ * be handled at a higher layer.
+ *
+ * @param[in] data - json data from a json file.
+ * @return list of HandlerConfig objects.
+ */
+std::vector<HandlerConfig> buildHandlerFromJson(const nlohmann::json& data);
+
+/**
+ * Given a folder of json configs, build the configurations.
+ *
+ * @param[in] directory - the directory to search (recurisvely).
+ * @return list of HandlerConfig objects.
+ */
+std::vector<HandlerConfig> BuildHandlerConfigs(const std::string& directory);
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/config-bios.json.in b/bmc/firmware-handler/config-bios.json.in
new file mode 100644
index 0000000..2267704
--- /dev/null
+++ b/bmc/firmware-handler/config-bios.json.in
@@ -0,0 +1,21 @@
+[{
+ "blob": "/flash/bios",
+ "handler": {
+ "type": "file",
+ "path": "@BIOS_STAGED_NAME@"
+ },
+ "actions": {
+ "preparation": {
+ "type": "systemd",
+ "unit": "@PREPARATION_BIOS_TARGET@"
+ },
+ "verification": {
+ "type": "systemd",
+ "unit": "@VERIFY_BIOS_TARGET@"
+ },
+ "update": {
+ "type": "systemd",
+ "unit": "@UPDATE_BIOS_TARGET@"
+ }
+ }
+}]
diff --git a/bmc/firmware-handler/config-static-bmc-reboot.json.in b/bmc/firmware-handler/config-static-bmc-reboot.json.in
new file mode 100644
index 0000000..e2618e8
--- /dev/null
+++ b/bmc/firmware-handler/config-static-bmc-reboot.json.in
@@ -0,0 +1,20 @@
+[{
+ "blob": "/flash/image",
+ "handler": {
+ "type": "file",
+ "path": "@STATIC_HANDLER_STAGED_NAME@"
+ },
+ "actions": {
+ "preparation": {
+ "type": "systemd",
+ "unit": "@PREPARATION_DBUS_SERVICE@"
+ },
+ "verification": {
+ "type": "systemd",
+ "unit": "@VERIFY_DBUS_SERVICE@"
+ },
+ "update": {
+ "type": "reboot"
+ }
+ }
+}]
diff --git a/bmc/firmware-handler/config-static-bmc-with-update-status.json.in b/bmc/firmware-handler/config-static-bmc-with-update-status.json.in
new file mode 100644
index 0000000..e2077a8
--- /dev/null
+++ b/bmc/firmware-handler/config-static-bmc-with-update-status.json.in
@@ -0,0 +1,21 @@
+[{
+ "blob": "/flash/image",
+ "handler": {
+ "type": "file",
+ "path": "@STATIC_HANDLER_STAGED_NAME@"
+ },
+ "actions": {
+ "preparation": {
+ "type": "systemd",
+ "unit": "@PREPARATION_DBUS_SERVICE@"
+ },
+ "verification": {
+ "type": "systemd",
+ "unit": "@VERIFY_DBUS_SERVICE@"
+ },
+ "update": {
+ "type": "systemd",
+ "unit": "@UPDATE_DBUS_SERVICE@"
+ }
+ }
+}]
diff --git a/bmc/firmware-handler/config-static-bmc.json.in b/bmc/firmware-handler/config-static-bmc.json.in
new file mode 100644
index 0000000..e2077a8
--- /dev/null
+++ b/bmc/firmware-handler/config-static-bmc.json.in
@@ -0,0 +1,21 @@
+[{
+ "blob": "/flash/image",
+ "handler": {
+ "type": "file",
+ "path": "@STATIC_HANDLER_STAGED_NAME@"
+ },
+ "actions": {
+ "preparation": {
+ "type": "systemd",
+ "unit": "@PREPARATION_DBUS_SERVICE@"
+ },
+ "verification": {
+ "type": "systemd",
+ "unit": "@VERIFY_DBUS_SERVICE@"
+ },
+ "update": {
+ "type": "systemd",
+ "unit": "@UPDATE_DBUS_SERVICE@"
+ }
+ }
+}]
diff --git a/bmc/firmware-handler/data_handler.hpp b/bmc/firmware-handler/data_handler.hpp
new file mode 100644
index 0000000..162347c
--- /dev/null
+++ b/bmc/firmware-handler/data_handler.hpp
@@ -0,0 +1,76 @@
+#pragma once
+
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+namespace ipmi_flash
+{
+
+/**
+ * Each data transport mechanism must implement the DataInterface.
+ */
+class DataInterface
+{
+ public:
+ virtual ~DataInterface() = default;
+
+ /**
+ * Initialize data transport mechanism. Calling this should be idempotent
+ * if possible.
+ *
+ * @return true if successful
+ */
+ virtual bool open() = 0;
+
+ /**
+ * Close the data transport mechanism.
+ *
+ * @return true if successful
+ */
+ virtual bool close() = 0;
+
+ /**
+ * Copy bytes from external interface (blocking call).
+ *
+ * @param[in] length - number of bytes to copy
+ * @return the bytes read
+ */
+ virtual std::vector<std::uint8_t> copyFrom(std::uint32_t length) = 0;
+
+ /**
+ * set configuration.
+ *
+ * @param[in] configuration - byte vector of data.
+ * @return bool - returns true on success.
+ */
+ virtual bool writeMeta(const std::vector<std::uint8_t>& configuration) = 0;
+
+ /**
+ * read configuration.
+ *
+ * @return bytes - whatever bytes are required configuration information for
+ * the mechanism.
+ */
+ virtual std::vector<std::uint8_t> readMeta() = 0;
+};
+
+struct DataHandlerPack
+{
+ std::uint16_t bitmask;
+ std::unique_ptr<DataInterface> handler;
+
+ DataHandlerPack(std::uint16_t bitmask,
+ std::unique_ptr<DataInterface> handler) :
+ bitmask(bitmask),
+ handler(std::move(handler))
+ {}
+
+ /* Don't allow copying, assignment or move assignment, only moving. */
+ DataHandlerPack(const DataHandlerPack&) = delete;
+ DataHandlerPack& operator=(const DataHandlerPack&) = delete;
+ DataHandlerPack(DataHandlerPack&&) = default;
+ DataHandlerPack& operator=(DataHandlerPack&&) = delete;
+};
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/firmware_handler.cpp b/bmc/firmware-handler/firmware_handler.cpp
new file mode 100644
index 0000000..d71cc9f
--- /dev/null
+++ b/bmc/firmware-handler/firmware_handler.cpp
@@ -0,0 +1,852 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "firmware_handler.hpp"
+
+#include "data.hpp"
+#include "flags.hpp"
+#include "image_handler.hpp"
+#include "status.hpp"
+#include "util.hpp"
+
+#include <blobs-ipmid/blobs.hpp>
+
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+#include <fstream>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace ipmi_flash
+{
+
+std::unique_ptr<blobs::GenericBlobInterface>
+ FirmwareBlobHandler::CreateFirmwareBlobHandler(
+ std::vector<HandlerPack>&& firmwares,
+ std::vector<DataHandlerPack>&& transports, ActionMap&& actionPacks)
+{
+ /* There must be at least one in addition to the hash blob handler. */
+ if (firmwares.size() < 2)
+ {
+ std::fprintf(stderr, "Must provide at least two firmware handlers.");
+ return nullptr;
+ }
+ if (transports.empty())
+ {
+ return nullptr;
+ }
+ if (actionPacks.empty())
+ {
+ return nullptr;
+ }
+
+ std::vector<std::string> blobs;
+ for (const auto& item : firmwares)
+ {
+ blobs.push_back(item.blobName);
+ }
+
+ if (0 == std::count(blobs.begin(), blobs.end(), hashBlobId))
+ {
+ return nullptr;
+ }
+
+ return std::make_unique<FirmwareBlobHandler>(std::move(firmwares), blobs,
+ std::move(transports),
+ std::move(actionPacks));
+}
+
+/* Check if the path is in our supported list (or active list). */
+bool FirmwareBlobHandler::canHandleBlob(const std::string& path)
+{
+ return (std::count(blobIDs.begin(), blobIDs.end(), path) > 0);
+}
+
+/*
+ * Grab the list of supported firmware.
+ *
+ * If there's an open firmware session, it'll already be present in the
+ * list as "/flash/active/image", and if the hash has started,
+ * "/flash/active/hash" regardless of mechanism. This is done in the open
+ * comamnd, no extra work is required here.
+ */
+std::vector<std::string> FirmwareBlobHandler::getBlobIds()
+{
+ return blobIDs;
+}
+
+/*
+ * Per the design, this mean abort, and this will trigger whatever
+ * appropriate actions are required to abort the process.
+ */
+bool FirmwareBlobHandler::deleteBlob(const std::string& path)
+{
+ switch (state)
+ {
+ case UpdateState::notYetStarted:
+ /* Trying to delete anything at this point has no effect and returns
+ * false.
+ */
+ return false;
+ case UpdateState::verificationPending:
+ abortProcess();
+ return true;
+ case UpdateState::updatePending:
+ abortProcess();
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+/*
+ * Stat on the files will return information such as what supported
+ * transport mechanisms are available.
+ *
+ * Stat on an active file or hash will return information such as the size
+ * of the data cached, and any additional pertinent information. The
+ * blob_state on the active files will return the state of the update.
+ */
+bool FirmwareBlobHandler::stat(const std::string& path, blobs::BlobMeta* meta)
+{
+ /* We know we support this path because canHandle is called ahead */
+ if (path == verifyBlobId || path == activeImageBlobId ||
+ path == activeHashBlobId || path == updateBlobId)
+ {
+ /* These blobs are placeholders that indicate things, or allow actions,
+ * but are not stat-able as-is.
+ */
+ return false;
+ }
+
+ /* They are requesting information about the generic blob_id. */
+
+ /* Older host tools expect the blobState to contain a bitmask of available
+ * transport backends, so report that we support all of them in order to
+ * preserve backwards compatibility.
+ */
+ meta->blobState = transportMask;
+ meta->size = 0;
+ return true;
+}
+
+ActionStatus FirmwareBlobHandler::getActionStatus()
+{
+ ActionStatus value = ActionStatus::unknown;
+ auto* pack = getActionPack();
+
+ switch (state)
+ {
+ case UpdateState::verificationPending:
+ value = ActionStatus::unknown;
+ break;
+ case UpdateState::verificationStarted:
+ /* If we got here, there must be data AND a hash, not just a hash,
+ * therefore pack will be known. */
+ if (!pack)
+ {
+ break;
+ }
+ value = pack->verification->status();
+ lastVerificationStatus = value;
+ break;
+ case UpdateState::verificationCompleted:
+ value = lastVerificationStatus;
+ break;
+ case UpdateState::updatePending:
+ value = ActionStatus::unknown;
+ break;
+ case UpdateState::updateStarted:
+ if (!pack)
+ {
+ break;
+ }
+ value = pack->update->status();
+ lastUpdateStatus = value;
+ break;
+ case UpdateState::updateCompleted:
+ value = lastUpdateStatus;
+ break;
+ default:
+ break;
+ }
+
+ return value;
+}
+
+/*
+ * Return stat information on an open session. It therefore must be an active
+ * handle to either the active image or active hash.
+ */
+bool FirmwareBlobHandler::stat(uint16_t session, blobs::BlobMeta* meta)
+{
+ auto item = lookup.find(session);
+ if (item == lookup.end())
+ {
+ return false;
+ }
+
+ /* The size here refers to the size of the file -- of something analagous.
+ */
+ meta->size = (item->second->imageHandler)
+ ? item->second->imageHandler->getSize()
+ : 0;
+
+ meta->metadata.clear();
+
+ if (item->second->activePath == verifyBlobId ||
+ item->second->activePath == updateBlobId)
+ {
+ ActionStatus value = getActionStatus();
+
+ meta->metadata.push_back(static_cast<std::uint8_t>(value));
+
+ /* Change the firmware handler's state and the blob's stat value
+ * depending.
+ */
+ if (value == ActionStatus::success || value == ActionStatus::failed)
+ {
+ if (item->second->activePath == verifyBlobId)
+ {
+ changeState(UpdateState::verificationCompleted);
+ }
+ else
+ {
+ /* item->second->activePath == updateBlobId */
+ changeState(UpdateState::updateCompleted);
+ }
+
+ item->second->flags &= ~blobs::StateFlags::committing;
+
+ if (value == ActionStatus::success)
+ {
+ item->second->flags |= blobs::StateFlags::committed;
+ }
+ else
+ {
+ item->second->flags |= blobs::StateFlags::commit_error;
+ }
+ }
+ }
+
+ /* The blobState here relates to an active sesion, so we should return the
+ * flags used to open this session.
+ */
+ meta->blobState = item->second->flags;
+
+ /* The metadata blob returned comes from the data handler... it's used for
+ * instance, in P2A bridging to get required information about the mapping,
+ * and is the "opposite" of the lpc writemeta requirement.
+ */
+ if (item->second->dataHandler)
+ {
+ auto bytes = item->second->dataHandler->readMeta();
+ meta->metadata.insert(meta->metadata.begin(), bytes.begin(),
+ bytes.end());
+ }
+
+ return true;
+}
+
+/*
+ * If you open /flash/image or /flash/tarball, or /flash/hash it will
+ * interpret the open flags and perform whatever actions are required for
+ * that update process. The session returned can be used immediately for
+ * sending data down, without requiring one to open the new active file.
+ *
+ * If you open the active flash image or active hash it will let you
+ * overwrite pieces, depending on the state.
+ *
+ * Once the verification process has started the active files cannot be
+ * opened.
+ *
+ * You can only have one open session at a time. Which means, you can only
+ * have one file open at a time. Trying to open the hash blob_id while you
+ * still have the flash image blob_id open will fail. Opening the flash
+ * blob_id when it is already open will fail.
+ */
+bool FirmwareBlobHandler::open(uint16_t session, uint16_t flags,
+ const std::string& path)
+{
+ /* Is there an open session already? We only allow one at a time.
+ *
+ * Further on this, if there's an active session to the hash we don't allow
+ * re-opening the image, and if we have the image open, we don't allow
+ * opening the hash. This design decision may be re-evaluated, and changed
+ * to only allow one session per object type (of the two types). But,
+ * consider if the hash is open, do we want to allow writing to the image?
+ * And why would we? But, really, the point of no-return is once the
+ * verification process has begun -- which is done via commit() on the hash
+ * blob_id, we no longer want to allow updating the contents.
+ */
+ if (fileOpen())
+ {
+ return false;
+ }
+
+ /* The active blobs are only meant to indicate status that something has
+ * opened the image file or the hash file.
+ */
+ if (path == activeImageBlobId || path == activeHashBlobId)
+ {
+ /* 2a) are they opening the active image? this can only happen if they
+ * already started one (due to canHandleBlob's behavior).
+ */
+ /* 2b) are they opening the active hash? this can only happen if they
+ * already started one (due to canHandleBlob's behavior).
+ */
+ return false;
+ }
+
+ /* Check that they've opened for writing - read back not currently
+ * supported.
+ */
+ if ((flags & blobs::OpenFlags::write) == 0)
+ {
+ return false;
+ }
+
+ /* Because canHandleBlob is called before open, we know that if they try to
+ * open the verifyBlobId, they're in a state where it's present.
+ */
+
+ switch (state)
+ {
+ case UpdateState::notYetStarted:
+ /* Only hashBlobId and firmware BlobIds present. */
+ break;
+ case UpdateState::uploadInProgress:
+ /* Unreachable code because if it's started a file is open. */
+ break;
+ case UpdateState::verificationPending:
+ /* Handle opening the verifyBlobId --> we know the image and hash
+ * aren't open because of the fileOpen() check. They can still open
+ * other files from this state to transition back into
+ * uploadInProgress.
+ *
+ * The file must be opened for writing, but no transport mechanism
+ * specified since it's irrelevant.
+ */
+ if (path == verifyBlobId)
+ {
+ verifyImage.flags = flags;
+
+ lookup[session] = &verifyImage;
+
+ return true;
+ }
+ break;
+ case UpdateState::verificationStarted:
+ case UpdateState::verificationCompleted:
+ /* Unreachable code because if it's started a file is open. */
+ return false;
+ case UpdateState::updatePending:
+ {
+ /* When in this state, they can only open the updateBlobId */
+ if (path == updateBlobId)
+ {
+ updateImage.flags = flags;
+
+ lookup[session] = &updateImage;
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ case UpdateState::updateStarted:
+ case UpdateState::updateCompleted:
+ /* Unreachable code because if it's started a file is open. */
+ break;
+ default:
+ break;
+ }
+
+ /* To support multiple firmware options, we need to make sure they're
+ * opening the one they already opened during this update sequence, or it's
+ * the first time they're opening it.
+ */
+ if (path != hashBlobId)
+ {
+ /* If they're not opening the hashBlobId they must be opening a firmware
+ * handler.
+ */
+ if (openedFirmwareType.empty())
+ {
+ /* First time for this sequence. */
+ openedFirmwareType = path;
+ }
+ else
+ {
+ if (openedFirmwareType != path)
+ {
+ /* Previously, in this sequence they opened /flash/image, and
+ * now they're opening /flash/bios without finishing out
+ * /flash/image (for example).
+ */
+ std::fprintf(stderr, "Trying to open alternate firmware while "
+ "unfinished with other firmware.\n");
+ return false;
+ }
+ }
+ }
+
+ /* There are two abstractions at play, how you get the data and how you
+ * handle that data. such that, whether the data comes from the PCI bridge
+ * or LPC bridge is not connected to whether the data goes into a static
+ * layout flash update or a UBI tarball.
+ */
+
+ std::uint16_t transportFlag = flags & transportMask;
+
+ /* How are they expecting to copy this data? */
+ auto d = std::find_if(transports.begin(), transports.end(),
+ [&transportFlag](const auto& iter) {
+ return (iter.bitmask == transportFlag);
+ });
+ if (d == transports.end())
+ {
+ return false;
+ }
+
+ /* We found the transport handler they requested */
+
+ /* Elsewhere I do this check by checking "if ::ipmi" because that's the
+ * only non-external data pathway -- but this is just a more generic
+ * approach to that.
+ */
+ if (d->handler)
+ {
+ /* If the data handler open call fails, open fails. */
+ if (!d->handler->open())
+ {
+ return false;
+ }
+ }
+
+ /* Do we have a file handler for the type of file they're opening.
+ * Note: This should only fail if something is somehow crazy wrong.
+ * Since the canHandle() said yes, and that's tied into the list of explicit
+ * firmware handers (and file handlers, like this'll know where to write the
+ * tarball, etc).
+ */
+ auto h = std::find_if(
+ handlers.begin(), handlers.end(),
+ [&path](const auto& iter) { return (iter.blobName == path); });
+ if (h == handlers.end())
+ {
+ return false;
+ }
+
+ /* Ok, so we found a handler that matched, so call open() */
+ if (!h->handler->open(path))
+ {
+ return false;
+ }
+
+ Session* curr;
+ const char* active;
+
+ if (path == hashBlobId)
+ {
+ /* 2c) are they opening the /flash/hash ? (to start the process) */
+ curr = &activeHash;
+ active = activeHashBlobId;
+ }
+ else
+ {
+ curr = &activeImage;
+ active = activeImageBlobId;
+ }
+
+ curr->flags = flags;
+ curr->dataHandler = d->handler.get();
+ curr->imageHandler = h->handler.get();
+
+ lookup[session] = curr;
+
+ addBlobId(active);
+ removeBlobId(verifyBlobId);
+
+ changeState(UpdateState::uploadInProgress);
+
+ return true;
+}
+
+/**
+ * The write command really just grabs the data from wherever it is and sends it
+ * to the image handler. It's the image handler's responsibility to deal with
+ * the data provided.
+ *
+ * This receives a session from the blob manager, therefore it is always called
+ * between open() and close().
+ */
+bool FirmwareBlobHandler::write(uint16_t session, uint32_t offset,
+ const std::vector<uint8_t>& data)
+{
+ auto item = lookup.find(session);
+ if (item == lookup.end())
+ {
+ return false;
+ }
+
+ /* Prevent writing during verification. */
+ if (state == UpdateState::verificationStarted)
+ {
+ return false;
+ }
+
+ /* Prevent writing to the verification or update blobs. */
+ if (item->second->activePath == verifyBlobId ||
+ item->second->activePath == updateBlobId)
+ {
+ return false;
+ }
+
+ std::vector<std::uint8_t> bytes;
+
+ if (item->second->flags & FirmwareFlags::UpdateFlags::ipmi)
+ {
+ bytes = data;
+ }
+ else
+ {
+ /* little endian required per design, and so on, but TODO: do endianness
+ * with boost.
+ */
+ struct ExtChunkHdr header;
+
+ if (data.size() != sizeof(header))
+ {
+ return false;
+ }
+
+ std::memcpy(&header, data.data(), data.size());
+ bytes = item->second->dataHandler->copyFrom(header.length);
+ }
+
+ return item->second->imageHandler->write(offset, bytes);
+}
+
+/*
+ * If the active session (image or hash) is over LPC, this allows
+ * configuring it. This option is only available before you start
+ * writing data for the given item (image or hash). This will return
+ * false at any other part. -- the lpc handler portion will know to return
+ * false.
+ */
+bool FirmwareBlobHandler::writeMeta(uint16_t session, uint32_t offset,
+ const std::vector<uint8_t>& data)
+{
+ auto item = lookup.find(session);
+ if (item == lookup.end())
+ {
+ return false;
+ }
+
+ if (item->second->flags & FirmwareFlags::UpdateFlags::ipmi)
+ {
+ return false;
+ }
+
+ /* Prevent writing meta to the verification blob (it has no data handler).
+ */
+ if (item->second->dataHandler)
+ {
+ return item->second->dataHandler->writeMeta(data);
+ }
+
+ return false;
+}
+
+/*
+ * If this command is called on the session for the verifyBlobId, it'll
+ * trigger a systemd service `verify_image.service` to attempt to verify
+ * the image.
+ *
+ * For this file to have opened, the other two must be closed, which means any
+ * out-of-band transport mechanism involved is closed.
+ */
+bool FirmwareBlobHandler::commit(uint16_t session,
+ const std::vector<uint8_t>& data)
+{
+ auto item = lookup.find(session);
+ if (item == lookup.end())
+ {
+ return false;
+ }
+
+ /* You can only commit on the verifyBlodId or updateBlobId */
+ if (item->second->activePath != verifyBlobId &&
+ item->second->activePath != updateBlobId)
+ {
+ std::fprintf(stderr, "path: '%s' not expected for commit\n",
+ item->second->activePath.c_str());
+ return false;
+ }
+
+ switch (state)
+ {
+ case UpdateState::verificationPending:
+ /* Set state to committing. */
+ item->second->flags |= blobs::StateFlags::committing;
+ return triggerVerification();
+ case UpdateState::verificationStarted:
+ /* Calling repeatedly has no effect within an update process. */
+ return true;
+ case UpdateState::verificationCompleted:
+ /* Calling after the verification process has completed returns
+ * failure. */
+ return false;
+ case UpdateState::updatePending:
+ item->second->flags |= blobs::StateFlags::committing;
+ return triggerUpdate();
+ case UpdateState::updateStarted:
+ /* Calling repeatedly has no effect within an update process. */
+ return true;
+ default:
+ return false;
+ }
+}
+
+/*
+ * Close must be called on the firmware image before triggering
+ * verification via commit. Once the verification is complete, you can
+ * then close the hash file.
+ *
+ * If the `verify_image.service` returned success, closing the hash file
+ * will have a specific behavior depending on the update. If it's UBI,
+ * it'll perform the install. If it's static layout, it'll do nothing. The
+ * verify_image service in the static layout case is responsible for placing
+ * the file in the correct staging position.
+ */
+bool FirmwareBlobHandler::close(uint16_t session)
+{
+ auto item = lookup.find(session);
+ if (item == lookup.end())
+ {
+ return false;
+ }
+
+ switch (state)
+ {
+ case UpdateState::uploadInProgress:
+ /* They are closing a data pathway (image, tarball, hash). */
+ changeState(UpdateState::verificationPending);
+
+ /* Add verify blob ID now that we can expect it, IIF they also wrote
+ * some data.
+ */
+ if (std::count(blobIDs.begin(), blobIDs.end(), activeImageBlobId))
+ {
+ addBlobId(verifyBlobId);
+ }
+ break;
+ case UpdateState::verificationPending:
+ /* They haven't triggered, therefore closing is uninteresting.
+ */
+ break;
+ case UpdateState::verificationStarted:
+ /* Abort without checking to see if it happened to finish. Require
+ * the caller to stat() deliberately.
+ */
+ abortVerification();
+ abortProcess();
+ break;
+ case UpdateState::verificationCompleted:
+ if (lastVerificationStatus == ActionStatus::success)
+ {
+ changeState(UpdateState::updatePending);
+ addBlobId(updateBlobId);
+ removeBlobId(verifyBlobId);
+ }
+ else
+ {
+ /* Verification failed, and the host-tool knows this by calling
+ * stat(), which triggered the state change to
+ * verificationCompleted.
+ *
+ * Therefore, let's abort the process at this point.
+ */
+ abortProcess();
+ }
+ break;
+ case UpdateState::updatePending:
+ /* They haven't triggered the update, therefore this is
+ * uninteresting. */
+ break;
+ case UpdateState::updateStarted:
+ /* Abort without checking to see if it happened to finish. Require
+ * the caller to stat() deliberately.
+ */
+ abortUpdate();
+ abortProcess();
+ break;
+ case UpdateState::updateCompleted:
+ if (lastUpdateStatus == ActionStatus::failed)
+ {
+ /* TODO: lOG something? */
+ std::fprintf(stderr, "Update failed\n");
+ }
+
+ abortProcess();
+ break;
+ default:
+ break;
+ }
+
+ if (!lookup.empty())
+ {
+ if (item->second->dataHandler)
+ {
+ item->second->dataHandler->close();
+ }
+ if (item->second->imageHandler)
+ {
+ item->second->imageHandler->close();
+ }
+ lookup.erase(item);
+ }
+ return true;
+}
+
+void FirmwareBlobHandler::changeState(UpdateState next)
+{
+ state = next;
+
+ if (state == UpdateState::notYetStarted)
+ {
+ /* Going back to notyetstarted, let them trigger preparation again. */
+ preparationTriggered = false;
+ }
+ else if (state == UpdateState::uploadInProgress)
+ {
+ /* Store this transition logic here instead of ::open() */
+ if (!preparationTriggered)
+ {
+ auto* pack = getActionPack();
+ if (pack)
+ {
+ pack->preparation->trigger();
+ preparationTriggered = true;
+ }
+ }
+ }
+}
+
+bool FirmwareBlobHandler::expire(uint16_t session)
+{
+ abortProcess();
+ return true;
+}
+
+/*
+ * Currently, the design does not provide this with a function, however,
+ * it will likely change to support reading data back.
+ */
+std::vector<uint8_t> FirmwareBlobHandler::read(uint16_t session,
+ uint32_t offset,
+ uint32_t requestedSize)
+{
+ return {};
+}
+
+void FirmwareBlobHandler::abortProcess()
+{
+ /* Closing of open files is handled from close() -- Reaching here from
+ * delete may never be supported.
+ */
+ removeBlobId(verifyBlobId);
+ removeBlobId(updateBlobId);
+ removeBlobId(activeImageBlobId);
+ removeBlobId(activeHashBlobId);
+
+ for (auto item : lookup)
+ {
+ if (item.second->dataHandler)
+ {
+ item.second->dataHandler->close();
+ }
+ if (item.second->imageHandler)
+ {
+ item.second->imageHandler->close();
+ }
+ }
+ lookup.clear();
+
+ openedFirmwareType = "";
+ changeState(UpdateState::notYetStarted);
+}
+
+void FirmwareBlobHandler::abortVerification()
+{
+ auto* pack = getActionPack();
+ if (pack)
+ {
+ pack->verification->abort();
+ }
+}
+
+bool FirmwareBlobHandler::triggerVerification()
+{
+ auto* pack = getActionPack();
+ if (!pack)
+ {
+ return false;
+ }
+
+ bool result = pack->verification->trigger();
+ if (result)
+ {
+ changeState(UpdateState::verificationStarted);
+ }
+
+ return result;
+}
+
+void FirmwareBlobHandler::abortUpdate()
+{
+ auto* pack = getActionPack();
+ if (pack)
+ {
+ pack->update->abort();
+ }
+}
+
+bool FirmwareBlobHandler::triggerUpdate()
+{
+ auto* pack = getActionPack();
+ if (!pack)
+ {
+ return false;
+ }
+
+ bool result = pack->update->trigger();
+ if (result)
+ {
+ changeState(UpdateState::updateStarted);
+ }
+
+ return result;
+}
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/firmware_handler.hpp b/bmc/firmware-handler/firmware_handler.hpp
new file mode 100644
index 0000000..505f1a7
--- /dev/null
+++ b/bmc/firmware-handler/firmware_handler.hpp
@@ -0,0 +1,264 @@
+#pragma once
+
+#include "config.h"
+
+#include "data_handler.hpp"
+#include "image_handler.hpp"
+#include "status.hpp"
+#include "util.hpp"
+
+#include <blobs-ipmid/blobs.hpp>
+
+#include <algorithm>
+#include <cstdint>
+#include <map>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace ipmi_flash
+{
+
+/**
+ * Given a firmware name, provide a set of triggerable action interfaces
+ * associated with that firmware type.
+ */
+struct ActionPack
+{
+ /** The name of the action pack, something like image, or tarball, or bios.
+ * The firmware blob id is parsed to pull the "filename" portion from the
+ * path, and matched against the key to a map of these.
+ */
+ std::unique_ptr<TriggerableActionInterface> preparation;
+ std::unique_ptr<TriggerableActionInterface> verification;
+ std::unique_ptr<TriggerableActionInterface> update;
+};
+
+using ActionMap =
+ std::unordered_map<std::string, std::unique_ptr<ipmi_flash::ActionPack>>;
+
+/**
+ * Representation of a session, includes how to read/write data.
+ */
+struct Session
+{
+ /**
+ * Built a session object.
+ *
+ * @param[in] the active path to which this corresponds.
+ */
+ explicit Session(const std::string& path) :
+ dataHandler(nullptr), imageHandler(nullptr), flags(0), activePath(path)
+ {}
+
+ /**
+ * Pointer to the correct Data handler interface. (nullptr on BT (or KCS))
+ */
+ DataInterface* dataHandler;
+
+ /**
+ * Pointer to the correct image handler interface. (nullptr on hash
+ * blob_id)
+ */
+ ipmi_flash::ImageHandlerInterface* imageHandler;
+
+ /** The flags used to open the session. */
+ std::uint16_t flags;
+
+ /** The active path. */
+ std::string activePath;
+};
+
+/**
+ * Register only one firmware blob handler that will manage all sessions.
+ */
+class FirmwareBlobHandler : public blobs::GenericBlobInterface
+{
+ public:
+ /** The state of the firmware update process. */
+ enum class UpdateState
+ {
+ /** The initial state. */
+ notYetStarted = 0,
+ /** The BMC is expecting to receive bytes. */
+ uploadInProgress,
+ /** The BMC is ready for verification or more bytes. */
+ verificationPending,
+ /** The verification process has started, no more writes allowed. */
+ verificationStarted,
+ /** The verification process has completed. */
+ verificationCompleted,
+ /** The update process is pending. */
+ updatePending,
+ /** The update process has started. */
+ updateStarted,
+ /** The update has completed (optional state to reach) */
+ updateCompleted,
+ };
+
+ /**
+ * Create a FirmwareBlobHandler.
+ *
+ * @param[in] firmwares - list of firmware blob_ids to support.
+ * @param[in] transports - list of transports to support.
+ * @param[in] verification - pointer to object for triggering verification
+ * @param[in] update - point to object for triggering the update
+ */
+ static std::unique_ptr<blobs::GenericBlobInterface>
+ CreateFirmwareBlobHandler(std::vector<HandlerPack>&& firmwares,
+ std::vector<DataHandlerPack>&& transports,
+ ActionMap&& actionPacks);
+
+ /**
+ * Create a FirmwareBlobHandler.
+ *
+ * @param[in] firmwares - list of firmware types and their handlers
+ * @param[in] blobs - list of blobs_ids to support
+ * @param[in] transports - list of transport types and their handlers
+ * @param[in] verification - pointer to object for triggering verification
+ * @param[in] update - point to object for triggering the update
+ */
+ FirmwareBlobHandler(std::vector<HandlerPack>&& firmwares,
+ const std::vector<std::string>& blobs,
+ std::vector<DataHandlerPack>&& transports,
+ ActionMap&& actionPacks) :
+ handlers(std::move(firmwares)),
+ blobIDs(blobs), transports(std::move(transports)),
+ activeImage(activeImageBlobId), activeHash(activeHashBlobId),
+ verifyImage(verifyBlobId), updateImage(updateBlobId), lookup(),
+ state(UpdateState::notYetStarted), actionPacks(std::move(actionPacks))
+ {}
+ ~FirmwareBlobHandler() = default;
+ FirmwareBlobHandler(const FirmwareBlobHandler&) = delete;
+ FirmwareBlobHandler& operator=(const FirmwareBlobHandler&) = delete;
+ FirmwareBlobHandler(FirmwareBlobHandler&&) = default;
+ FirmwareBlobHandler& operator=(FirmwareBlobHandler&&) = default;
+
+ bool canHandleBlob(const std::string& path) override;
+ std::vector<std::string> getBlobIds() override;
+ bool deleteBlob(const std::string& path) override;
+ bool stat(const std::string& path, blobs::BlobMeta* meta) override;
+ bool open(uint16_t session, uint16_t flags,
+ const std::string& path) override;
+ std::vector<uint8_t> read(uint16_t session, uint32_t offset,
+ uint32_t requestedSize) override;
+ bool write(uint16_t session, uint32_t offset,
+ const std::vector<uint8_t>& data) override;
+ bool writeMeta(uint16_t session, uint32_t offset,
+ const std::vector<uint8_t>& data) override;
+ bool commit(uint16_t session, const std::vector<uint8_t>& data) override;
+ bool close(uint16_t session) override;
+ bool stat(uint16_t session, blobs::BlobMeta* meta) override;
+ bool expire(uint16_t session) override;
+
+ void abortProcess();
+
+ void abortVerification();
+ bool triggerVerification();
+ void abortUpdate();
+ bool triggerUpdate();
+
+ /** Allow grabbing the current state. */
+ UpdateState getCurrentState() const
+ {
+ return state;
+ };
+
+ /** Provide for any state change triggers in convenience handler. */
+ void changeState(UpdateState next);
+
+ private:
+ /**
+ * Given the current session type, grab the ActionPack (likely will be
+ * worked into the Session for lookup).
+ */
+ ActionPack* getActionPack()
+ {
+ if (openedFirmwareType.empty())
+ {
+ /* No firmware type has been opened, but we're triggering
+ * verification, or preparing. This can happen if they open the hash
+ * before the image, which is possible.
+ */
+ return nullptr;
+ }
+
+ /* TODO: Once the actionPacks and supportedFirmwares are merged this'll
+ * be less dangerous
+ */
+ return actionPacks[openedFirmwareType].get();
+ }
+
+ void addBlobId(const std::string& blob)
+ {
+ auto blobIdMatch = std::find_if(
+ blobIDs.begin(), blobIDs.end(),
+ [&blob](const std::string& iter) { return (iter == blob); });
+ if (blobIdMatch == blobIDs.end())
+ {
+ blobIDs.push_back(blob);
+ }
+ }
+
+ void removeBlobId(const std::string& blob)
+ {
+ blobIDs.erase(std::remove(blobIDs.begin(), blobIDs.end(), blob),
+ blobIDs.end());
+ }
+
+ inline bool fileOpen()
+ {
+ return !lookup.empty();
+ }
+
+ ActionStatus getVerifyStatus();
+ ActionStatus getActionStatus();
+
+ /** List of handlers by type. */
+ std::vector<HandlerPack> handlers;
+
+ /** Active list of blobIDs. */
+ std::vector<std::string> blobIDs;
+
+ /** List of handlers by transport type. */
+ std::vector<DataHandlerPack> transports;
+
+ /** Active image session. */
+ Session activeImage;
+
+ /** Active hash session. */
+ Session activeHash;
+
+ /** Session for verification. */
+ Session verifyImage;
+
+ /** Session for update. */
+ Session updateImage;
+
+ /** A quick method for looking up a session's mechanisms and details. */
+ std::map<std::uint16_t, Session*> lookup;
+
+ /** The firmware update state. */
+ UpdateState state;
+
+ /** Track what firmware blobid they opened to start this sequence. */
+ std::string openedFirmwareType;
+
+ /* preparation is triggered once we go into uploadInProgress(), but only
+ * once per full cycle, going back to notYetStarted resets this.
+ */
+ bool preparationTriggered = false;
+ ActionMap actionPacks;
+
+ ActionStatus lastVerificationStatus = ActionStatus::unknown;
+
+ ActionStatus lastUpdateStatus = ActionStatus::unknown;
+
+ /** Portion of "flags" argument to open() which specifies the desired
+ * transport type
+ */
+ static constexpr std::uint16_t transportMask = 0xff00;
+};
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/lpc_aspeed.cpp b/bmc/firmware-handler/lpc_aspeed.cpp
new file mode 100644
index 0000000..364aff5
--- /dev/null
+++ b/bmc/firmware-handler/lpc_aspeed.cpp
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "lpc_aspeed.hpp"
+
+#include "mapper_errors.hpp"
+#include "window_hw_interface.hpp"
+
+#include <fcntl.h>
+#include <linux/aspeed-lpc-ctrl.h>
+#include <linux/kernel.h>
+
+#include <cerrno>
+#include <cstdint>
+#include <cstring>
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace ipmi_flash
+{
+
+const std::string LpcMapperAspeed::lpcControlPath = "/dev/aspeed-lpc-ctrl";
+
+std::unique_ptr<HardwareMapperInterface>
+ LpcMapperAspeed::createAspeedMapper(std::uint32_t regionAddress,
+ std::size_t regionSize)
+{
+ /* NOTE: considered using a joint factory to create one or the other, for
+ * now, separate factories.
+ */
+ return std::make_unique<LpcMapperAspeed>(regionAddress, regionSize);
+}
+
+void LpcMapperAspeed::close()
+{
+ if (mappedRegion)
+ {
+ sys->munmap(mappedRegion, regionSize);
+ mappedRegion = nullptr;
+ }
+
+ if (mappedFd != -1)
+ {
+ sys->close(mappedFd);
+ mappedFd = -1;
+ }
+}
+
+WindowMapResult LpcMapperAspeed::mapWindow(std::uint32_t address,
+ std::uint32_t length)
+{
+ WindowMapResult result = {};
+ static const std::uint32_t MASK_64K = 0xFFFFU;
+ const std::uint32_t offset = address & MASK_64K;
+
+ if (offset + length > regionSize)
+ {
+ std::fprintf(stderr,
+ "requested window size %" PRIu32 ", offset %#" PRIx32
+ " is too large for mem region"
+ " of size %zu\n",
+ length, offset, regionSize);
+
+ result.response = EFBIG;
+ result.windowSize = regionSize - offset;
+ return result;
+ }
+
+ struct aspeed_lpc_ctrl_mapping map = {
+ .window_type = ASPEED_LPC_CTRL_WINDOW_MEMORY,
+ .window_id = 0,
+ .flags = 0,
+ .addr = address & ~MASK_64K,
+ .offset = 0,
+ .size = __ALIGN_KERNEL_MASK(offset + length, MASK_64K),
+ };
+
+ std::fprintf(stderr,
+ "requesting Aspeed LPC window at %#" PRIx32 " of size %" PRIu32
+ "\n",
+ map.addr, map.size);
+
+ const auto lpcControlFd = sys->open(lpcControlPath.c_str(), O_RDWR);
+ if (lpcControlFd == -1)
+ {
+ std::fprintf(stderr,
+ "cannot open Aspeed LPC kernel control dev \"%s\"\n",
+ lpcControlPath.c_str());
+
+ result.response = EINVAL;
+ return result;
+ }
+
+ if (sys->ioctl(lpcControlFd, ASPEED_LPC_CTRL_IOCTL_MAP, &map) == -1)
+ {
+ std::fprintf(stderr, "Failed to ioctl Aspeed LPC map with error %s\n",
+ std::strerror(errno));
+ sys->close(lpcControlFd);
+
+ result.response = EINVAL;
+ return result;
+ }
+
+ sys->close(lpcControlFd);
+
+ result.response = 0;
+ result.windowOffset = offset;
+ result.windowSize = length;
+ return result;
+}
+
+MemorySet LpcMapperAspeed::open()
+{
+ if (mapRegion())
+ {
+ MemorySet output;
+ output.mappedFd = mappedFd;
+ output.mapped = mappedRegion;
+ return output;
+ }
+
+ throw MapperException("Unable to memory-map region");
+}
+
+bool LpcMapperAspeed::mapRegion()
+{
+ /* Open the file to map. */
+ mappedFd = sys->open(lpcControlPath.c_str(), O_RDONLY | O_SYNC);
+ if (mappedFd == -1)
+ {
+ std::fprintf(stderr, "ipmiflash: unable to open %s\n",
+ lpcControlPath.c_str());
+ return false;
+ }
+
+ mappedRegion = reinterpret_cast<uint8_t*>(
+ sys->mmap(0, regionSize, PROT_READ, MAP_SHARED, mappedFd, 0));
+
+ if (mappedRegion == MAP_FAILED)
+ {
+ sys->close(mappedFd);
+ mappedFd = -1;
+ std::fprintf(stderr, "Mmap failure: '%s'\n", std::strerror(errno));
+ return false;
+ }
+
+ /* TOOD: There is no close() method here, to close mappedFd, or mappedRegion
+ * -- therefore, a good next step will be to evaluate whether or not the
+ * other pieces should go here...
+ */
+ return true;
+}
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/lpc_aspeed.hpp b/bmc/firmware-handler/lpc_aspeed.hpp
new file mode 100644
index 0000000..ac87c22
--- /dev/null
+++ b/bmc/firmware-handler/lpc_aspeed.hpp
@@ -0,0 +1,58 @@
+#pragma once
+
+#include "internal/sys.hpp"
+#include "window_hw_interface.hpp"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace ipmi_flash
+{
+
+class LpcMapperAspeed : public HardwareMapperInterface
+{
+ public:
+ static std::unique_ptr<HardwareMapperInterface>
+ createAspeedMapper(std::uint32_t regionAddress, std::size_t regionSize);
+
+ /* NOTE: This object is created and then never destroyed (unless ipmid
+ * stops/crashes, etc)
+ */
+ LpcMapperAspeed(std::uint32_t regionAddress, std::size_t regionSize,
+ const internal::Sys* sys = &internal::sys_impl) :
+ regionAddress(regionAddress),
+ regionSize(regionSize), sys(sys){};
+
+ LpcMapperAspeed(const LpcMapperAspeed&) = delete;
+ LpcMapperAspeed& operator=(const LpcMapperAspeed&) = delete;
+ LpcMapperAspeed(LpcMapperAspeed&&) = default;
+ LpcMapperAspeed& operator=(LpcMapperAspeed&&) = default;
+
+ /* throws MapperException */
+ MemorySet open() override;
+
+ void close() override;
+
+ WindowMapResult mapWindow(std::uint32_t address,
+ std::uint32_t length) override;
+
+ /**
+ * Attempt to mmap the region.
+ *
+ * @return true on success, false otherwise.
+ */
+ bool mapRegion();
+
+ private:
+ static const std::string lpcControlPath;
+ int mappedFd = -1;
+ std::uint8_t* mappedRegion = nullptr;
+ std::uint32_t regionAddress;
+ std::size_t regionSize;
+ const internal::Sys* sys;
+};
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/lpc_handler.cpp b/bmc/firmware-handler/lpc_handler.cpp
new file mode 100644
index 0000000..e3a5e08
--- /dev/null
+++ b/bmc/firmware-handler/lpc_handler.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "lpc_handler.hpp"
+
+#include <cstdint>
+#include <cstring>
+#include <vector>
+
+namespace ipmi_flash
+{
+
+bool LpcDataHandler::open()
+{
+ /* For the ASPEED LPC CTRL driver, the ioctl is required to set up the
+ * window, with information from writeMeta() below.
+ */
+ return true;
+}
+
+bool LpcDataHandler::close()
+{
+ mapper->close();
+
+ return setInitializedAndReturn(false);
+}
+
+std::vector<std::uint8_t> LpcDataHandler::copyFrom(std::uint32_t length)
+{
+ /* TODO: implement this -- in an earlier and different version of this that
+ * didn't use BLOBs, the region was memory-mapped and the writes to the data
+ * were just done directly from the memory-mapped region instead of a
+ * copyFrom() first call. The idea with this change is that we may not be
+ * able to get a memory-mapped handle from the driver from which to
+ * automatically read data, but rather must perform some ioctl or other
+ * access to get the data from the driver.
+ */
+ if (!initialized)
+ {
+ /* TODO: Consider designing some exceptions we can catch for when there
+ * is an error.
+ */
+ return {};
+ }
+
+ std::vector<std::uint8_t> results(length);
+ std::memcpy(results.data(), memory.mapped + mappingResult.windowOffset,
+ length);
+
+ return results;
+}
+
+bool LpcDataHandler::writeMeta(const std::vector<std::uint8_t>& configuration)
+{
+ struct LpcRegion lpcRegion;
+
+ if (configuration.size() != sizeof(lpcRegion))
+ {
+ return false;
+ }
+
+ std::memcpy(&lpcRegion, configuration.data(), configuration.size());
+
+ /* TODO: LpcRegion sanity checking. */
+ mappingResult = mapper->mapWindow(lpcRegion.address, lpcRegion.length);
+ if (mappingResult.response != 0)
+ {
+ std::fprintf(stderr, "mappingResult.response %u\n",
+ mappingResult.response);
+ /* Failed to map region. */
+ return false;
+ }
+
+ return setInitializedAndReturn(true);
+}
+
+std::vector<std::uint8_t> LpcDataHandler::readMeta()
+{
+ /* Return the MemoryResult structure packed. */
+ std::vector<std::uint8_t> output(
+ sizeof(std::uint8_t) + sizeof(std::uint32_t) + sizeof(std::uint32_t));
+
+ int index = 0;
+ std::memcpy(&output[index], &mappingResult.response,
+ sizeof(mappingResult.response));
+
+ index += sizeof(mappingResult.response);
+ std::memcpy(&output[index], &mappingResult.windowOffset,
+ sizeof(mappingResult.windowOffset));
+
+ index += sizeof(mappingResult.windowOffset);
+ std::memcpy(&output[index], &mappingResult.windowSize,
+ sizeof(mappingResult.windowSize));
+
+ return output;
+}
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/lpc_handler.hpp b/bmc/firmware-handler/lpc_handler.hpp
new file mode 100644
index 0000000..cc04433
--- /dev/null
+++ b/bmc/firmware-handler/lpc_handler.hpp
@@ -0,0 +1,81 @@
+#pragma once
+
+#include "data_handler.hpp"
+#include "mapper_errors.hpp"
+#include "window_hw_interface.hpp"
+
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+namespace ipmi_flash
+{
+
+struct LpcRegion
+{
+ /* Host LPC address where the chunk is to be mapped. */
+ std::uint32_t address;
+
+ /* Size of the chunk to be mapped. */
+ std::uint32_t length;
+} __attribute__((packed));
+
+/**
+ * Data Handler for configuration the ASPEED LPC memory region, reading and
+ * writing data.
+ */
+class LpcDataHandler : public DataInterface
+{
+ public:
+ /**
+ * Create an LpcDataHandler.
+ *
+ * @param[in] mapper - pointer to a mapper implementation to use.
+ */
+ explicit LpcDataHandler(std::unique_ptr<HardwareMapperInterface> mapper) :
+ mapper(std::move(mapper)), initialized(false)
+ {}
+
+ bool open() override;
+ bool close() override;
+ std::vector<std::uint8_t> copyFrom(std::uint32_t length) override;
+ bool writeMeta(const std::vector<std::uint8_t>& configuration) override;
+ std::vector<std::uint8_t> readMeta() override;
+
+ private:
+ bool setInitializedAndReturn(bool value)
+ {
+ if (value)
+ {
+ try
+ {
+ /* Try really opening the map. */
+ memory = mapper->open();
+ }
+ catch (const MapperException& e)
+ {
+ std::fprintf(stderr, "received mapper exception: %s\n",
+ e.what());
+ return false;
+ }
+ }
+
+ initialized = value;
+ return value;
+ }
+
+ std::unique_ptr<HardwareMapperInterface> mapper;
+ bool initialized;
+ /* The LPC Handler does not take ownership of this, in case there's cleanup
+ * required for close()
+ */
+ MemorySet memory = {};
+
+ /* Offset in reserved memory at which host data arrives. */
+ /* Size of the chunk of the memory region in use by the host (e.g.
+ * mapped over external block mechanism).
+ */
+ WindowMapResult mappingResult = {};
+};
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/lpc_nuvoton.cpp b/bmc/firmware-handler/lpc_nuvoton.cpp
new file mode 100644
index 0000000..eaa316f
--- /dev/null
+++ b/bmc/firmware-handler/lpc_nuvoton.cpp
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "lpc_nuvoton.hpp"
+
+#include "mapper_errors.hpp"
+#include "window_hw_interface.hpp"
+
+#include <fcntl.h>
+#include <sys/mman.h>
+
+#include <cerrno>
+#include <cinttypes>
+#include <cstdint>
+#include <cstdio>
+#include <memory>
+#include <utility>
+#include <vector>
+
+namespace ipmi_flash
+{
+using std::uint16_t;
+using std::uint32_t;
+using std::uint8_t;
+
+std::unique_ptr<HardwareMapperInterface>
+ LpcMapperNuvoton::createNuvotonMapper(std::uint32_t regionAddress,
+ std::uint32_t regionSize)
+{
+ /* NOTE: Considered making one factory for both types. */
+ return std::make_unique<LpcMapperNuvoton>(regionAddress, regionSize);
+}
+
+MemorySet LpcMapperNuvoton::open()
+{
+ static constexpr auto devmem = "/dev/mem";
+
+ mappedFd = sys->open(devmem, O_RDWR | O_SYNC);
+ if (mappedFd == -1)
+ {
+ throw MapperException("Unable to open /dev/mem");
+ }
+
+ mapped = reinterpret_cast<uint8_t*>(sys->mmap(
+ 0, memoryRegionSize, PROT_READ, MAP_SHARED, mappedFd, regionAddress));
+ if (mapped == MAP_FAILED)
+ {
+ sys->close(mappedFd);
+ mappedFd = -1;
+ mapped = nullptr;
+
+ throw MapperException("Unable to map region");
+ }
+
+ MemorySet output;
+ output.mappedFd = mappedFd;
+ output.mapped = mapped;
+
+ return output;
+}
+
+void LpcMapperNuvoton::close()
+{
+ if (mapped)
+ {
+ sys->munmap(mapped, memoryRegionSize);
+ mapped = nullptr;
+ }
+
+ if (mappedFd != -1)
+ {
+ sys->close(mappedFd);
+ mappedFd = -1;
+ }
+}
+
+/*
+ * The host buffer address is configured by host through
+ * SuperIO. On BMC side the max memory can be mapped is 4kB with the caveat that
+ * first byte of the buffer is reserved as host/BMC semaphore and not usable as
+ * shared memory.
+ *
+ * Mapper returns success for (addr, len) where (addr & 0x7) == 4 and len <=
+ * (4096 - 4). Otherwise, mapper returns either
+ * - WindowOffset = 4 and WindowSize = len - 4 if (addr & 0x7) == 0
+ * - WindowSize = 0 means that the region cannot be mapped otherwise
+ */
+WindowMapResult LpcMapperNuvoton::mapWindow(std::uint32_t address,
+ std::uint32_t length)
+{
+ WindowMapResult result = {};
+
+ /* We reserve the first 4 bytes from the mapped region; the first byte
+ * is shared semaphore, and the number of 4 is for alignment.
+ */
+ const uint32_t bmcMapReserveBytes = 4;
+ const uint32_t bmcMapMaxSizeBytes = 4 * 1024 - bmcMapReserveBytes;
+
+ if (length <= bmcMapReserveBytes)
+ {
+ std::fprintf(stderr, "window size %" PRIx32 " too small to map.\n",
+ length);
+ result.response = EINVAL;
+ return result;
+ }
+
+ if (length > bmcMapMaxSizeBytes)
+ {
+ std::fprintf(stderr,
+ "window size %" PRIx32 " not supported. Max size 4k.\n",
+ length);
+ length = bmcMapMaxSizeBytes;
+ }
+
+ /* If host requested region starts at an aligned address, return offset of 4
+ * bytes so as to skip the semaphore register.
+ */
+ uint32_t windowOffset = bmcMapReserveBytes;
+ // uint32_t windowSize = length;
+
+ result.response = 0;
+ result.windowOffset = windowOffset;
+ result.windowSize = length;
+
+ const uint32_t addressOffset = address & 0x7;
+
+ if (addressOffset == 0)
+ {
+ std::fprintf(stderr, "Map address offset should be 4 for Nuvoton.\n");
+
+ result.response = EFBIG;
+ return result;
+ }
+ else if (addressOffset != bmcMapReserveBytes)
+ {
+ std::fprintf(stderr, "Map address offset should be 4 for Nuvoton.\n");
+
+ result.response = EINVAL;
+ return result;
+ }
+
+ /* TODO: need a kernel driver to handle mapping configuration.
+ * Until then program the register through /dev/mem.
+ */
+ int fd;
+ if ((fd = sys->open("/dev/mem", O_RDWR | O_SYNC)) == -1)
+ {
+ std::fprintf(stderr, "Failed to open /dev/mem\n");
+ sys->close(fd);
+
+ result.response = EINVAL;
+ return result;
+ }
+
+ const uint32_t bmcMapConfigBaseAddr = 0xc0001000;
+ const uint32_t bmcMapConfigWindowSizeOffset = 0x7;
+ const uint32_t bmcMapConfigWindowBaseOffset = 0xa;
+ const uint8_t bmcWindowSizeValue = 0xc; // 4k
+ const uint16_t bmcWindowBaseValue = 0x8000; // BMC phyAddr from 0xc0008000
+
+ int pageSize = sys->getpagesize();
+
+ auto mapBasePtr = reinterpret_cast<uint8_t*>(
+ sys->mmap(nullptr, pageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
+ bmcMapConfigBaseAddr));
+
+ uint8_t* bmcWindowSize = mapBasePtr + bmcMapConfigWindowSizeOffset;
+ uint16_t* bmcWindowBase =
+ reinterpret_cast<uint16_t*>(mapBasePtr + bmcMapConfigWindowBaseOffset);
+
+ *bmcWindowSize = bmcWindowSizeValue;
+ *bmcWindowBase = bmcWindowBaseValue;
+
+ sys->munmap(mapBasePtr, pageSize);
+ sys->close(fd);
+
+ return result;
+}
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/lpc_nuvoton.hpp b/bmc/firmware-handler/lpc_nuvoton.hpp
new file mode 100644
index 0000000..c66653b
--- /dev/null
+++ b/bmc/firmware-handler/lpc_nuvoton.hpp
@@ -0,0 +1,56 @@
+#pragma once
+
+#include "internal/sys.hpp"
+#include "window_hw_interface.hpp"
+
+#include <cstdint>
+#include <memory>
+#include <utility>
+#include <vector>
+
+namespace ipmi_flash
+{
+
+class LpcMapperNuvoton : public HardwareMapperInterface
+{
+ public:
+ static std::unique_ptr<HardwareMapperInterface>
+ createNuvotonMapper(std::uint32_t regionAddress,
+ std::uint32_t regionSize);
+
+ /**
+ * Create an LpcMapper for Nuvoton.
+ *
+ * @param[in] regionAddress - where to map the window into BMC memory.
+ * @param[in] regionSize - the size to map for copying data.
+ * @param[in] a sys call interface pointer.
+ * @todo Needs reserved memory region's physical address and size.
+ */
+ LpcMapperNuvoton(std::uint32_t regionAddress, std::uint32_t regionSize,
+ const internal::Sys* sys = &internal::sys_impl) :
+ regionAddress(regionAddress),
+ memoryRegionSize(regionSize), sys(sys){};
+
+ /** Attempt to map the window for copying bytes, after mapWindow is called.
+ * throws MapperException
+ */
+ MemorySet open() override;
+
+ void close() override;
+
+ WindowMapResult mapWindow(std::uint32_t address,
+ std::uint32_t length) override;
+
+ private:
+ std::uint32_t regionAddress;
+ std::uint32_t memoryRegionSize;
+ const internal::Sys* sys;
+
+ /* The file handle to /dev/mem. */
+ int mappedFd = -1;
+
+ /* The pointer to the memory-mapped region. */
+ std::uint8_t* mapped = nullptr;
+};
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/main.cpp b/bmc/firmware-handler/main.cpp
new file mode 100644
index 0000000..eeca589
--- /dev/null
+++ b/bmc/firmware-handler/main.cpp
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+
+#include "buildjson.hpp"
+#include "file_handler.hpp"
+#include "firmware_handler.hpp"
+#include "flags.hpp"
+#include "general_systemd.hpp"
+#include "image_handler.hpp"
+#include "lpc_aspeed.hpp"
+#include "lpc_handler.hpp"
+#include "lpc_nuvoton.hpp"
+#include "net_handler.hpp"
+#include "pci_handler.hpp"
+#include "status.hpp"
+#include "util.hpp"
+
+#include <sdbusplus/bus.hpp>
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace ipmi_flash
+{
+
+namespace
+{
+
+static constexpr const char* jsonConfigurationPath =
+ "/usr/share/phosphor-ipmi-flash/";
+
+/**
+ * Given a name and path, create a HandlerPack.
+ *
+ * @param[in] name - the blob id path for this
+ * @param[in] path - the file path to write the contents.
+ * @return the HandlerPack.
+ */
+HandlerPack CreateFileHandlerPack(const std::string& name,
+ const std::string& path)
+{
+ return HandlerPack(name, std::make_unique<FileHandler>(path));
+}
+
+#ifdef NUVOTON_P2A_MBOX
+static constexpr std::size_t memoryRegionSize = 16 * 1024UL;
+#elif defined NUVOTON_P2A_VGA
+static constexpr std::size_t memoryRegionSize = 4 * 1024 * 1024UL;
+#else
+/* The maximum external buffer size we expect is 64KB. */
+static constexpr std::size_t memoryRegionSize = 64 * 1024UL;
+#endif
+
+} // namespace
+} // namespace ipmi_flash
+
+extern "C"
+{
+ std::unique_ptr<blobs::GenericBlobInterface> createHandler();
+}
+
+std::unique_ptr<blobs::GenericBlobInterface> createHandler()
+{
+ using namespace ipmi_flash;
+
+ std::vector<DataHandlerPack> supportedTransports;
+
+ supportedTransports.emplace_back(FirmwareFlags::UpdateFlags::ipmi, nullptr);
+
+#ifdef ENABLE_PCI_BRIDGE
+ supportedTransports.emplace_back(
+ FirmwareFlags::UpdateFlags::p2a,
+ std::make_unique<PciDataHandler>(MAPPED_ADDRESS, memoryRegionSize));
+#endif
+
+#ifdef ENABLE_LPC_BRIDGE
+#if defined(ASPEED_LPC)
+ supportedTransports.emplace_back(
+ FirmwareFlags::UpdateFlags::lpc,
+ std::make_unique<LpcDataHandler>(LpcMapperAspeed::createAspeedMapper(
+ MAPPED_ADDRESS, memoryRegionSize)));
+#elif defined(NUVOTON_LPC)
+ supportedTransports.emplace_back(
+ FirmwareFlags::UpdateFlags::lpc,
+ std::make_unique<LpcDataHandler>(LpcMapperNuvoton::createNuvotonMapper(
+ MAPPED_ADDRESS, memoryRegionSize)));
+#else
+#error "You must specify a hardware implementation."
+#endif
+#endif
+
+#ifdef ENABLE_NET_BRIDGE
+ supportedTransports.emplace_back(FirmwareFlags::UpdateFlags::net,
+ std::make_unique<NetDataHandler>());
+#endif
+
+ ActionMap actionPacks = {};
+
+ std::vector<HandlerConfig> configsFromJson =
+ BuildHandlerConfigs(jsonConfigurationPath);
+
+ std::vector<HandlerPack> supportedFirmware;
+
+ supportedFirmware.push_back(
+ std::move(CreateFileHandlerPack(hashBlobId, HASH_FILENAME)));
+
+ for (auto& config : configsFromJson)
+ {
+ supportedFirmware.emplace_back(config.blobId,
+ std::move(config.handler));
+ actionPacks[config.blobId] = std::move(config.actions);
+
+ std::fprintf(stderr, "config loaded: %s\n", config.blobId.c_str());
+ }
+
+ auto handler = FirmwareBlobHandler::CreateFirmwareBlobHandler(
+ std::move(supportedFirmware), std::move(supportedTransports),
+ std::move(actionPacks));
+
+ if (!handler)
+ {
+ std::fprintf(stderr, "Firmware Handler has an invalid configuration");
+ return nullptr;
+ }
+
+ return handler;
+}
diff --git a/bmc/firmware-handler/mapper_errors.hpp b/bmc/firmware-handler/mapper_errors.hpp
new file mode 100644
index 0000000..7717f4b
--- /dev/null
+++ b/bmc/firmware-handler/mapper_errors.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <exception>
+#include <string>
+
+namespace ipmi_flash
+{
+
+class MapperException : public std::exception
+{
+ public:
+ explicit MapperException(const std::string& message) : message(message)
+ {}
+
+ virtual const char* what() const noexcept override
+ {
+ return message.c_str();
+ }
+
+ private:
+ std::string message;
+};
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/net_handler.cpp b/bmc/firmware-handler/net_handler.cpp
new file mode 100644
index 0000000..14ae4fb
--- /dev/null
+++ b/bmc/firmware-handler/net_handler.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "net_handler.hpp"
+
+#include <errno.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <cstdio>
+
+namespace ipmi_flash
+{
+
+bool NetDataHandler::open()
+{
+ listenFd.reset(::socket(AF_INET6, SOCK_STREAM, 0));
+ if (*listenFd < 0)
+ {
+ std::perror("Failed to create socket");
+ (void)listenFd.release();
+ return false;
+ }
+
+ struct sockaddr_in6 listenAddr;
+ listenAddr.sin6_family = AF_INET6;
+ listenAddr.sin6_port = htons(listenPort);
+ listenAddr.sin6_flowinfo = 0;
+ listenAddr.sin6_addr = in6addr_any;
+ listenAddr.sin6_scope_id = 0;
+
+ if (::bind(*listenFd, (struct sockaddr*)&listenAddr, sizeof(listenAddr)) <
+ 0)
+ {
+ std::perror("Failed to bind");
+ return false;
+ }
+
+ if (::listen(*listenFd, 1) < 0)
+ {
+ std::perror("Failed to listen");
+ return false;
+ }
+ return true;
+}
+
+bool NetDataHandler::close()
+{
+ connFd.reset();
+ listenFd.reset();
+
+ return true;
+}
+
+std::vector<std::uint8_t> NetDataHandler::copyFrom(std::uint32_t length)
+{
+ if (!connFd)
+ {
+ struct pollfd fds;
+ fds.fd = *listenFd;
+ fds.events = POLLIN;
+
+ int ret = ::poll(&fds, 1, timeoutS * 1000);
+ if (ret < 0)
+ {
+ std::perror("Failed to poll");
+ return std::vector<uint8_t>();
+ }
+ else if (ret == 0)
+ {
+ fprintf(stderr, "Timed out waiting for connection\n");
+ return std::vector<uint8_t>();
+ }
+ else if (fds.revents != POLLIN)
+ {
+ fprintf(stderr, "Invalid poll state: 0x%x\n", fds.revents);
+ return std::vector<uint8_t>();
+ }
+
+ connFd.reset(::accept(*listenFd, nullptr, nullptr));
+ if (*connFd < 0)
+ {
+ std::perror("Failed to accept connection");
+ (void)connFd.release();
+ return std::vector<uint8_t>();
+ }
+
+ struct timeval tv = {};
+ tv.tv_sec = timeoutS;
+ if (setsockopt(*connFd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0)
+ {
+ std::perror("Failed to set receive timeout");
+ return std::vector<uint8_t>();
+ }
+ }
+
+ std::vector<std::uint8_t> data(length);
+
+ std::uint32_t bytesRead = 0;
+ ssize_t ret;
+ do
+ {
+ ret = read(*connFd, data.data() + bytesRead, length - bytesRead);
+ if (ret < 0)
+ {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ std::perror("Failed to read from socket");
+ break;
+ }
+
+ bytesRead += ret;
+ } while (ret > 0 && bytesRead < length);
+
+ if (bytesRead != length)
+ {
+ fprintf(stderr,
+ "Couldn't read full expected amount. Wanted %u but got %u\n",
+ length, bytesRead);
+ data.resize(bytesRead);
+ }
+
+ return data;
+}
+
+bool NetDataHandler::writeMeta(const std::vector<std::uint8_t>& configuration)
+{
+ // TODO: have the host tool send the expected IP address that it will
+ // connect from
+ return true;
+}
+
+std::vector<std::uint8_t> NetDataHandler::readMeta()
+{
+ return std::vector<std::uint8_t>();
+}
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/net_handler.hpp b/bmc/firmware-handler/net_handler.hpp
new file mode 100644
index 0000000..b107e72
--- /dev/null
+++ b/bmc/firmware-handler/net_handler.hpp
@@ -0,0 +1,45 @@
+#pragma once
+
+#include "data_handler.hpp"
+
+#include <unistd.h>
+
+#include <stdplus/handle/managed.hpp>
+
+#include <cstdint>
+#include <optional>
+#include <vector>
+
+namespace ipmi_flash
+{
+
+/**
+ * Data Handler for receiving the image over a network port
+ */
+class NetDataHandler : public DataInterface
+{
+ public:
+ NetDataHandler() : listenFd(std::nullopt), connFd(std::nullopt)
+ {}
+
+ bool open() override;
+ bool close() override;
+ std::vector<std::uint8_t> copyFrom(std::uint32_t length) override;
+ bool writeMeta(const std::vector<std::uint8_t>& configuration) override;
+ std::vector<std::uint8_t> readMeta() override;
+
+ static constexpr std::uint16_t listenPort = 623;
+ static constexpr int timeoutS = 5;
+
+ private:
+ static void closefd(int&& fd)
+ {
+ ::close(fd);
+ }
+ using Fd = stdplus::Managed<int>::Handle<closefd>;
+
+ Fd listenFd;
+ Fd connFd;
+};
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/pci_handler.cpp b/bmc/firmware-handler/pci_handler.cpp
new file mode 100644
index 0000000..1b6b7ff
--- /dev/null
+++ b/bmc/firmware-handler/pci_handler.cpp
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pci_handler.hpp"
+
+#include "data.hpp"
+
+#include <fcntl.h>
+#include <linux/aspeed-p2a-ctrl.h>
+
+#include <cstdint>
+#include <cstring>
+#include <string>
+#include <vector>
+
+namespace ipmi_flash
+{
+
+const std::string PciDataHandler::p2aControlPath = "/dev/aspeed-p2a-ctrl";
+
+bool PciDataHandler::open()
+{
+ mappedFd = sys->open(p2aControlPath.c_str(), O_RDWR);
+ if (mappedFd == -1)
+ {
+ return false;
+ }
+
+ struct aspeed_p2a_ctrl_mapping map;
+ map.addr = regionAddress;
+ map.length = memoryRegionSize;
+ map.flags = ASPEED_P2A_CTRL_READWRITE;
+
+ if (sys->ioctl(mappedFd, ASPEED_P2A_CTRL_IOCTL_SET_WINDOW, &map))
+ {
+ sys->close(mappedFd);
+ mappedFd = -1;
+
+ return false;
+ }
+
+ if (sys->ioctl(mappedFd, ASPEED_P2A_CTRL_IOCTL_GET_MEMORY_CONFIG, &map))
+ {
+ sys->close(mappedFd);
+ mappedFd = -1;
+
+ return false;
+ }
+
+ /* The length of the region reserved is reported, and it's important
+ * because the offset + memory region could be beyond that reserved
+ * region.
+ */
+ std::uint64_t offset = regionAddress - map.addr;
+
+ mapped = reinterpret_cast<std::uint8_t*>(
+ mmap(0, memoryRegionSize, PROT_READ, MAP_SHARED, mappedFd, offset));
+ if (mapped == MAP_FAILED)
+ {
+ sys->close(mappedFd);
+ mappedFd = -1;
+ mapped = nullptr;
+
+ return false;
+ }
+
+ return true;
+}
+
+bool PciDataHandler::close()
+{
+ /* TODO: Turn off the P2A bridge and region to disable host-side access.
+ */
+ if (mapped)
+ {
+ sys->munmap(mapped, memoryRegionSize);
+ mapped = nullptr;
+ }
+
+ if (mappedFd != -1)
+ {
+ sys->close(mappedFd);
+ mappedFd = -1;
+ }
+
+ return true;
+}
+
+std::vector<std::uint8_t> PciDataHandler::copyFrom(std::uint32_t length)
+{
+ std::vector<std::uint8_t> results(length);
+ std::memcpy(results.data(), mapped, length);
+
+ return results;
+}
+
+bool PciDataHandler::writeMeta(const std::vector<std::uint8_t>& configuration)
+{
+ /* PCI handler doesn't require configuration write, only read. */
+ return false;
+}
+
+std::vector<std::uint8_t> PciDataHandler::readMeta()
+{
+ /* PCI handler does require returning a configuration from read. */
+ struct PciConfigResponse reply;
+ reply.address = regionAddress;
+
+ std::vector<std::uint8_t> bytes;
+ bytes.resize(sizeof(reply));
+ std::memcpy(bytes.data(), &reply, sizeof(reply));
+
+ return bytes;
+}
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/pci_handler.hpp b/bmc/firmware-handler/pci_handler.hpp
new file mode 100644
index 0000000..9b2c5b6
--- /dev/null
+++ b/bmc/firmware-handler/pci_handler.hpp
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "data_handler.hpp"
+#include "internal/sys.hpp"
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+namespace ipmi_flash
+{
+
+/**
+ * Data handler for reading and writing data via the P2A bridge.
+ *
+ * @note: Currently implemented to support only aspeed-p2a-ctrl.
+ */
+class PciDataHandler : public DataInterface
+{
+ public:
+ PciDataHandler(std::uint32_t regionAddress, std::size_t regionSize,
+ const internal::Sys* sys = &internal::sys_impl) :
+ regionAddress(regionAddress),
+ memoryRegionSize(regionSize), sys(sys){};
+
+ bool open() override;
+ bool close() override;
+ std::vector<std::uint8_t> copyFrom(std::uint32_t length) override;
+ bool writeMeta(const std::vector<std::uint8_t>& configuration) override;
+ std::vector<std::uint8_t> readMeta() override;
+
+ private:
+ std::uint32_t regionAddress;
+ std::uint32_t memoryRegionSize;
+ const internal::Sys* sys;
+
+ int mappedFd = -1;
+ std::uint8_t* mapped = nullptr;
+ static const std::string p2aControlPath;
+};
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/pci_nuvoton_handler.cpp b/bmc/firmware-handler/pci_nuvoton_handler.cpp
new file mode 100644
index 0000000..0a3f62e
--- /dev/null
+++ b/bmc/firmware-handler/pci_nuvoton_handler.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "data.hpp"
+#include "pci_handler.hpp"
+
+#include <fcntl.h>
+
+#include <cstdint>
+#include <cstring>
+#include <string>
+#include <vector>
+
+namespace ipmi_flash
+{
+
+bool PciDataHandler::open()
+{
+ static constexpr auto devmem = "/dev/mem";
+
+ mappedFd = sys->open(devmem, O_RDWR | O_SYNC);
+ if (mappedFd == -1)
+ {
+ std::fprintf(stderr, "PciDataHandler::Unable to open /dev/mem");
+ return false;
+ }
+
+ mapped = reinterpret_cast<uint8_t*>(sys->mmap(
+ 0, memoryRegionSize, PROT_READ, MAP_SHARED, mappedFd, regionAddress));
+ if (mapped == MAP_FAILED)
+ {
+ sys->close(mappedFd);
+ mappedFd = -1;
+ mapped = nullptr;
+
+ std::fprintf(stderr, "PciDataHandler::Unable to map region");
+ return false;
+ }
+
+ return true;
+}
+
+bool PciDataHandler::close()
+{
+ /* TODO: Turn off the P2A bridge and region to disable host-side access.
+ */
+ if (mapped)
+ {
+ sys->munmap(mapped, memoryRegionSize);
+ mapped = nullptr;
+ }
+
+ if (mappedFd != -1)
+ {
+ sys->close(mappedFd);
+ mappedFd = -1;
+ }
+
+ return true;
+}
+
+std::vector<std::uint8_t> PciDataHandler::copyFrom(std::uint32_t length)
+{
+ std::vector<std::uint8_t> results(length);
+ std::memcpy(results.data(), mapped, length);
+
+ return results;
+}
+
+bool PciDataHandler::writeMeta(const std::vector<std::uint8_t>& configuration)
+{
+ /* PCI handler doesn't require configuration write, only read. */
+ return false;
+}
+
+std::vector<std::uint8_t> PciDataHandler::readMeta()
+{
+ /* PCI handler does require returning a configuration from read. */
+ struct PciConfigResponse reply;
+ reply.address = regionAddress;
+
+ std::vector<std::uint8_t> bytes;
+ bytes.resize(sizeof(reply));
+ std::memcpy(bytes.data(), &reply, sizeof(reply));
+
+ return bytes;
+}
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/phosphor-ipmi-flash-bios-prepare.target.in b/bmc/firmware-handler/phosphor-ipmi-flash-bios-prepare.target.in
new file mode 100644
index 0000000..b50e644
--- /dev/null
+++ b/bmc/firmware-handler/phosphor-ipmi-flash-bios-prepare.target.in
@@ -0,0 +1,2 @@
+[Unit]
+Description=Phosphor-ipmi-flash Prepare BIOS to receive update
diff --git a/bmc/firmware-handler/phosphor-ipmi-flash-bios-update.target.in b/bmc/firmware-handler/phosphor-ipmi-flash-bios-update.target.in
new file mode 100644
index 0000000..46759ed
--- /dev/null
+++ b/bmc/firmware-handler/phosphor-ipmi-flash-bios-update.target.in
@@ -0,0 +1,2 @@
+[Unit]
+Description=Phosphor-ipmi-flash update the BIOS image
diff --git a/bmc/firmware-handler/phosphor-ipmi-flash-bios-verify.target.in b/bmc/firmware-handler/phosphor-ipmi-flash-bios-verify.target.in
new file mode 100644
index 0000000..d45da94
--- /dev/null
+++ b/bmc/firmware-handler/phosphor-ipmi-flash-bios-verify.target.in
@@ -0,0 +1,2 @@
+[Unit]
+Description=Phosphor-ipmi-flash verify the image contents
diff --git a/bmc/firmware-handler/phosphor-ipmi-flash-bmc-prepare.target.in b/bmc/firmware-handler/phosphor-ipmi-flash-bmc-prepare.target.in
new file mode 100644
index 0000000..7f0cbd9
--- /dev/null
+++ b/bmc/firmware-handler/phosphor-ipmi-flash-bmc-prepare.target.in
@@ -0,0 +1,2 @@
+[Unit]
+Description=Phosphor-ipmi-flash Prepare BMC to receive update
diff --git a/bmc/firmware-handler/phosphor-ipmi-flash-bmc-update.target.in b/bmc/firmware-handler/phosphor-ipmi-flash-bmc-update.target.in
new file mode 100644
index 0000000..a7335b0
--- /dev/null
+++ b/bmc/firmware-handler/phosphor-ipmi-flash-bmc-update.target.in
@@ -0,0 +1,2 @@
+[Unit]
+Description=Phosphor-ipmi-flash update the BMC image
diff --git a/bmc/firmware-handler/phosphor-ipmi-flash-bmc-verify.target.in b/bmc/firmware-handler/phosphor-ipmi-flash-bmc-verify.target.in
new file mode 100644
index 0000000..d45da94
--- /dev/null
+++ b/bmc/firmware-handler/phosphor-ipmi-flash-bmc-verify.target.in
@@ -0,0 +1,2 @@
+[Unit]
+Description=Phosphor-ipmi-flash verify the image contents
diff --git a/bmc/firmware-handler/test/Makefile.am b/bmc/firmware-handler/test/Makefile.am
new file mode 100644
index 0000000..bf818fd
--- /dev/null
+++ b/bmc/firmware-handler/test/Makefile.am
@@ -0,0 +1,115 @@
+@VALGRIND_CHECK_RULES@
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/ \
+ -I$(top_srcdir)/tools/ \
+ -I$(top_srcdir)/bmc/ \
+ -I$(top_srcdir)/bmc/firmware-handler \
+ $(GTEST_CFLAGS) \
+ $(GMOCK_CFLAGS) \
+ $(CODE_COVERAGE_CPPFLAGS)
+AM_CXXFLAGS = \
+ $(SDBUSPLUS_CFLAGS) \
+ $(PHOSPHOR_LOGGING_CFLAGS) \
+ $(CODE_COVERAGE_CXXFLAGS)
+AM_LDFLAGS = \
+ $(GTEST_LIBS) \
+ $(GMOCK_LIBS) \
+ -lgmock_main \
+ $(OESDK_TESTCASE_FLAGS) \
+ $(SDBUSPLUS_LIBS) \
+ $(PHOSPHOR_LOGGING_LIBS) \
+ $(CODE_COVERAGE_LIBS)
+
+# Run all 'check' test programs
+check_PROGRAMS = \
+ firmware_handler_unittest \
+ firmware_stat_unittest \
+ firmware_canhandle_unittest \
+ firmware_write_unittest \
+ firmware_writemeta_unittest \
+ firmware_open_unittest \
+ firmware_close_unittest \
+ firmware_sessionstat_unittest \
+ firmware_commit_unittest \
+ file_handler_unittest \
+ firmware_state_notyetstarted_unittest \
+ firmware_state_uploadinprogress_unittest \
+ firmware_state_verificationpending_unittest \
+ firmware_state_verificationstarted_unittest \
+ firmware_state_verificationcompleted_unittest \
+ firmware_state_updatepending_unittest \
+ firmware_state_updatestarted_unittest \
+ firmware_state_updatecompleted_unittest \
+ firmware_state_notyetstarted_tarball_unittest \
+ firmware_multiplebundle_unittest \
+ firmware_json_unittest \
+ firmware_skip_unittest
+
+TESTS = $(check_PROGRAMS)
+
+firmware_handler_unittest_SOURCES = firmware_handler_unittest.cpp
+firmware_handler_unittest_LDADD = $(top_builddir)/bmc/firmware-handler/libfirmwareblob_common.la
+
+firmware_stat_unittest_SOURCES = firmware_stat_unittest.cpp
+firmware_stat_unittest_LDADD = $(top_builddir)/bmc/firmware-handler/libfirmwareblob_common.la
+
+firmware_canhandle_unittest_SOURCES = firmware_canhandle_unittest.cpp
+firmware_canhandle_unittest_LDADD = $(top_builddir)/bmc/firmware-handler/libfirmwareblob_common.la
+
+firmware_write_unittest_SOURCES = firmware_write_unittest.cpp
+firmware_write_unittest_LDADD = $(top_builddir)/bmc/firmware-handler/libfirmwareblob_common.la
+
+firmware_writemeta_unittest_SOURCES = firmware_writemeta_unittest.cpp
+firmware_writemeta_unittest_LDADD = $(top_builddir)/bmc/firmware-handler/libfirmwareblob_common.la
+
+firmware_open_unittest_SOURCES = firmware_open_unittest.cpp
+firmware_open_unittest_LDADD = $(top_builddir)/bmc/firmware-handler/libfirmwareblob_common.la
+
+firmware_close_unittest_SOURCES = firmware_close_unittest.cpp
+firmware_close_unittest_LDADD = $(top_builddir)/bmc/firmware-handler/libfirmwareblob_common.la
+
+firmware_sessionstat_unittest_SOURCES = firmware_sessionstat_unittest.cpp
+firmware_sessionstat_unittest_LDADD = $(top_builddir)/bmc/firmware-handler/libfirmwareblob_common.la
+
+firmware_commit_unittest_SOURCES = firmware_commit_unittest.cpp
+firmware_commit_unittest_LDADD = $(top_builddir)/bmc/firmware-handler/libfirmwareblob_common.la
+
+file_handler_unittest_SOURCES = file_handler_unittest.cpp
+file_handler_unittest_LDADD = $(top_builddir)/bmc/firmware-handler/libfirmwareblob_common.la -lstdc++fs
+
+firmware_state_notyetstarted_unittest_SOURCES = firmware_state_notyetstarted_unittest.cpp
+firmware_state_notyetstarted_unittest_LDADD = $(top_builddir)/bmc/firmware-handler/libfirmwareblob_common.la
+
+firmware_state_uploadinprogress_unittest_SOURCES = firmware_state_uploadinprogress_unittest.cpp
+firmware_state_uploadinprogress_unittest_LDADD = $(top_builddir)/bmc/firmware-handler/libfirmwareblob_common.la
+
+firmware_state_verificationpending_unittest_SOURCES = firmware_state_verificationpending_unittest.cpp
+firmware_state_verificationpending_unittest_LDADD = $(top_builddir)/bmc/firmware-handler/libfirmwareblob_common.la
+
+firmware_state_verificationstarted_unittest_SOURCES = firmware_state_verificationstarted_unittest.cpp
+firmware_state_verificationstarted_unittest_LDADD = $(top_builddir)/bmc/firmware-handler/libfirmwareblob_common.la
+
+firmware_state_verificationcompleted_unittest_SOURCES = firmware_state_verificationcompleted_unittest.cpp
+firmware_state_verificationcompleted_unittest_LDADD = $(top_builddir)/bmc/firmware-handler/libfirmwareblob_common.la
+
+firmware_state_updatepending_unittest_SOURCES = firmware_state_updatepending_unittest.cpp
+firmware_state_updatepending_unittest_LDADD = $(top_builddir)/bmc/firmware-handler/libfirmwareblob_common.la
+
+firmware_state_updatestarted_unittest_SOURCES = firmware_state_updatestarted_unittest.cpp
+firmware_state_updatestarted_unittest_LDADD = $(top_builddir)/bmc/firmware-handler/libfirmwareblob_common.la
+
+firmware_state_updatecompleted_unittest_SOURCES = firmware_state_updatecompleted_unittest.cpp
+firmware_state_updatecompleted_unittest_LDADD = $(top_builddir)/bmc/firmware-handler/libfirmwareblob_common.la
+
+firmware_state_notyetstarted_tarball_unittest_SOURCES = firmware_state_notyetstarted_tarball_unittest.cpp
+firmware_state_notyetstarted_tarball_unittest_LDADD = $(top_builddir)/bmc/firmware-handler/libfirmwareblob_common.la
+
+firmware_multiplebundle_unittest_SOURCES = firmware_multiplebundle_unittest.cpp
+firmware_multiplebundle_unittest_LDADD = $(top_builddir)/bmc/firmware-handler/libfirmwareblob_common.la
+
+firmware_json_unittest_SOURCES = firmware_json_unittest.cpp
+firmware_json_unittest_LDADD = $(top_builddir)/bmc/firmware-handler/libfirmwareblob_common.la
+
+firmware_skip_unittest_SOURCES = firmware_skip_unittest.cpp
+firmware_skip_unittest_LDADD = $(top_builddir)/bmc/firmware-handler/libfirmwareblob_common.la
diff --git a/bmc/firmware-handler/test/crc_mock.hpp b/bmc/firmware-handler/test/crc_mock.hpp
new file mode 100644
index 0000000..293ec24
--- /dev/null
+++ b/bmc/firmware-handler/test/crc_mock.hpp
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <cstdint>
+#include <vector>
+
+#include <gmock/gmock.h>
+
+class CrcInterface
+{
+ public:
+ virtual ~CrcInterface() = default;
+
+ virtual std::uint16_t
+ generateCrc(const std::vector<std::uint8_t>& data) const = 0;
+};
+
+class CrcMock : public CrcInterface
+{
+ public:
+ virtual ~CrcMock() = default;
+ MOCK_CONST_METHOD1(generateCrc,
+ std::uint16_t(const std::vector<std::uint8_t>&));
+};
diff --git a/bmc/firmware-handler/test/data_mock.hpp b/bmc/firmware-handler/test/data_mock.hpp
new file mode 100644
index 0000000..c8109ad
--- /dev/null
+++ b/bmc/firmware-handler/test/data_mock.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "data_handler.hpp"
+
+#include <gmock/gmock.h>
+
+namespace ipmi_flash
+{
+
+class DataHandlerMock : public DataInterface
+{
+ public:
+ virtual ~DataHandlerMock() = default;
+
+ MOCK_METHOD0(open, bool());
+ MOCK_METHOD0(close, bool());
+ MOCK_METHOD1(copyFrom, std::vector<std::uint8_t>(std::uint32_t));
+ MOCK_METHOD1(writeMeta, bool(const std::vector<std::uint8_t>&));
+ MOCK_METHOD0(readMeta, std::vector<std::uint8_t>());
+};
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/file_handler_unittest.cpp b/bmc/firmware-handler/test/file_handler_unittest.cpp
new file mode 100644
index 0000000..ab12125
--- /dev/null
+++ b/bmc/firmware-handler/test/file_handler_unittest.cpp
@@ -0,0 +1,58 @@
+#include "file_handler.hpp"
+
+#include <cstdint>
+#include <cstdio>
+#include <fstream>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+
+static constexpr auto TESTPATH = "test.output";
+
+class FileHandlerOpenTest : public ::testing::Test
+{
+ protected:
+ void TearDown() override
+ {
+ (void)std::remove(TESTPATH);
+ }
+};
+
+TEST_F(FileHandlerOpenTest, VerifyItIsHappy)
+{
+ /* Opening a fail may create it? */
+
+ FileHandler handler(TESTPATH);
+ EXPECT_TRUE(handler.open(""));
+
+ /* Calling open twice fails the second time. */
+ EXPECT_FALSE(handler.open(""));
+}
+
+TEST_F(FileHandlerOpenTest, VerifyWriteDataWrites)
+{
+ /* Verify writing bytes writes them... flushing data can be an issue here,
+ * so we close first.
+ */
+ FileHandler handler(TESTPATH);
+ EXPECT_TRUE(handler.open(""));
+
+ std::vector<std::uint8_t> bytes = {0x01, 0x02};
+ std::uint32_t offset = 0;
+
+ EXPECT_TRUE(handler.write(offset, bytes));
+ handler.close();
+
+ std::ifstream data;
+ data.open(TESTPATH, std::ios::binary);
+ char expectedBytes[2];
+ data.read(&expectedBytes[0], sizeof(expectedBytes));
+ EXPECT_EQ(expectedBytes[0], bytes[0]);
+ EXPECT_EQ(expectedBytes[1], bytes[1]);
+ /* annoyingly the memcmp was failing... but it's the same data. */
+}
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/firmware_canhandle_unittest.cpp b/bmc/firmware-handler/test/firmware_canhandle_unittest.cpp
new file mode 100644
index 0000000..34c4e51
--- /dev/null
+++ b/bmc/firmware-handler/test/firmware_canhandle_unittest.cpp
@@ -0,0 +1,43 @@
+#include "data_mock.hpp"
+#include "firmware_handler.hpp"
+#include "flags.hpp"
+#include "image_mock.hpp"
+#include "triggerable_mock.hpp"
+#include "util.hpp"
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+TEST(FirmwareHandlerCanHandleTest, VerifyItemsInListAreOk)
+{
+ struct ListItem
+ {
+ std::string name;
+ bool expected;
+ };
+
+ std::vector<ListItem> items = {
+ {"asdf", true}, {"nope", false}, {"123123", false}, {"bcdf", true}};
+
+ ImageHandlerMock imageMock;
+
+ std::vector<HandlerPack> blobs;
+ blobs.emplace_back(hashBlobId, std::make_unique<ImageHandlerMock>());
+ blobs.emplace_back("asdf", std::make_unique<ImageHandlerMock>());
+ blobs.emplace_back("bcdf", std::make_unique<ImageHandlerMock>());
+
+ std::vector<DataHandlerPack> data;
+ data.emplace_back(FirmwareFlags::UpdateFlags::ipmi, nullptr);
+
+ auto handler = FirmwareBlobHandler::CreateFirmwareBlobHandler(
+ std::move(blobs), std::move(data), CreateActionMap("asdf"));
+
+ for (const auto& item : items)
+ {
+ EXPECT_EQ(item.expected, handler->canHandleBlob(item.name));
+ }
+}
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/firmware_close_unittest.cpp b/bmc/firmware-handler/test/firmware_close_unittest.cpp
new file mode 100644
index 0000000..6d1fe55
--- /dev/null
+++ b/bmc/firmware-handler/test/firmware_close_unittest.cpp
@@ -0,0 +1,76 @@
+#include "data_mock.hpp"
+#include "firmware_handler.hpp"
+#include "firmware_unittest.hpp"
+#include "image_mock.hpp"
+#include "triggerable_mock.hpp"
+#include "util.hpp"
+
+#include <memory>
+#include <vector>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+
+using ::testing::Return;
+using ::testing::StrEq;
+
+class FirmwareHandlerCloseTest : public FakeLpcFirmwareTest
+{};
+
+TEST_F(FirmwareHandlerCloseTest, CloseSucceedsWithDataHandler)
+{
+ /* Boring test where you open a blob_id, then verify that when it's closed
+ * everything looks right.
+ */
+ EXPECT_CALL(*dataMock, open()).WillOnce(Return(true));
+ EXPECT_CALL(*hashImageMock, open(StrEq(hashBlobId))).WillOnce(Return(true));
+
+ EXPECT_TRUE(handler->open(
+ 0, blobs::OpenFlags::write | FirmwareFlags::UpdateFlags::lpc,
+ hashBlobId));
+
+ /* The active hash blob_id was added. */
+ auto currentBlobs = handler->getBlobIds();
+ EXPECT_EQ(3, currentBlobs.size());
+ EXPECT_EQ(1, std::count(currentBlobs.begin(), currentBlobs.end(),
+ activeHashBlobId));
+
+ /* Set up close() expectations. */
+ EXPECT_CALL(*dataMock, close());
+ EXPECT_CALL(*hashImageMock, close());
+ EXPECT_TRUE(handler->close(0));
+
+ /* Close does not delete the active blob id. This indicates that there is
+ * data queued.
+ */
+}
+
+TEST_F(FirmwareHandlerCloseTest, CloseSucceedsWithoutDataHandler)
+{
+ /* Boring test where you open a blob_id using ipmi, so there's no data
+ * handler, and it's closed and everything looks right.
+ */
+ EXPECT_CALL(*hashImageMock, open(StrEq(hashBlobId))).WillOnce(Return(true));
+
+ EXPECT_TRUE(handler->open(
+ 0, blobs::OpenFlags::write | FirmwareFlags::UpdateFlags::ipmi,
+ hashBlobId));
+
+ /* The active hash blob_id was added. */
+ auto currentBlobs = handler->getBlobIds();
+ EXPECT_EQ(3, currentBlobs.size());
+ EXPECT_EQ(1, std::count(currentBlobs.begin(), currentBlobs.end(),
+ activeHashBlobId));
+
+ /* Set up close() expectations. */
+ EXPECT_CALL(*hashImageMock, close());
+ EXPECT_TRUE(handler->close(0));
+}
+
+} // namespace
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/firmware_commit_unittest.cpp b/bmc/firmware-handler/test/firmware_commit_unittest.cpp
new file mode 100644
index 0000000..cb50b8e
--- /dev/null
+++ b/bmc/firmware-handler/test/firmware_commit_unittest.cpp
@@ -0,0 +1,77 @@
+#include "data_mock.hpp"
+#include "firmware_handler.hpp"
+#include "flags.hpp"
+#include "image_mock.hpp"
+#include "triggerable_mock.hpp"
+#include "util.hpp"
+
+#include <memory>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+
+using ::testing::Return;
+using ::testing::StrEq;
+
+class FirmwareHandlerCommitTest : public ::testing::Test
+{
+ protected:
+ ImageHandlerMock *imageMock1, *imageMock2;
+ std::vector<HandlerPack> blobs;
+ std::vector<DataHandlerPack> data;
+
+ void SetUp() override
+ {
+ std::unique_ptr<ImageHandlerInterface> image =
+ std::make_unique<ImageHandlerMock>();
+ imageMock1 = reinterpret_cast<ImageHandlerMock*>(image.get());
+ blobs.emplace_back(hashBlobId, std::move(image));
+
+ image = std::make_unique<ImageHandlerMock>();
+ imageMock2 = reinterpret_cast<ImageHandlerMock*>(image.get());
+ blobs.emplace_back("asdf", std::move(image));
+
+ data.emplace_back(FirmwareFlags::UpdateFlags::ipmi, nullptr);
+ }
+};
+
+TEST_F(FirmwareHandlerCommitTest, VerifyCannotCommitOnFlashImage)
+{
+ /* Verify the flash image returns failure on this command. It's a fairly
+ * artificial test.
+ */
+ auto handler = FirmwareBlobHandler::CreateFirmwareBlobHandler(
+ std::move(blobs), std::move(data), CreateActionMap("asdf"));
+
+ EXPECT_CALL(*imageMock2, open("asdf")).WillOnce(Return(true));
+
+ EXPECT_TRUE(handler->open(
+ 0, blobs::OpenFlags::write | FirmwareFlags::UpdateFlags::ipmi, "asdf"));
+
+ EXPECT_FALSE(handler->commit(0, {}));
+}
+
+TEST_F(FirmwareHandlerCommitTest, VerifyCannotCommitOnHashFile)
+{
+ /* Verify the hash file returns failure on this command. It's a fairly
+ * artificial test.
+ */
+ auto handler = FirmwareBlobHandler::CreateFirmwareBlobHandler(
+ std::move(blobs), std::move(data), CreateActionMap("asdf"));
+
+ EXPECT_CALL(*imageMock1, open(StrEq(hashBlobId))).WillOnce(Return(true));
+
+ EXPECT_TRUE(handler->open(
+ 0, blobs::OpenFlags::write | FirmwareFlags::UpdateFlags::ipmi,
+ hashBlobId));
+
+ EXPECT_FALSE(handler->commit(0, {}));
+}
+
+} // namespace
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/firmware_handler_unittest.cpp b/bmc/firmware-handler/test/firmware_handler_unittest.cpp
new file mode 100644
index 0000000..1c3cc58
--- /dev/null
+++ b/bmc/firmware-handler/test/firmware_handler_unittest.cpp
@@ -0,0 +1,122 @@
+#include "firmware_handler.hpp"
+#include "flags.hpp"
+#include "image_mock.hpp"
+#include "triggerable_mock.hpp"
+#include "util.hpp"
+
+#include <algorithm>
+#include <memory>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+
+using ::testing::UnorderedElementsAreArray;
+
+TEST(FirmwareHandlerTest, CreateEmptyHandlerListVerifyFails)
+{
+ std::vector<DataHandlerPack> data;
+ data.emplace_back(FirmwareFlags::UpdateFlags::ipmi, nullptr);
+
+ auto handler = FirmwareBlobHandler::CreateFirmwareBlobHandler(
+ {}, std::move(data), CreateActionMap("abcd"));
+ EXPECT_EQ(handler, nullptr);
+}
+TEST(FirmwareHandlerTest, CreateEmptyDataHandlerListFails)
+{
+ ImageHandlerMock imageMock;
+
+ std::vector<HandlerPack> blobs;
+ blobs.emplace_back(hashBlobId, std::make_unique<ImageHandlerMock>());
+ blobs.emplace_back("asdf", std::make_unique<ImageHandlerMock>());
+
+ auto handler = FirmwareBlobHandler::CreateFirmwareBlobHandler(
+ std::move(blobs), std::vector<DataHandlerPack>(),
+ CreateActionMap("asdf"));
+ EXPECT_EQ(handler, nullptr);
+}
+TEST(FirmwareHandlerTest, CreateEmptyActionPackVerifyFails)
+{
+ /* The ActionPack map corresponds to the firmware list passed in, but
+ * they're not checked against each other yet.
+ */
+ std::vector<DataHandlerPack> data;
+ data.emplace_back(FirmwareFlags::UpdateFlags::ipmi, nullptr);
+
+ std::vector<HandlerPack> blobs;
+ blobs.emplace_back("asdf", std::make_unique<ImageHandlerMock>());
+ blobs.emplace_back(hashBlobId, std::make_unique<ImageHandlerMock>());
+
+ ActionMap emptyMap;
+
+ auto handler = FirmwareBlobHandler::CreateFirmwareBlobHandler(
+ std::move(blobs), std::move(data), std::move(emptyMap));
+ EXPECT_EQ(handler, nullptr);
+}
+TEST(FirmwareHandlerTest, FirmwareHandlerListRequiresAtLeastTwoEntries)
+{
+ /* The hashblob handler must be one of the entries, but it cannot be the
+ * only entry.
+ */
+ std::vector<DataHandlerPack> data;
+ data.emplace_back(FirmwareFlags::UpdateFlags::ipmi, nullptr);
+
+ /* Provide a firmware list that has the hash blob, which is the required one
+ * -- tested in a different test.
+ */
+ std::vector<HandlerPack> blobs;
+ blobs.emplace_back(hashBlobId, std::make_unique<ImageHandlerMock>());
+
+ auto handler = FirmwareBlobHandler::CreateFirmwareBlobHandler(
+ std::move(blobs), std::move(data), CreateActionMap("asdf"));
+ EXPECT_EQ(handler, nullptr);
+
+ /* Add second firmware and it'll now work. */
+ std::vector<HandlerPack> blobs2;
+ blobs2.emplace_back(hashBlobId, std::make_unique<ImageHandlerMock>());
+ blobs2.emplace_back("asdf", std::make_unique<ImageHandlerMock>());
+
+ std::vector<DataHandlerPack> data2;
+ data2.emplace_back(FirmwareFlags::UpdateFlags::ipmi, nullptr);
+
+ handler = FirmwareBlobHandler::CreateFirmwareBlobHandler(
+ std::move(blobs2), std::move(data2), CreateActionMap("asdf"));
+
+ auto result = handler->getBlobIds();
+ std::vector<std::string> expectedBlobs = {"asdf", hashBlobId};
+ EXPECT_THAT(result, UnorderedElementsAreArray(expectedBlobs));
+}
+TEST(FirmwareHandlerTest, VerifyHashRequiredForHappiness)
+{
+ std::vector<DataHandlerPack> data;
+ data.emplace_back(FirmwareFlags::UpdateFlags::ipmi, nullptr);
+
+ /* This works fine only if you also pass in the hash handler. */
+ std::vector<HandlerPack> blobs;
+ blobs.emplace_back("asdf", std::make_unique<ImageHandlerMock>());
+
+ auto handler = FirmwareBlobHandler::CreateFirmwareBlobHandler(
+ std::move(blobs), std::move(data), CreateActionMap("asdf"));
+ EXPECT_EQ(handler, nullptr);
+
+ std::vector<HandlerPack> blobs2;
+ blobs2.emplace_back("asdf", std::make_unique<ImageHandlerMock>());
+ blobs2.emplace_back(hashBlobId, std::make_unique<ImageHandlerMock>());
+
+ std::vector<DataHandlerPack> data2;
+ data2.emplace_back(FirmwareFlags::UpdateFlags::ipmi, nullptr);
+
+ handler = FirmwareBlobHandler::CreateFirmwareBlobHandler(
+ std::move(blobs2), std::move(data2), CreateActionMap("asdf"));
+
+ auto result = handler->getBlobIds();
+ std::vector<std::string> expectedBlobs = {"asdf", hashBlobId};
+ EXPECT_THAT(result, UnorderedElementsAreArray(expectedBlobs));
+}
+
+} // namespace
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/firmware_json_unittest.cpp b/bmc/firmware-handler/test/firmware_json_unittest.cpp
new file mode 100644
index 0000000..8f3ea30
--- /dev/null
+++ b/bmc/firmware-handler/test/firmware_json_unittest.cpp
@@ -0,0 +1,620 @@
+#include "buildjson.hpp"
+#include "general_systemd.hpp"
+#include "skip_action.hpp"
+
+#include <nlohmann/json.hpp>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+using ::testing::IsEmpty;
+
+using json = nlohmann::json;
+
+TEST(FirmwareJsonTest, InvalidHandlerType)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/image",
+ "handler" : {
+ "type" : "unsupported",
+ "path" : "/run/initramfs/bmc-image"
+ },
+ "actions" : {
+ "preparation" : {
+ "type" : "systemd",
+ "unit" : "phosphor-ipmi-flash-bmc-prepare.target"
+ },
+ "verification" : {
+ "type" : "fileSystemdVerify",
+ "unit" : "phosphor-ipmi-flash-bmc-verify.target",
+ "path" : "/tmp/bmc.verify"
+ },
+ "update" : {
+ "type" : "reboot"
+ }
+ }
+ }]
+ )"_json;
+
+ EXPECT_THAT(buildHandlerFromJson(j2), IsEmpty());
+}
+
+TEST(FirmwareJsonTest, InvalidPreparationType)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/image",
+ "handler" : {
+ "type" : "file",
+ "path" : "/run/initramfs/bmc-image"
+ },
+ "actions" : {
+ "preparation" : {
+ "type" : "superfun",
+ "unit" : "phosphor-ipmi-flash-bmc-prepare.target"
+ },
+ "verification" : {
+ "type" : "fileSystemdVerify",
+ "unit" : "phosphor-ipmi-flash-bmc-verify.target",
+ "path" : "/tmp/bmc.verify"
+ },
+ "update" : {
+ "type" : "reboot"
+ }
+ }
+ }]
+ )"_json;
+
+ EXPECT_THAT(buildHandlerFromJson(j2), IsEmpty());
+}
+
+TEST(FirmwareJsonTest, InvalidVerificationType)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/image",
+ "handler" : {
+ "type" : "file",
+ "path" : "/run/initramfs/bmc-image"
+ },
+ "actions" : {
+ "preparation" : {
+ "type" : "systemd",
+ "unit" : "phosphor-ipmi-flash-bmc-prepare.target"
+ },
+ "verification" : {
+ "type" : "funtimes",
+ "unit" : "phosphor-ipmi-flash-bmc-verify.target",
+ "path" : "/tmp/bmc.verify"
+ },
+ "update" : {
+ "type" : "reboot"
+ }
+ }
+ }]
+ )"_json;
+
+ EXPECT_THAT(buildHandlerFromJson(j2), IsEmpty());
+}
+
+TEST(FirmwareJsonTest, InvalidUpdateType)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/image",
+ "handler" : {
+ "type" : "file",
+ "path" : "/run/initramfs/bmc-image"
+ },
+ "actions" : {
+ "preparation" : {
+ "type" : "systemd",
+ "unit" : "phosphor-ipmi-flash-bmc-prepare.target"
+ },
+ "verification" : {
+ "type" : "fileSystemdVerify",
+ "unit" : "phosphor-ipmi-flash-bmc-verify.target",
+ "path" : "/tmp/bmc.verify"
+ },
+ "update" : {
+ "type" : "systemd"
+ }
+ }
+ }]
+ )"_json;
+
+ EXPECT_THAT(buildHandlerFromJson(j2), IsEmpty());
+}
+
+TEST(FirmwareJsonTest, MissingHandler)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/image",
+ "actions" : {
+ "preparation" : {
+ "type" : "systemd",
+ "unit" : "phosphor-ipmi-flash-bmc-prepare.target"
+ },
+ "verification" : {
+ "type" : "fileSystemdVerify",
+ "unit" : "phosphor-ipmi-flash-bmc-verify.target",
+ "path" : "/tmp/bmc.verify"
+ },
+ "update" : {
+ "type" : "reboot"
+ }
+ }
+ }]
+ )"_json;
+
+ EXPECT_THAT(buildHandlerFromJson(j2), IsEmpty());
+}
+
+TEST(FirmwareJsonTest, MissingActions)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/image",
+ "handler" : {
+ "type" : "file",
+ "path" : "/run/initramfs/bmc-image"
+ }
+ }]
+ )"_json;
+
+ EXPECT_THAT(buildHandlerFromJson(j2), IsEmpty());
+}
+
+TEST(FirmwareJsonTest, MissingActionPreparation)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/image",
+ "handler" : {
+ "type" : "file",
+ "path" : "/run/initramfs/bmc-image"
+ },
+ "actions" : {
+ "verification" : {
+ "type" : "fileSystemdVerify",
+ "unit" : "phosphor-ipmi-flash-bmc-verify.target",
+ "path" : "/tmp/bmc.verify"
+ },
+ "update" : {
+ "type" : "reboot"
+ }
+ }
+ }]
+ )"_json;
+
+ EXPECT_THAT(buildHandlerFromJson(j2), IsEmpty());
+}
+
+TEST(FirmwareJsonTest, MissingActionVerification)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/image",
+ "handler" : {
+ "type" : "file",
+ "path" : "/run/initramfs/bmc-image"
+ },
+ "actions" : {
+ "preparation" : {
+ "type" : "systemd",
+ "unit" : "phosphor-ipmi-flash-bmc-prepare.target"
+ },
+ "update" : {
+ "type" : "reboot"
+ }
+ }
+ }]
+ )"_json;
+
+ EXPECT_THAT(buildHandlerFromJson(j2), IsEmpty());
+}
+
+TEST(FirmwareJsonTest, MissingActionUpdate)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/image",
+ "handler" : {
+ "type" : "file",
+ "path" : "/run/initramfs/bmc-image"
+ },
+ "actions" : {
+ "preparation" : {
+ "type" : "systemd",
+ "unit" : "phosphor-ipmi-flash-bmc-prepare.target"
+ },
+ "verification" : {
+ "type" : "fileSystemdVerify",
+ "unit" : "phosphor-ipmi-flash-bmc-verify.target",
+ "path" : "/tmp/bmc.verify"
+ }
+ }
+ }]
+ )"_json;
+
+ EXPECT_THAT(buildHandlerFromJson(j2), IsEmpty());
+}
+
+TEST(FirmwareJsonTest, TwoConfigsOneInvalidReturnsValid)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/image",
+ "actions" : {
+ "preparation" : {
+ "type" : "systemd",
+ "unit" : "phosphor-ipmi-flash-bmc-prepare.target"
+ },
+ "verification" : {
+ "type" : "fileSystemdVerify",
+ "unit" : "phosphor-ipmi-flash-bmc-verify.target",
+ "path" : "/tmp/bmc.verify"
+ },
+ "update" : {
+ "type" : "reboot"
+ }
+ }
+ },
+ {
+ "blob" : "/flash/image2",
+ "handler" : {
+ "type" : "file",
+ "path" : "/run/initramfs/bmc-image"
+ },
+ "actions" : {
+ "preparation" : {
+ "type" : "systemd",
+ "unit" : "phosphor-ipmi-flash-bmc-prepare.target"
+ },
+ "verification" : {
+ "type" : "fileSystemdVerify",
+ "unit" : "phosphor-ipmi-flash-bmc-verify.target",
+ "path" : "/tmp/bmc.verify"
+ },
+ "update" : {
+ "type" : "reboot"
+ }
+ }
+ }]
+ )"_json;
+
+ auto h = buildHandlerFromJson(j2);
+ EXPECT_EQ(h[0].blobId, "/flash/image2");
+ EXPECT_EQ(h.size(), 1);
+}
+
+/*
+ * TODO: It may be worth individually using builders per type, and testing
+ * those.
+ *
+ * TODO: Only allow unique handler blob paths (tested at a higher level).
+ */
+
+TEST(FirmwareJsonTest, VerifyBlobNameMatches)
+{
+ /* A perfect configuration except the blob name doesn't start with "/flash/"
+ */
+ auto j2 = R"(
+ [{
+ "blob" : "bmc-image-flash",
+ "handler" : {
+ "type" : "file",
+ "path" : "/run/initramfs/bmc-image"
+ },
+ "actions" : {
+ "preparation" : {
+ "type" : "systemd",
+ "unit" : "phosphor-ipmi-flash-bmc-prepare.target"
+ },
+ "verification" : {
+ "type" : "fileSystemdVerify",
+ "unit" : "phosphor-ipmi-flash-bmc-verify.target",
+ "path" : "/tmp/bmc.verify"
+ },
+ "update" : {
+ "type" : "reboot"
+ }
+ }
+ }]
+ )"_json;
+
+ EXPECT_THAT(buildHandlerFromJson(j2), IsEmpty());
+}
+
+TEST(FirmwareJsonTest, VerifyMinimumBlobNameLength)
+{
+ /* A perfect configuration except the blob name is effectively zero length.
+ */
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/",
+ "handler" : {
+ "type" : "file",
+ "path" : "/run/initramfs/bmc-image"
+ },
+ "actions" : {
+ "preparation" : {
+ "type" : "systemd",
+ "unit" : "phosphor-ipmi-flash-bmc-prepare.target"
+ },
+ "verification" : {
+ "type" : "fileSystemdVerify",
+ "unit" : "phosphor-ipmi-flash-bmc-verify.target",
+ "path" : "/tmp/bmc.verify"
+ },
+ "update" : {
+ "type" : "reboot"
+ }
+ }
+ }]
+ )"_json;
+
+ EXPECT_THAT(buildHandlerFromJson(j2), IsEmpty());
+}
+
+TEST(FirmwareJsonTest, VerifySystemdWithReboot)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/image",
+ "handler" : {
+ "type" : "file",
+ "path" : "/run/initramfs/bmc-image"
+ },
+ "actions" : {
+ "preparation" : {
+ "type" : "systemd",
+ "unit" : "phosphor-ipmi-flash-bmc-prepare.target"
+ },
+ "verification" : {
+ "type" : "fileSystemdVerify",
+ "unit" : "phosphor-ipmi-flash-bmc-verify.target",
+ "path" : "/tmp/bmc.verify"
+ },
+ "update" : {
+ "type" : "reboot"
+ }
+ }
+ }]
+ )"_json;
+
+ auto h = buildHandlerFromJson(j2);
+ EXPECT_EQ(h[0].blobId, "/flash/image");
+ EXPECT_FALSE(h[0].handler == nullptr);
+ EXPECT_FALSE(h[0].actions == nullptr);
+ EXPECT_FALSE(h[0].actions->preparation == nullptr);
+ EXPECT_FALSE(h[0].actions->verification == nullptr);
+ EXPECT_FALSE(h[0].actions->update == nullptr);
+}
+
+TEST(FirmwareJsonTest, VerifyMultipleHandlersReturned)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/image",
+ "handler" : {
+ "type" : "file",
+ "path" : "/run/initramfs/bmc-image"
+ },
+ "actions" : {
+ "preparation" : {
+ "type" : "systemd",
+ "unit" : "phosphor-ipmi-flash-bmc-prepare.target"
+ },
+ "verification" : {
+ "type" : "fileSystemdVerify",
+ "unit" : "phosphor-ipmi-flash-bmc-verify.target",
+ "path" : "/tmp/bmc.verify"
+ },
+ "update" : {
+ "type" : "systemd",
+ "unit" : "phosphor-ipmi-flash-bmc-update.target"
+ }
+ }
+ },
+ {
+ "blob" : "/flash/bios",
+ "handler" : {
+ "type" : "file",
+ "path" : "/run/initramfs/bmc-image"
+ },
+ "actions" : {
+ "preparation" : {
+ "type" : "systemd",
+ "unit" : "phosphor-ipmi-flash-bmc-prepare.target"
+ },
+ "verification" : {
+ "type" : "fileSystemdVerify",
+ "unit" : "phosphor-ipmi-flash-bmc-verify.target",
+ "path" : "/tmp/bmc.verify"
+ },
+ "update" : {
+ "type" : "systemd",
+ "unit" : "phosphor-ipmi-flash-bmc-update.target"
+ }
+ }
+ }]
+ )"_json;
+
+ auto h = buildHandlerFromJson(j2);
+ EXPECT_EQ(h.size(), 2);
+ EXPECT_EQ(h[0].blobId, "/flash/image");
+ EXPECT_EQ(h[1].blobId, "/flash/bios");
+}
+
+TEST(FirmwareJsonTest, VerifyValidSingleNonReboot)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/image",
+ "handler" : {
+ "type" : "file",
+ "path" : "/run/initramfs/bmc-image"
+ },
+ "actions" : {
+ "preparation" : {
+ "type" : "systemd",
+ "unit" : "phosphor-ipmi-flash-bmc-prepare.target"
+ },
+ "verification" : {
+ "type" : "fileSystemdVerify",
+ "unit" : "phosphor-ipmi-flash-bmc-verify.target",
+ "path" : "/tmp/bmc.verify"
+ },
+ "update" : {
+ "type" : "systemd",
+ "unit" : "phosphor-ipmi-flash-bmc-update.target"
+ }
+ }
+ }]
+ )"_json;
+
+ auto h = buildHandlerFromJson(j2);
+ EXPECT_EQ(h[0].blobId, "/flash/image");
+ EXPECT_FALSE(h[0].handler == nullptr);
+ EXPECT_FALSE(h[0].actions == nullptr);
+ EXPECT_FALSE(h[0].actions->preparation == nullptr);
+ EXPECT_FALSE(h[0].actions->verification == nullptr);
+ auto verifier = reinterpret_cast<SystemdWithStatusFile*>(
+ h[0].actions->verification.get());
+ EXPECT_THAT(verifier->getMode(), "replace");
+ EXPECT_FALSE(h[0].actions->update == nullptr);
+ auto updater = reinterpret_cast<SystemdNoFile*>(h[0].actions->update.get());
+ EXPECT_THAT(updater->getMode(), "replace");
+}
+
+TEST(FirmwareJsonTest, VerifyValidWithModes)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/image",
+ "handler" : {
+ "type" : "file",
+ "path" : "/run/initramfs/bmc-image"
+ },
+ "actions" : {
+ "preparation" : {
+ "type" : "systemd",
+ "unit" : "phosphor-ipmi-flash-bmc-prepare.target"
+ },
+ "verification" : {
+ "type" : "fileSystemdVerify",
+ "unit" : "phosphor-ipmi-flash-bmc-verify.target",
+ "path" : "/tmp/bmc.verify",
+ "mode" : "replace-nope"
+ },
+ "update" : {
+ "type" : "systemd",
+ "mode" : "replace-fake",
+ "unit" : "phosphor-ipmi-flash-bmc-update.target"
+ }
+ }
+ }]
+ )"_json;
+
+ auto h = buildHandlerFromJson(j2);
+ EXPECT_EQ(h[0].blobId, "/flash/image");
+ EXPECT_FALSE(h[0].handler == nullptr);
+ EXPECT_FALSE(h[0].actions == nullptr);
+ EXPECT_FALSE(h[0].actions->preparation == nullptr);
+ EXPECT_FALSE(h[0].actions->verification == nullptr);
+ auto verifier = reinterpret_cast<SystemdWithStatusFile*>(
+ h[0].actions->verification.get());
+ EXPECT_THAT(verifier->getMode(), "replace-nope");
+ EXPECT_FALSE(h[0].actions->update == nullptr);
+ auto updater = reinterpret_cast<SystemdNoFile*>(h[0].actions->update.get());
+ EXPECT_THAT(updater->getMode(), "replace-fake");
+}
+
+TEST(FirmwareJsonTest, VerifyValidUpdateWithFilePath)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/image",
+ "handler" : {
+ "type" : "file",
+ "path" : "/run/initramfs/bmc-image"
+ },
+ "actions" : {
+ "preparation" : {
+ "type" : "systemd",
+ "unit" : "phosphor-ipmi-flash-bmc-prepare.target"
+ },
+ "verification" : {
+ "type" : "fileSystemdVerify",
+ "unit" : "phosphor-ipmi-flash-bmc-verify.target",
+ "path" : "/tmp/bmc.verify",
+ "mode" : "replace-nope"
+ },
+ "update" : {
+ "type" : "fileSystemdUpdate",
+ "mode" : "replace-fake",
+ "unit" : "phosphor-ipmi-flash-bmc-update.target",
+ "path" : "/tmp/update.verify"
+ }
+ }
+ }]
+ )"_json;
+
+ auto h = buildHandlerFromJson(j2);
+ EXPECT_EQ(h[0].blobId, "/flash/image");
+ EXPECT_FALSE(h[0].handler == nullptr);
+ EXPECT_FALSE(h[0].actions == nullptr);
+ EXPECT_FALSE(h[0].actions->preparation == nullptr);
+ EXPECT_FALSE(h[0].actions->verification == nullptr);
+ auto verifier = reinterpret_cast<SystemdWithStatusFile*>(
+ h[0].actions->verification.get());
+ EXPECT_THAT(verifier->getMode(), "replace-nope");
+ EXPECT_FALSE(h[0].actions->update == nullptr);
+ auto updater =
+ reinterpret_cast<SystemdWithStatusFile*>(h[0].actions->update.get());
+ EXPECT_THAT(updater->getMode(), "replace-fake");
+}
+
+TEST(FirmwareJsonTest, VerifySkipFields)
+{
+ // In this configuration, nothing happens because all actions are set to
+ // skip.
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/image",
+ "handler" : {
+ "type" : "file",
+ "path" : "/run/initramfs/bmc-image"
+ },
+ "actions" : {
+ "preparation" : {
+ "type" : "skip"
+ },
+ "verification" : {
+ "type" : "skip"
+ },
+ "update" : {
+ "type" : "skip"
+ }
+ }
+ }]
+ )"_json;
+
+ auto h = buildHandlerFromJson(j2);
+ EXPECT_EQ(h[0].blobId, "/flash/image");
+ EXPECT_FALSE(h[0].handler == nullptr);
+ EXPECT_FALSE(h[0].actions == nullptr);
+ EXPECT_FALSE(h[0].actions->preparation == nullptr);
+ EXPECT_FALSE(h[0].actions->verification == nullptr);
+ EXPECT_FALSE(h[0].actions->update == nullptr);
+}
+
+} // namespace
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/firmware_multiplebundle_unittest.cpp b/bmc/firmware-handler/test/firmware_multiplebundle_unittest.cpp
new file mode 100644
index 0000000..20c63a9
--- /dev/null
+++ b/bmc/firmware-handler/test/firmware_multiplebundle_unittest.cpp
@@ -0,0 +1,161 @@
+/* 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
+ {
+ std::unique_ptr<ImageHandlerInterface> image =
+ std::make_unique<ImageHandlerMock>();
+ hashImageMock = reinterpret_cast<ImageHandlerMock*>(image.get());
+ blobs.emplace_back(hashBlobId, std::move(image));
+
+ image = std::make_unique<ImageHandlerMock>();
+ staticImageMock = reinterpret_cast<ImageHandlerMock*>(image.get());
+ blobs.emplace_back(staticLayoutBlobId, std::move(image));
+
+ image = std::make_unique<ImageHandlerMock>();
+ biosImageMock = reinterpret_cast<ImageHandlerMock*>(image.get());
+ blobs.emplace_back(biosBlobId, std::move(image));
+
+ 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[biosBlobId] = std::move(biosPack);
+
+ std::vector<DataHandlerPack> data;
+ data.emplace_back(FirmwareFlags::UpdateFlags::ipmi, nullptr);
+
+ handler = FirmwareBlobHandler::CreateFirmwareBlobHandler(
+ std::move(blobs), std::move(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::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;
+};
+
+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(biosBlobId)).Times(0);
+ EXPECT_FALSE(handler->open(session, flags, biosBlobId));
+}
+
+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(biosBlobId)).WillOnce(Return(true));
+ EXPECT_TRUE(handler->open(session, flags, biosBlobId));
+
+ expectedState(FirmwareBlobHandler::UpdateState::uploadInProgress);
+
+ EXPECT_CALL(*biosImageMock, close()).WillOnce(Return());
+ handler->close(session);
+}
+
+} // namespace
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/firmware_open_unittest.cpp b/bmc/firmware-handler/test/firmware_open_unittest.cpp
new file mode 100644
index 0000000..274ea14
--- /dev/null
+++ b/bmc/firmware-handler/test/firmware_open_unittest.cpp
@@ -0,0 +1,59 @@
+#include "firmware_handler.hpp"
+#include "flags.hpp"
+#include "image_mock.hpp"
+#include "triggerable_mock.hpp"
+#include "util.hpp"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+
+class FirmwareOpenFailTest : public ::testing::TestWithParam<std::uint16_t>
+{};
+
+TEST_P(FirmwareOpenFailTest, WithFlags)
+{
+ std::vector<DataHandlerPack> data;
+ data.emplace_back(FirmwareFlags::UpdateFlags::ipmi, nullptr);
+ data.emplace_back(FirmwareFlags::UpdateFlags::p2a, nullptr);
+ data.emplace_back(FirmwareFlags::UpdateFlags::lpc, nullptr);
+
+ std::vector<HandlerPack> blobs;
+ blobs.emplace_back(hashBlobId, std::make_unique<ImageHandlerMock>());
+ blobs.emplace_back("asdf", std::make_unique<ImageHandlerMock>());
+
+ auto handler = FirmwareBlobHandler::CreateFirmwareBlobHandler(
+ std::move(blobs), std::move(data), CreateActionMap("asdf"));
+
+ EXPECT_FALSE(handler->open(0, GetParam(), "asdf"));
+}
+
+const std::vector<std::uint16_t> OpenFailParams{
+ /* These first 4 fail because they don't have the "write" flag */
+ 0b000 << 8,
+ 0b110 << 8,
+ 0b101 << 8,
+ 0b011 << 8,
+ /* Next 1 doesn't specify any transport */
+ blobs::OpenFlags::write | 0b000 << 8,
+ /* Next 3 specify 2 reserved transport bits at the same time. This isn't
+ * allowed because older code expects these first 3 bits to be mutually
+ * exclusive.
+ */
+ blobs::OpenFlags::write | 0b110 << 8,
+ blobs::OpenFlags::write | 0b101 << 8,
+ blobs::OpenFlags::write | 0b011 << 8,
+};
+
+INSTANTIATE_TEST_CASE_P(WithFlags, FirmwareOpenFailTest,
+ ::testing::ValuesIn(OpenFailParams));
+
+} // namespace
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/firmware_sessionstat_unittest.cpp b/bmc/firmware-handler/test/firmware_sessionstat_unittest.cpp
new file mode 100644
index 0000000..0736293
--- /dev/null
+++ b/bmc/firmware-handler/test/firmware_sessionstat_unittest.cpp
@@ -0,0 +1,71 @@
+#include "data_mock.hpp"
+#include "firmware_handler.hpp"
+#include "firmware_unittest.hpp"
+#include "image_mock.hpp"
+#include "triggerable_mock.hpp"
+#include "util.hpp"
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+
+using ::testing::Return;
+
+class FirmwareSessionStateTestIpmiOnly : public IpmiOnlyFirmwareTest
+{};
+
+class FirmwareSessionStateTestLpc : public FakeLpcFirmwareTest
+{};
+
+TEST_F(FirmwareSessionStateTestIpmiOnly, DataTypeIpmiNoMetadata)
+{
+ /* Verifying running stat if the type of data session is IPMI returns no
+ * metadata.
+ */
+ EXPECT_CALL(*imageMock, open("asdf")).WillOnce(Return(true));
+
+ EXPECT_TRUE(handler->open(
+ 0, blobs::OpenFlags::write | FirmwareFlags::UpdateFlags::ipmi, "asdf"));
+
+ int size = 512;
+ EXPECT_CALL(*imageMock, getSize()).WillOnce(Return(size));
+
+ blobs::BlobMeta meta;
+ EXPECT_TRUE(handler->stat(0, &meta));
+ EXPECT_EQ(meta.blobState,
+ blobs::OpenFlags::write | FirmwareFlags::UpdateFlags::ipmi);
+ EXPECT_EQ(meta.size, size);
+ EXPECT_EQ(meta.metadata.size(), 0);
+}
+
+TEST_F(FirmwareSessionStateTestLpc, DataTypeP2AReturnsMetadata)
+{
+ /* Really any type that isn't IPMI can return metadata, but we only expect
+ * P2A to for now. Later, LPC may have reason to provide data, and can by
+ * simply implementing read().
+ */
+ EXPECT_CALL(*dataMock, open()).WillOnce(Return(true));
+ EXPECT_CALL(*imageMock, open("asdf")).WillOnce(Return(true));
+
+ EXPECT_TRUE(handler->open(
+ 0, blobs::OpenFlags::write | FirmwareFlags::UpdateFlags::lpc, "asdf"));
+
+ int size = 512;
+ EXPECT_CALL(*imageMock, getSize()).WillOnce(Return(size));
+ std::vector<std::uint8_t> mBytes = {0x01, 0x02};
+ EXPECT_CALL(*dataMock, readMeta()).WillOnce(Return(mBytes));
+
+ blobs::BlobMeta meta;
+ EXPECT_TRUE(handler->stat(0, &meta));
+ EXPECT_EQ(meta.blobState,
+ blobs::OpenFlags::write | FirmwareFlags::UpdateFlags::lpc);
+ EXPECT_EQ(meta.size, size);
+ EXPECT_EQ(meta.metadata.size(), mBytes.size());
+ EXPECT_EQ(meta.metadata[0], mBytes[0]);
+ EXPECT_EQ(meta.metadata[1], mBytes[1]);
+}
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/firmware_skip_unittest.cpp b/bmc/firmware-handler/test/firmware_skip_unittest.cpp
new file mode 100644
index 0000000..a549582
--- /dev/null
+++ b/bmc/firmware-handler/test/firmware_skip_unittest.cpp
@@ -0,0 +1,36 @@
+#include "skip_action.hpp"
+#include "status.hpp"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+
+TEST(SkipActionTest, ValidateTriggerReturnsTrue)
+{
+ SkipAction skip;
+ EXPECT_TRUE(skip.trigger());
+ EXPECT_TRUE(skip.trigger());
+}
+
+TEST(SkipActionTest, ValidateStatusAlwaysSuccess)
+{
+ SkipAction skip;
+ EXPECT_EQ(ActionStatus::success, skip.status());
+ EXPECT_TRUE(skip.trigger());
+ EXPECT_EQ(ActionStatus::success, skip.status());
+}
+
+TEST(SkipActionTest, AbortHasNoImpactOnStatus)
+{
+ SkipAction skip;
+ EXPECT_EQ(ActionStatus::success, skip.status());
+ skip.abort();
+ EXPECT_EQ(ActionStatus::success, skip.status());
+}
+
+} // namespace
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/firmware_stat_unittest.cpp b/bmc/firmware-handler/test/firmware_stat_unittest.cpp
new file mode 100644
index 0000000..fcb2d34
--- /dev/null
+++ b/bmc/firmware-handler/test/firmware_stat_unittest.cpp
@@ -0,0 +1,45 @@
+#include "firmware_handler.hpp"
+#include "flags.hpp"
+#include "image_mock.hpp"
+#include "triggerable_mock.hpp"
+#include "util.hpp"
+
+#include <memory>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+
+/* This test ensures the stat() method preserves compatibility with older host
+ * tools by reporting that all transports are supported. */
+TEST(FirmwareHandlerStatTest, StatOnInactiveBlobIDReturnsAllTransports)
+{
+ /* Test that the metadata information returned matches expectations for this
+ * case.
+ *
+ * canHandle has already been called at this point, so we don't need to test
+ * the input for this function.
+ */
+
+ std::vector<HandlerPack> blobs;
+ blobs.emplace_back(hashBlobId, std::make_unique<ImageHandlerMock>());
+ blobs.emplace_back("asdf", std::make_unique<ImageHandlerMock>());
+
+ std::vector<DataHandlerPack> data;
+ data.emplace_back(FirmwareFlags::UpdateFlags::ipmi, nullptr);
+
+ auto handler = FirmwareBlobHandler::CreateFirmwareBlobHandler(
+ std::move(blobs), std::move(data), CreateActionMap("asdf"));
+
+ blobs::BlobMeta meta;
+ EXPECT_TRUE(handler->stat("asdf", &meta));
+ /* All transport flags are set */
+ EXPECT_EQ(0xff00, meta.blobState);
+}
+
+} // namespace
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/firmware_state_notyetstarted_tarball_unittest.cpp b/bmc/firmware-handler/test/firmware_state_notyetstarted_tarball_unittest.cpp
new file mode 100644
index 0000000..e666395
--- /dev/null
+++ b/bmc/firmware-handler/test/firmware_state_notyetstarted_tarball_unittest.cpp
@@ -0,0 +1,104 @@
+/**
+ * The goal of these tests is to verify opening the ubi tarball changes state
+ * as expected and does not regress.
+ */
+#include "firmware_handler.hpp"
+#include "firmware_unittest.hpp"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+
+using ::testing::UnorderedElementsAreArray;
+
+class FirmwareHandlerNotYetStartedUbitTest : public ::testing::Test
+{
+ protected:
+ void SetUp() override
+ {
+ std::unique_ptr<ImageHandlerInterface> image =
+ std::make_unique<ImageHandlerMock>();
+ hashImageMock = reinterpret_cast<ImageHandlerMock*>(image.get());
+ blobs.emplace_back(hashBlobId, std::move(image));
+
+ image = std::make_unique<ImageHandlerMock>();
+ imageMock = reinterpret_cast<ImageHandlerMock*>(image.get());
+ blobs.emplace_back(ubiTarballBlobId, std::move(image));
+
+ std::unique_ptr<TriggerableActionInterface> verifyMock =
+ std::make_unique<TriggerMock>();
+ verifyMockPtr = reinterpret_cast<TriggerMock*>(verifyMock.get());
+
+ std::unique_ptr<TriggerableActionInterface> updateMock =
+ std::make_unique<TriggerMock>();
+ updateMockPtr = reinterpret_cast<TriggerMock*>(updateMock.get());
+
+ std::unique_ptr<ActionPack> actionPack = std::make_unique<ActionPack>();
+ actionPack->preparation = CreateTriggerMock();
+ actionPack->verification = std::move(verifyMock);
+ actionPack->update = std::move(updateMock);
+
+ ActionMap packs;
+ packs[ubiTarballBlobId] = std::move(actionPack);
+
+ std::vector<DataHandlerPack> data;
+ data.emplace_back(FirmwareFlags::UpdateFlags::ipmi, nullptr);
+
+ handler = FirmwareBlobHandler::CreateFirmwareBlobHandler(
+ std::move(blobs), std::move(data), std::move(packs));
+ }
+
+ void expectedState(FirmwareBlobHandler::UpdateState state)
+ {
+ auto realHandler = dynamic_cast<FirmwareBlobHandler*>(handler.get());
+ EXPECT_EQ(state, realHandler->getCurrentState());
+ }
+
+ void openToInProgress(const std::string& blobId)
+ {
+ if (blobId == hashBlobId)
+ {
+ EXPECT_CALL(*hashImageMock, open(blobId)).WillOnce(Return(true));
+ }
+ else
+ {
+ EXPECT_CALL(*imageMock, open(blobId)).WillOnce(Return(true));
+ }
+ EXPECT_TRUE(handler->open(session, flags, blobId));
+ expectedState(FirmwareBlobHandler::UpdateState::uploadInProgress);
+ }
+
+ ImageHandlerMock *hashImageMock, *imageMock;
+ std::vector<HandlerPack> blobs;
+ std::unique_ptr<blobs::GenericBlobInterface> handler;
+ TriggerMock* verifyMockPtr;
+ TriggerMock* updateMockPtr;
+
+ std::uint16_t session = 1;
+ std::uint16_t flags =
+ blobs::OpenFlags::write | FirmwareFlags::UpdateFlags::ipmi;
+};
+
+TEST_F(FirmwareHandlerNotYetStartedUbitTest,
+ OpeningTarballMovesToUploadInProgress)
+{
+ expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
+
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray({hashBlobId, ubiTarballBlobId}));
+
+ openToInProgress(ubiTarballBlobId);
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(
+ {hashBlobId, ubiTarballBlobId, activeImageBlobId}));
+}
+
+} // namespace
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/firmware_state_notyetstarted_unittest.cpp b/bmc/firmware-handler/test/firmware_state_notyetstarted_unittest.cpp
new file mode 100644
index 0000000..970ae2f
--- /dev/null
+++ b/bmc/firmware-handler/test/firmware_state_notyetstarted_unittest.cpp
@@ -0,0 +1,130 @@
+/**
+ * The goal of these tests is to verify the behavior of all blob commands given
+ * the current state is notYetStarted. The initial state.
+ */
+#include "firmware_handler.hpp"
+#include "firmware_unittest.hpp"
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+
+using ::testing::Return;
+using ::testing::UnorderedElementsAreArray;
+
+class FirmwareHandlerNotYetStartedTest : public IpmiOnlyFirmwareStaticTest
+{};
+
+/*
+ * There are the following calls (parameters may vary):
+ * Note: you cannot have a session yet, so only commands that don't take a
+ * session parameter are valid. Once you open() from this state, we will vary
+ * you transition out of this state (ensuring the above is true). Technically
+ * the firmware handler receives the session number with open(), but the blob
+ * manager is providing this normally.
+ *
+ * canHandleBlob
+ * getBlobIds
+ * deleteBlob
+ * stat
+ * open
+ *
+ * canHandleBlob is just a count check (or something similar) against what is
+ * returned by getBlobIds. It is tested in firmware_canhandle_unittest
+ */
+
+/*
+ * deleteBlob()
+ */
+TEST_F(FirmwareHandlerNotYetStartedTest, DeleteBlobInStateReturnsFalse)
+{
+ auto blobs = handler->getBlobIds();
+ for (const auto& b : blobs)
+ {
+ EXPECT_FALSE(handler->deleteBlob(b));
+ }
+}
+
+/* canHandleBlob, getBlobIds */
+TEST_F(FirmwareHandlerNotYetStartedTest, GetBlobListValidateListContents)
+{
+ /* By only checking that the hash and static blob ids are present to start
+ * with, we're also verifying others aren't.
+ */
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(startingBlobs));
+
+ /* Verify canHandleBlob is reading from the same list (basically) */
+ for (const auto& blob : startingBlobs)
+ {
+ EXPECT_TRUE(handler->canHandleBlob(blob));
+ }
+}
+
+/* stat(blob_id) */
+TEST_F(FirmwareHandlerNotYetStartedTest, StatEachBlobIdVerifyResults)
+{
+ /* In this original state, calling stat() on the blob ids will return the
+ * idle status
+ */
+
+ auto blobs = handler->getBlobIds();
+ for (const auto& blob : blobs)
+ {
+ blobs::BlobMeta meta = {};
+ EXPECT_TRUE(handler->stat(blob, &meta));
+ EXPECT_EQ(expectedIdleMeta, meta);
+ }
+}
+
+/* open(each blob id) (verifyblobid will no longer be available at this state.
+ */
+TEST_F(FirmwareHandlerNotYetStartedTest, OpenStaticImageFileVerifyStateChange)
+{
+ EXPECT_CALL(*imageMock2, open(staticLayoutBlobId)).WillOnce(Return(true));
+ EXPECT_CALL(*prepareMockPtr, trigger()).WillOnce(Return(true));
+
+ EXPECT_TRUE(handler->open(session, flags, staticLayoutBlobId));
+
+ expectedState(FirmwareBlobHandler::UpdateState::uploadInProgress);
+
+ EXPECT_TRUE(handler->canHandleBlob(activeImageBlobId));
+}
+
+TEST_F(FirmwareHandlerNotYetStartedTest, OpenHashFileVerifyStateChange)
+{
+ EXPECT_CALL(*hashImageMock, open(hashBlobId)).WillOnce(Return(true));
+ /* Opening the hash blob id doesn't trigger a preparation, only a firmware
+ * blob.
+ */
+ EXPECT_CALL(*prepareMockPtr, trigger()).Times(0);
+
+ EXPECT_TRUE(handler->open(session, flags, hashBlobId));
+
+ expectedState(FirmwareBlobHandler::UpdateState::uploadInProgress);
+
+ EXPECT_TRUE(handler->canHandleBlob(activeHashBlobId));
+}
+
+/*
+ * expire(session)
+ */
+TEST_F(FirmwareHandlerNotYetStartedTest, ExpireOnNotYetStartedAbortsProcess)
+{
+ ASSERT_TRUE(handler->expire(session));
+
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(startingBlobs));
+
+ expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
+}
+
+} // namespace
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/firmware_state_updatecompleted_unittest.cpp b/bmc/firmware-handler/test/firmware_state_updatecompleted_unittest.cpp
new file mode 100644
index 0000000..d002499
--- /dev/null
+++ b/bmc/firmware-handler/test/firmware_state_updatecompleted_unittest.cpp
@@ -0,0 +1,263 @@
+/* The goal of these tests is to verify the behavior of all blob commands given
+ * the current state is UpdateCompleted. This state is achieved as an exit from
+ * updateStarted.
+ *
+ * This can be reached with success or failure from an update, and is reached
+ * via a stat() call from updatedStarted.
+ */
+#include "firmware_handler.hpp"
+#include "firmware_unittest.hpp"
+#include "status.hpp"
+#include "util.hpp"
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+
+using ::testing::IsEmpty;
+using ::testing::UnorderedElementsAreArray;
+
+/*
+ * There are the following calls (parameters may vary):
+ * canHandleBlob(blob)
+ * getBlobIds
+ * deleteBlob(blob)
+ * stat(blob)
+ * stat(session)
+ * open(blob)
+ * close(session)
+ * writemeta(session)
+ * write(session)
+ * read(session)
+ * commit(session)
+ */
+
+class FirmwareHandlerUpdateCompletedTest : public IpmiOnlyFirmwareStaticTest
+{};
+
+/*
+ * open(blob)
+ */
+TEST_F(FirmwareHandlerUpdateCompletedTest,
+ AttemptToOpenFilesReturnsFailureAfterSuccess)
+{
+ /* In state updateCompleted a file is open, which means no others can be. */
+ getToUpdateCompleted(ActionStatus::success);
+
+ auto blobsToOpen = handler->getBlobIds();
+ for (const auto& blob : blobsToOpen)
+ {
+ EXPECT_FALSE(handler->open(session + 1, flags, blob));
+ }
+}
+
+/*
+ * stat(session)
+ */
+TEST_F(FirmwareHandlerUpdateCompletedTest,
+ CallingStatSessionAfterCompletedSuccessReturnsStateWithoutRechecking)
+{
+ getToUpdateCompleted(ActionStatus::success);
+ EXPECT_CALL(*updateMockPtr, status()).Times(0);
+
+ blobs::BlobMeta meta, expectedMeta = {};
+ expectedMeta.size = 0;
+ expectedMeta.blobState = flags | blobs::StateFlags::committed;
+ expectedMeta.metadata.push_back(
+ static_cast<std::uint8_t>(ActionStatus::success));
+
+ EXPECT_TRUE(handler->stat(session, &meta));
+ EXPECT_EQ(expectedMeta, meta);
+ expectedState(FirmwareBlobHandler::UpdateState::updateCompleted);
+}
+
+TEST_F(FirmwareHandlerUpdateCompletedTest,
+ CallingStatSessionAfterCompletedFailureReturnsStateWithoutRechecking)
+{
+ getToUpdateCompleted(ActionStatus::failed);
+ EXPECT_CALL(*updateMockPtr, status()).Times(0);
+
+ blobs::BlobMeta meta, expectedMeta = {};
+ expectedMeta.size = 0;
+ expectedMeta.blobState = flags | blobs::StateFlags::commit_error;
+ expectedMeta.metadata.push_back(
+ static_cast<std::uint8_t>(ActionStatus::failed));
+
+ EXPECT_TRUE(handler->stat(session, &meta));
+ EXPECT_EQ(expectedMeta, meta);
+ expectedState(FirmwareBlobHandler::UpdateState::updateCompleted);
+}
+
+/*
+ * stat(blob)
+ */
+TEST_F(FirmwareHandlerUpdateCompletedTest, StatOnActiveImageReturnsFailure)
+{
+ getToUpdateCompleted(ActionStatus::success);
+
+ ASSERT_TRUE(handler->canHandleBlob(activeImageBlobId));
+
+ blobs::BlobMeta meta;
+ EXPECT_FALSE(handler->stat(activeImageBlobId, &meta));
+}
+
+TEST_F(FirmwareHandlerUpdateCompletedTest, StatOnUpdateBlobReturnsFailure)
+{
+ getToUpdateCompleted(ActionStatus::success);
+
+ ASSERT_TRUE(handler->canHandleBlob(updateBlobId));
+
+ blobs::BlobMeta meta;
+ EXPECT_FALSE(handler->stat(updateBlobId, &meta));
+}
+
+TEST_F(FirmwareHandlerUpdateCompletedTest, StatOnNormalBlobsReturnsSuccess)
+{
+ getToUpdateCompleted(ActionStatus::success);
+
+ std::vector<std::string> testBlobs = {staticLayoutBlobId, hashBlobId};
+ for (const auto& blob : testBlobs)
+ {
+ ASSERT_TRUE(handler->canHandleBlob(blob));
+
+ blobs::BlobMeta meta = {};
+ EXPECT_TRUE(handler->stat(blob, &meta));
+ EXPECT_EQ(expectedIdleMeta, meta);
+ }
+}
+
+/*
+ * writemeta(session)
+ */
+TEST_F(FirmwareHandlerUpdateCompletedTest, WriteMetaToUpdateBlobReturnsFailure)
+{
+ getToUpdateCompleted(ActionStatus::success);
+
+ EXPECT_FALSE(handler->writeMeta(session, 0, {0x01}));
+}
+
+/*
+ * write(session)
+ */
+TEST_F(FirmwareHandlerUpdateCompletedTest, WriteToUpdateBlobReturnsFailure)
+{
+ getToUpdateCompleted(ActionStatus::success);
+
+ EXPECT_FALSE(handler->write(session, 0, {0x01}));
+}
+
+/*
+ * commit(session) - returns failure
+ */
+TEST_F(FirmwareHandlerUpdateCompletedTest,
+ CommitOnUpdateBlobAfterSuccessReturnsFailure)
+{
+ getToUpdateCompleted(ActionStatus::success);
+
+ EXPECT_CALL(*updateMockPtr, trigger()).Times(0);
+ EXPECT_FALSE(handler->commit(session, {}));
+}
+
+TEST_F(FirmwareHandlerUpdateCompletedTest,
+ CommitOnUpdateBlobAfterFailureReturnsFailure)
+{
+ getToUpdateCompleted(ActionStatus::failed);
+
+ EXPECT_CALL(*updateMockPtr, trigger()).Times(0);
+ EXPECT_FALSE(handler->commit(session, {}));
+}
+
+/*
+ * read(session) - nothing to read here.
+ */
+TEST_F(FirmwareHandlerUpdateCompletedTest, ReadingFromUpdateBlobReturnsNothing)
+{
+ getToUpdateCompleted(ActionStatus::success);
+
+ EXPECT_THAT(handler->read(session, 0, 1), IsEmpty());
+}
+
+/*
+ * getBlobIds
+ * canHandleBlob(blob)
+ */
+TEST_F(FirmwareHandlerUpdateCompletedTest, GetBlobListProvidesExpectedBlobs)
+{
+ getToUpdateCompleted(ActionStatus::success);
+
+ EXPECT_THAT(
+ handler->getBlobIds(),
+ UnorderedElementsAreArray(
+ {updateBlobId, hashBlobId, activeImageBlobId, staticLayoutBlobId}));
+}
+
+/*
+ * close(session) - closes everything out and returns to state not-yet-started.
+ * It doesn't go and clean out any update artifacts that may be present on the
+ * system. It's up to the update implementation to deal with this before
+ * marking complete.
+ */
+TEST_F(FirmwareHandlerUpdateCompletedTest,
+ ClosingOnUpdateBlobIdAfterSuccessReturnsToNotYetStartedAndCleansBlobList)
+{
+ getToUpdateCompleted(ActionStatus::success);
+
+ handler->close(session);
+ expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
+
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(startingBlobs));
+}
+
+TEST_F(FirmwareHandlerUpdateCompletedTest,
+ ClosingOnUpdateBlobIdAfterFailureReturnsToNotYetStartedAndCleansBlobList)
+{
+ getToUpdateCompleted(ActionStatus::failed);
+
+ handler->close(session);
+ expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
+
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(startingBlobs));
+}
+
+/*
+ * deleteBlob(blob)
+ */
+TEST_F(FirmwareHandlerUpdateCompletedTest, DeleteBlobReturnsFalse)
+{
+ /* Try deleting all blobs, it doesn't really matter which though because you
+ * cannot close out an open session, therefore you must fail to delete
+ * anything unless everything is closed.
+ */
+ getToUpdateCompleted(ActionStatus::success);
+
+ auto blobs = handler->getBlobIds();
+ for (const auto& b : blobs)
+ {
+ EXPECT_FALSE(handler->deleteBlob(b));
+ }
+}
+
+/*
+ * expire(session)
+ */
+TEST_F(FirmwareHandlerUpdateCompletedTest, ExpireOnUpdateCompletedAbortsProcess)
+{
+ getToUpdateCompleted(ActionStatus::success);
+
+ ASSERT_TRUE(handler->expire(session));
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(startingBlobs));
+
+ expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
+}
+
+} // namespace
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/firmware_state_updatepending_unittest.cpp b/bmc/firmware-handler/test/firmware_state_updatepending_unittest.cpp
new file mode 100644
index 0000000..5f43259
--- /dev/null
+++ b/bmc/firmware-handler/test/firmware_state_updatepending_unittest.cpp
@@ -0,0 +1,309 @@
+/* The goal of these tests is to verify the behavior of all blob commands given
+ * the current state is updatePending. This state is achieved as an exit from
+ * verificationCompleted.
+ */
+#include "firmware_handler.hpp"
+#include "firmware_unittest.hpp"
+#include "status.hpp"
+#include "util.hpp"
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+
+using ::testing::IsEmpty;
+using ::testing::Return;
+using ::testing::UnorderedElementsAreArray;
+
+/*
+ * There are the following calls (parameters may vary):
+ * canHandleBlob(blob)
+ * getBlobIds
+ * deleteBlob(blob)
+ * stat(blob)
+ * stat(session)
+ * open(blob)
+ * close(session)
+ * writemeta(session)
+ * write(session)
+ * read(session)
+ * commit(session)
+ *
+ * Testing canHandleBlob is uninteresting in this state. Getting the BlobIDs
+ * will inform what canHandleBlob will return.
+ */
+
+class FirmwareHandlerUpdatePendingTest : public IpmiOnlyFirmwareStaticTest
+{};
+
+/*
+ * There are the following calls (parameters may vary):
+ * canHandleBlob(blob)
+ * getBlobIds
+ */
+TEST_F(FirmwareHandlerUpdatePendingTest, GetBlobsListHasExpectedValues)
+{
+ getToUpdatePending();
+
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray({updateBlobId, activeImageBlobId,
+ hashBlobId, staticLayoutBlobId}));
+}
+
+/*
+ * open(blob) - because updatePending is in a fileOpen==false state, one can
+ * then open blobs. However, because we're in a special state, we will restrict
+ * them s.t. they can only open the updateBlobId.
+ */
+TEST_F(FirmwareHandlerUpdatePendingTest,
+ OpenUpdateBlobIdIsSuccessfulAndDoesNotChangeState)
+{
+ getToUpdatePending();
+
+ /* Opening the update blob isn't interesting, except it's required for
+ * commit() which triggers the update process.
+ */
+ EXPECT_TRUE(handler->open(session, flags, updateBlobId));
+ expectedState(FirmwareBlobHandler::UpdateState::updatePending);
+}
+
+TEST_F(FirmwareHandlerUpdatePendingTest, OpenAnyBlobOtherThanUpdateFails)
+{
+ getToUpdatePending();
+
+ auto blobs = handler->getBlobIds();
+ for (const auto& blob : blobs)
+ {
+ if (blob == updateBlobId)
+ {
+ continue;
+ }
+ EXPECT_FALSE(handler->open(session, flags, blob));
+ }
+}
+
+/*
+ * close(session) - close from this state is uninteresting.
+ */
+TEST_F(FirmwareHandlerUpdatePendingTest, CloseUpdateBlobDoesNotChangeState)
+{
+ /* Verify nothing changes when one just opens, then closes the updateBlobId.
+ */
+ getToUpdatePending();
+
+ EXPECT_TRUE(handler->open(session, flags, updateBlobId));
+
+ handler->close(session);
+
+ expectedState(FirmwareBlobHandler::UpdateState::updatePending);
+ EXPECT_TRUE(handler->canHandleBlob(updateBlobId));
+}
+
+/*
+ * writemeta(session) - this will return failure.
+ */
+TEST_F(FirmwareHandlerUpdatePendingTest, WriteMetaToUpdateBlobReturnsFailure)
+{
+ getToUpdatePending();
+
+ EXPECT_TRUE(handler->open(session, flags, updateBlobId));
+ EXPECT_FALSE(handler->writeMeta(session, 0, {0x01}));
+}
+
+/*
+ * write(session)
+ */
+TEST_F(FirmwareHandlerUpdatePendingTest, WriteToUpdateBlobReturnsFailure)
+{
+ getToUpdatePending();
+
+ EXPECT_TRUE(handler->open(session, flags, updateBlobId));
+ EXPECT_FALSE(handler->write(session, 0, {0x01}));
+}
+
+/*
+ * read(session)
+ */
+TEST_F(FirmwareHandlerUpdatePendingTest, ReadFromUpdateBlobIdReturnsEmpty)
+{
+ getToUpdatePending();
+ EXPECT_THAT(handler->read(session, 0, 1), IsEmpty());
+}
+
+/*
+ * stat(blob)
+ */
+TEST_F(FirmwareHandlerUpdatePendingTest, StatOnActiveImageReturnsFailure)
+{
+ getToUpdatePending();
+ ASSERT_TRUE(handler->canHandleBlob(activeImageBlobId));
+
+ blobs::BlobMeta meta;
+ EXPECT_FALSE(handler->stat(activeImageBlobId, &meta));
+}
+
+TEST_F(FirmwareHandlerUpdatePendingTest, StatOnUpdateBlobReturnsFailure)
+{
+ getToUpdatePending();
+ ASSERT_TRUE(handler->canHandleBlob(updateBlobId));
+
+ blobs::BlobMeta meta;
+ EXPECT_FALSE(handler->stat(updateBlobId, &meta));
+}
+
+TEST_F(FirmwareHandlerUpdatePendingTest, StatOnNormalBlobsReturnsSuccess)
+{
+ getToUpdatePending();
+
+ for (const auto& blob : startingBlobs)
+ {
+ ASSERT_TRUE(handler->canHandleBlob(blob));
+
+ blobs::BlobMeta meta = {};
+ EXPECT_TRUE(handler->stat(blob, &meta));
+ EXPECT_EQ(expectedIdleMeta, meta);
+ }
+}
+
+/*
+ * stat(session)
+ * In this case, you can open updateBlobId without changing state, therefore,
+ * let's call stat() against a session against this file. This done, ahead of
+ * commit() should report the state as "other."
+ */
+TEST_F(FirmwareHandlerUpdatePendingTest,
+ SessionStatOnUpdateBlobIdReturnsFailure)
+{
+ getToUpdatePending();
+ EXPECT_TRUE(handler->open(session, flags, updateBlobId));
+ expectedState(FirmwareBlobHandler::UpdateState::updatePending);
+
+ blobs::BlobMeta meta, expectedMeta = {};
+ expectedMeta.size = 0;
+ expectedMeta.blobState = flags;
+ expectedMeta.metadata.push_back(
+ static_cast<std::uint8_t>(ActionStatus::unknown));
+
+ EXPECT_TRUE(handler->stat(session, &meta));
+ EXPECT_EQ(expectedMeta, meta);
+ expectedState(FirmwareBlobHandler::UpdateState::updatePending);
+}
+
+/*
+ * commit(session)
+ */
+TEST_F(FirmwareHandlerUpdatePendingTest,
+ CommitOnUpdateBlobTriggersUpdateAndChangesState)
+{
+ /* Commit triggers the update mechanism (similarly for the verifyBlobId) and
+ * changes state to updateStarted.
+ */
+ getToUpdatePending();
+ EXPECT_TRUE(handler->open(session, flags, updateBlobId));
+ expectedState(FirmwareBlobHandler::UpdateState::updatePending);
+
+ EXPECT_CALL(*updateMockPtr, trigger()).WillOnce(Return(true));
+
+ EXPECT_TRUE(handler->commit(session, {}));
+ expectedState(FirmwareBlobHandler::UpdateState::updateStarted);
+}
+
+TEST_F(FirmwareHandlerUpdatePendingTest,
+ CommitOnUpdateBlobTriggersUpdateAndReturnsFailureDoesNotChangeState)
+{
+ getToUpdatePending();
+ EXPECT_TRUE(handler->open(session, flags, updateBlobId));
+ expectedState(FirmwareBlobHandler::UpdateState::updatePending);
+
+ EXPECT_CALL(*updateMockPtr, trigger()).WillOnce(Return(false));
+
+ EXPECT_FALSE(handler->commit(session, {}));
+ expectedState(FirmwareBlobHandler::UpdateState::updatePending);
+}
+
+/*
+ * deleteBlob(blob)
+ */
+TEST_F(FirmwareHandlerUpdatePendingTest, DeleteUpdateAbortsProcess)
+{
+ /* It doesn't matter what blob id is used to delete in the design, so just
+ * delete the update blob id
+ */
+ getToUpdatePending();
+
+ EXPECT_CALL(*updateMockPtr, abort()).Times(0);
+
+ ASSERT_TRUE(handler->canHandleBlob(updateBlobId));
+ EXPECT_TRUE(handler->deleteBlob(updateBlobId));
+
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(startingBlobs));
+ expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
+}
+
+TEST_F(FirmwareHandlerUpdatePendingTest, DeleteActiveImageAbortsProcess)
+{
+ getToUpdatePending();
+
+ EXPECT_CALL(*updateMockPtr, abort()).Times(0);
+
+ ASSERT_TRUE(handler->canHandleBlob(activeImageBlobId));
+ EXPECT_TRUE(handler->deleteBlob(activeImageBlobId));
+
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(startingBlobs));
+ expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
+}
+
+TEST_F(FirmwareHandlerUpdatePendingTest, DeleteStaticLayoutAbortsProcess)
+{
+ getToUpdatePending();
+
+ EXPECT_CALL(*updateMockPtr, abort()).Times(0);
+
+ ASSERT_TRUE(handler->canHandleBlob(staticLayoutBlobId));
+ EXPECT_TRUE(handler->deleteBlob(staticLayoutBlobId));
+
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(startingBlobs));
+ expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
+}
+
+TEST_F(FirmwareHandlerUpdatePendingTest, DeleteHashAbortsProcess)
+{
+ getToUpdatePending();
+
+ EXPECT_CALL(*updateMockPtr, abort()).Times(0);
+
+ ASSERT_TRUE(handler->canHandleBlob(hashBlobId));
+ EXPECT_TRUE(handler->deleteBlob(hashBlobId));
+
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(startingBlobs));
+ expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
+}
+
+/*
+ * expire(session)
+ */
+TEST_F(FirmwareHandlerUpdatePendingTest, ExpireOnUpdatePendingAborstsProcess)
+{
+ getToUpdatePending();
+
+ EXPECT_CALL(*updateMockPtr, abort()).Times(0);
+
+ ASSERT_TRUE(handler->expire(session));
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(startingBlobs));
+ expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
+}
+
+} // namespace
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/firmware_state_updatestarted_unittest.cpp b/bmc/firmware-handler/test/firmware_state_updatestarted_unittest.cpp
new file mode 100644
index 0000000..f187a8e
--- /dev/null
+++ b/bmc/firmware-handler/test/firmware_state_updatestarted_unittest.cpp
@@ -0,0 +1,257 @@
+/* The goal of these tests is to verify the behavior of all blob commands given
+ * the current state is updateStarted. This state is achieved as an exit from
+ * updatePending.
+ */
+#include "firmware_handler.hpp"
+#include "firmware_unittest.hpp"
+#include "status.hpp"
+#include "util.hpp"
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+
+using ::testing::IsEmpty;
+using ::testing::Return;
+using ::testing::UnorderedElementsAreArray;
+
+/*
+ * There are the following calls (parameters may vary):
+ * canHandleBlob(blob)
+ * getBlobIds
+ * deleteBlob(blob)
+ * stat(blob)
+ * stat(session)
+ * open(blob)
+ * close(session)
+ * writemeta(session)
+ * write(session)
+ * read(session)
+ * commit(session)
+ */
+
+class FirmwareHandlerUpdateStartedTest : public IpmiOnlyFirmwareStaticTest
+{};
+
+/*
+ * open(blob)
+ */
+TEST_F(FirmwareHandlerUpdateStartedTest, AttemptToOpenFilesReturnsFailure)
+{
+ /* In state updateStarted a file is open, which means no others can be. */
+ getToUpdateStarted();
+
+ auto blobsToOpen = handler->getBlobIds();
+ for (const auto& blob : blobsToOpen)
+ {
+ EXPECT_FALSE(handler->open(session + 1, flags, blob));
+ }
+}
+
+/* canHandleBlob(blob)
+ * getBlobIds
+ */
+TEST_F(FirmwareHandlerUpdateStartedTest, VerifyListOfBlobs)
+{
+ getToUpdateStarted();
+
+ EXPECT_THAT(
+ handler->getBlobIds(),
+ UnorderedElementsAreArray(
+ {updateBlobId, hashBlobId, activeImageBlobId, staticLayoutBlobId}));
+}
+
+/*
+ * deleteBlob(blob)
+ */
+TEST_F(FirmwareHandlerUpdateStartedTest, DeleteBlobReturnsFalse)
+{
+ /* Try deleting all blobs, it doesn't really matter which though because you
+ * cannot close out an open session, therefore you must fail to delete
+ * anything unless everything is closed.
+ */
+ getToUpdateStarted();
+ auto blobs = handler->getBlobIds();
+ for (const auto& b : blobs)
+ {
+ EXPECT_FALSE(handler->deleteBlob(b));
+ }
+}
+
+/*
+ * stat(blob)
+ */
+TEST_F(FirmwareHandlerUpdateStartedTest, StatOnActiveImageReturnsFailure)
+{
+ getToUpdateStarted();
+
+ ASSERT_TRUE(handler->canHandleBlob(activeImageBlobId));
+
+ blobs::BlobMeta meta;
+ EXPECT_FALSE(handler->stat(activeImageBlobId, &meta));
+}
+
+TEST_F(FirmwareHandlerUpdateStartedTest, StatOnUpdateBlobReturnsFailure)
+{
+ getToUpdateStarted();
+
+ ASSERT_TRUE(handler->canHandleBlob(updateBlobId));
+
+ blobs::BlobMeta meta;
+ EXPECT_FALSE(handler->stat(updateBlobId, &meta));
+}
+
+TEST_F(FirmwareHandlerUpdateStartedTest, StatOnNormalBlobsReturnsSuccess)
+{
+ getToUpdateStarted();
+
+ std::vector<std::string> testBlobs = {staticLayoutBlobId, hashBlobId};
+ for (const auto& blob : testBlobs)
+ {
+ ASSERT_TRUE(handler->canHandleBlob(blob));
+
+ blobs::BlobMeta meta = {};
+ EXPECT_TRUE(handler->stat(blob, &meta));
+ EXPECT_EQ(expectedIdleMeta, meta);
+ }
+}
+
+/*
+ * writemeta(session)
+ */
+TEST_F(FirmwareHandlerUpdateStartedTest, WriteMetaToUpdateBlobReturnsFailure)
+{
+ getToUpdateStarted();
+ EXPECT_FALSE(handler->writeMeta(session, 0, {0x01}));
+}
+
+/*
+ * write(session)
+ */
+TEST_F(FirmwareHandlerUpdateStartedTest, WriteToUpdateBlobReturnsFailure)
+{
+ getToUpdateStarted();
+ EXPECT_FALSE(handler->write(session, 0, {0x01}));
+}
+
+/*
+ * read(session)
+ */
+TEST_F(FirmwareHandlerUpdateStartedTest, ReadFromUpdateBlobReturnsEmpty)
+{
+ getToUpdateStarted();
+ EXPECT_THAT(handler->read(session, 0, 1), IsEmpty());
+}
+
+/*
+ * commit(session)
+ */
+TEST_F(FirmwareHandlerUpdateStartedTest,
+ CallingCommitShouldReturnTrueAndHaveNoEffect)
+{
+ getToUpdateStarted();
+ EXPECT_CALL(*updateMockPtr, trigger()).Times(0);
+
+ EXPECT_TRUE(handler->commit(session, {}));
+ expectedState(FirmwareBlobHandler::UpdateState::updateStarted);
+}
+
+/*
+ * stat(session) - this will trigger a check, and the state may change.
+ */
+TEST_F(FirmwareHandlerUpdateStartedTest,
+ CallStatChecksActionStatusReturnsRunningDoesNotChangeState)
+{
+ getToUpdateStarted();
+ EXPECT_CALL(*updateMockPtr, status())
+ .WillOnce(Return(ActionStatus::running));
+
+ blobs::BlobMeta meta, expectedMeta = {};
+ expectedMeta.size = 0;
+ expectedMeta.blobState = flags | blobs::StateFlags::committing;
+ expectedMeta.metadata.push_back(
+ static_cast<std::uint8_t>(ActionStatus::running));
+
+ EXPECT_TRUE(handler->stat(session, &meta));
+ EXPECT_EQ(expectedMeta, meta);
+ expectedState(FirmwareBlobHandler::UpdateState::updateStarted);
+}
+
+TEST_F(FirmwareHandlerUpdateStartedTest,
+ CallStatChecksActionStatusReturnsFailedChangesStateToCompleted)
+{
+ getToUpdateStarted();
+ EXPECT_CALL(*updateMockPtr, status())
+ .WillOnce(Return(ActionStatus::failed));
+
+ blobs::BlobMeta meta, expectedMeta = {};
+ expectedMeta.size = 0;
+ expectedMeta.blobState = flags | blobs::StateFlags::commit_error;
+ expectedMeta.metadata.push_back(
+ static_cast<std::uint8_t>(ActionStatus::failed));
+
+ EXPECT_TRUE(handler->stat(session, &meta));
+ EXPECT_EQ(expectedMeta, meta);
+ expectedState(FirmwareBlobHandler::UpdateState::updateCompleted);
+}
+
+TEST_F(FirmwareHandlerUpdateStartedTest,
+ CallStatChecksActionStatusReturnsSuccessChangesStateToCompleted)
+{
+ getToUpdateStarted();
+ EXPECT_CALL(*updateMockPtr, status())
+ .WillOnce(Return(ActionStatus::success));
+
+ blobs::BlobMeta meta, expectedMeta = {};
+ expectedMeta.size = 0;
+ expectedMeta.blobState = flags | blobs::StateFlags::committed;
+ expectedMeta.metadata.push_back(
+ static_cast<std::uint8_t>(ActionStatus::success));
+
+ EXPECT_TRUE(handler->stat(session, &meta));
+ EXPECT_EQ(expectedMeta, meta);
+ expectedState(FirmwareBlobHandler::UpdateState::updateCompleted);
+}
+
+/*
+ * close(session) - this will abort.
+ */
+TEST_F(FirmwareHandlerUpdateStartedTest, CloseOnUpdateDuringUpdateAbortsProcess)
+{
+ getToUpdateStarted();
+ EXPECT_CALL(*updateMockPtr, abort()).Times(1);
+
+ EXPECT_TRUE(handler->close(session));
+
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(startingBlobs));
+
+ expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
+}
+
+/*
+ * expire(session)
+ */
+TEST_F(FirmwareHandlerUpdateStartedTest,
+ ExpireOnUpdateDuringUpdateAbortsProcess)
+{
+ getToUpdateStarted();
+ EXPECT_CALL(*updateMockPtr, abort()).Times(0);
+
+ ASSERT_TRUE(handler->expire(session));
+
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(startingBlobs));
+
+ expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
+}
+
+} // namespace
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/firmware_state_uploadinprogress_unittest.cpp b/bmc/firmware-handler/test/firmware_state_uploadinprogress_unittest.cpp
new file mode 100644
index 0000000..c1a27fe
--- /dev/null
+++ b/bmc/firmware-handler/test/firmware_state_uploadinprogress_unittest.cpp
@@ -0,0 +1,295 @@
+/**
+ * The goal of these tests is to verify the behavior of all blob commands given
+ * the current state is uploadInProgress. This state is achieved when an image
+ * or hash blob is opened and the handler is expected to receive bytes.
+ */
+#include "firmware_handler.hpp"
+#include "firmware_unittest.hpp"
+#include "util.hpp"
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+
+using ::testing::ContainerEq;
+using ::testing::IsEmpty;
+using ::testing::Return;
+using ::testing::UnorderedElementsAreArray;
+
+/*
+ * There are the following calls (parameters may vary):
+ * canHandleBlob(blob)
+ * getBlobIds
+ * deleteBlob(blob)
+ * stat(blob)
+ * stat(session)
+ * open(blob)
+ * close(session)
+ * writemeta(session)
+ * write(session)
+ * read(session)
+ * commit(session)
+ *
+ * Testing canHandleBlob is uninteresting in this state. Getting the BlobIDs
+ * will inform what canHandleBlob will return.
+ */
+class FirmwareHandlerUploadInProgressTest : public IpmiOnlyFirmwareStaticTest
+{};
+
+TEST_F(FirmwareHandlerUploadInProgressTest, GetBlobIdsVerifyOutputActiveImage)
+{
+ /* Opening the image file will add the active image blob id */
+ openToInProgress(staticLayoutBlobId);
+
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(
+ {staticLayoutBlobId, hashBlobId, activeImageBlobId}));
+}
+
+TEST_F(FirmwareHandlerUploadInProgressTest, GetBlobIdsVerifyOutputActiveHash)
+{
+ /* Opening the image file will add the active image blob id */
+ openToInProgress(hashBlobId);
+
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(
+ {staticLayoutBlobId, hashBlobId, activeHashBlobId}));
+}
+
+/*
+ * stat(blob)
+ */
+TEST_F(FirmwareHandlerUploadInProgressTest, StatOnActiveImageReturnsFailure)
+{
+ /* you cannot call stat() on the active image or the active hash or the
+ * verify blob.
+ */
+ openToInProgress(staticLayoutBlobId);
+ ASSERT_TRUE(handler->canHandleBlob(activeImageBlobId));
+
+ blobs::BlobMeta meta;
+ EXPECT_FALSE(handler->stat(activeImageBlobId, &meta));
+}
+
+TEST_F(FirmwareHandlerUploadInProgressTest, StatOnActiveHashReturnsFailure)
+{
+ /* this test is separate from the active image one so that the state doesn't
+ * change from close.
+ */
+ openToInProgress(hashBlobId);
+ ASSERT_TRUE(handler->canHandleBlob(activeHashBlobId));
+
+ blobs::BlobMeta meta;
+ EXPECT_FALSE(handler->stat(activeHashBlobId, &meta));
+}
+
+TEST_F(FirmwareHandlerUploadInProgressTest, StatOnNormalBlobsReturnsSuccess)
+{
+ /* Calling stat() on the normal blobs (not the active) ones will work and
+ * return the same information as in the notYetStarted state.
+ */
+ openToInProgress(staticLayoutBlobId);
+
+ std::vector<std::string> testBlobs = {staticLayoutBlobId, hashBlobId};
+ for (const auto& blob : testBlobs)
+ {
+ blobs::BlobMeta meta = {};
+ EXPECT_TRUE(handler->stat(blob, &meta));
+ EXPECT_EQ(expectedIdleMeta, meta);
+ }
+}
+
+/*
+ * stat(session)
+ */
+TEST_F(FirmwareHandlerUploadInProgressTest,
+ CallingStatOnActiveImageOrHashSessionReturnsDetails)
+{
+ /* This test will verify that the underlying image handler is called with
+ * this stat, in addition to the normal information.
+ */
+ openToInProgress(staticLayoutBlobId);
+
+ EXPECT_CALL(*imageMock2, getSize()).WillOnce(Return(32));
+
+ blobs::BlobMeta meta, expectedMeta = {};
+ expectedMeta.size = 32;
+ expectedMeta.blobState =
+ blobs::OpenFlags::write | FirmwareFlags::UpdateFlags::ipmi;
+ EXPECT_TRUE(handler->stat(session, &meta));
+ EXPECT_EQ(expectedMeta, meta);
+}
+
+/*
+ * open(blob) - While any blob is open, all other fail.
+ *
+ * The fullBlobsList is all the blob_ids present if both /flash/image and
+ * /flash/hash are opened, and one is left open (so there's no verify blob). if
+ * left closed, we'd be in verificationPending, not uploadInProgress.
+ */
+const std::vector<std::string> fullBlobsList = {
+ activeHashBlobId, activeImageBlobId, hashBlobId, staticLayoutBlobId};
+
+TEST_F(FirmwareHandlerUploadInProgressTest, OpeningHashFileWhileImageOpenFails)
+{
+ /* To be in this state, something must be open (and specifically either an
+ * active image (or tarball) or the hash file. Also verifies you can't just
+ * re-open the currently open file.
+ */
+ openToInProgress(staticLayoutBlobId);
+
+ for (const auto& blob : fullBlobsList)
+ {
+ EXPECT_FALSE(handler->open(2, flags, blob));
+ }
+}
+
+TEST_F(FirmwareHandlerUploadInProgressTest, OpeningImageFileWhileHashOpenFails)
+{
+ openToInProgress(hashBlobId);
+
+ for (const auto& blob : fullBlobsList)
+ {
+ EXPECT_FALSE(handler->open(2, flags, blob));
+ }
+}
+
+/*
+ * close(session) - closing the hash or image will trigger a state transition to
+ * verificationPending.
+ *
+ * NOTE: Re-opening /flash/image will transition back to uploadInProgress, but
+ * that is verified in the verificationPending::open tests.
+ */
+TEST_F(FirmwareHandlerUploadInProgressTest,
+ ClosingImageFileTransitionsToVerificationPending)
+{
+ EXPECT_FALSE(handler->canHandleBlob(verifyBlobId));
+ openToInProgress(staticLayoutBlobId);
+
+ handler->close(session);
+ expectedState(FirmwareBlobHandler::UpdateState::verificationPending);
+
+ EXPECT_TRUE(handler->canHandleBlob(verifyBlobId));
+}
+
+TEST_F(FirmwareHandlerUploadInProgressTest,
+ ClosingHashFileTransitionsToVerificationPending)
+{
+ EXPECT_FALSE(handler->canHandleBlob(verifyBlobId));
+ openToInProgress(hashBlobId);
+
+ handler->close(session);
+ expectedState(FirmwareBlobHandler::UpdateState::verificationPending);
+
+ EXPECT_FALSE(handler->canHandleBlob(verifyBlobId));
+}
+
+/*
+ * writemeta(session)
+ */
+TEST_F(FirmwareHandlerUploadInProgressTest,
+ WriteMetaAgainstImageReturnsFailureIfNoDataHandler)
+{
+ /* Calling write/read/writeMeta are uninteresting against the open blob in
+ * this case because the blob will just pass the call along. Whereas
+ * calling against the verify or update blob may be more interesting.
+ */
+ openToInProgress(staticLayoutBlobId);
+
+ /* TODO: Consider adding a test that has a data handler, but that test
+ * already exists under the general writeMeta test suite.
+ */
+ /* Note: with IPMI as the transport there's no data handler, so this should
+ * fail nicely. */
+ std::vector<std::uint8_t> bytes = {0x01, 0x02};
+ EXPECT_FALSE(handler->writeMeta(session, 0, bytes));
+}
+
+/*
+ * write(session)
+ */
+TEST_F(FirmwareHandlerUploadInProgressTest, WriteToImageReturnsSuccess)
+{
+ openToInProgress(staticLayoutBlobId);
+ std::vector<std::uint8_t> bytes = {0x01, 0x02};
+ EXPECT_CALL(*imageMock2, write(0, ContainerEq(bytes)))
+ .WillOnce(Return(true));
+ EXPECT_TRUE(handler->write(session, 0, bytes));
+}
+
+TEST_F(FirmwareHandlerUploadInProgressTest, WriteToHashReturnsSuccess)
+{
+ openToInProgress(hashBlobId);
+ std::vector<std::uint8_t> bytes = {0x01, 0x02};
+ EXPECT_CALL(*hashImageMock, write(0, ContainerEq(bytes)))
+ .WillOnce(Return(true));
+ EXPECT_TRUE(handler->write(session, 0, bytes));
+}
+
+/*
+ * read(session)
+ */
+TEST_F(FirmwareHandlerUploadInProgressTest, ReadImageFileReturnsFailure)
+{
+ /* Read is not supported. */
+ openToInProgress(staticLayoutBlobId);
+ EXPECT_THAT(handler->read(session, 0, 32), IsEmpty());
+}
+
+/*
+ * commit(session)
+ */
+TEST_F(FirmwareHandlerUploadInProgressTest,
+ CommitAgainstImageFileReturnsFailure)
+{
+ /* Commit is only valid against specific blobs. */
+ openToInProgress(staticLayoutBlobId);
+ EXPECT_FALSE(handler->commit(session, {}));
+}
+
+TEST_F(FirmwareHandlerUploadInProgressTest, CommitAgainstHashFileReturnsFailure)
+{
+ openToInProgress(hashBlobId);
+ EXPECT_FALSE(handler->commit(session, {}));
+}
+
+/*
+ * deleteBlob(blob)
+ */
+TEST_F(FirmwareHandlerUploadInProgressTest, DeleteBlobReturnsFalse)
+{
+ /* Try deleting all blobs, it doesn't really matter which though because you
+ * cannot close out an open session, therefore you must fail to delete
+ * anything unless everything is closed.
+ */
+ openToInProgress(staticLayoutBlobId);
+ auto blobs = handler->getBlobIds();
+ for (const auto& b : blobs)
+ {
+ EXPECT_FALSE(handler->deleteBlob(b));
+ }
+}
+
+/*
+ * expire(session)
+ */
+TEST_F(FirmwareHandlerUploadInProgressTest, ExpireAbortsProcess)
+{
+ openToInProgress(staticLayoutBlobId);
+
+ ASSERT_TRUE(handler->expire(session));
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(startingBlobs));
+ expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
+}
+
+} // namespace
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/firmware_state_verificationcompleted_unittest.cpp b/bmc/firmware-handler/test/firmware_state_verificationcompleted_unittest.cpp
new file mode 100644
index 0000000..bdac123
--- /dev/null
+++ b/bmc/firmware-handler/test/firmware_state_verificationcompleted_unittest.cpp
@@ -0,0 +1,324 @@
+/**
+ * The goal of these tests is to verify the behavior of all blob commands given
+ * the current state is verificationCompleted. This state is achieved as a out
+ * of verificationStarted.
+ */
+#include "firmware_handler.hpp"
+#include "firmware_unittest.hpp"
+#include "status.hpp"
+#include "util.hpp"
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+
+using ::testing::IsEmpty;
+using ::testing::UnorderedElementsAreArray;
+
+/*
+ * There are the following calls (parameters may vary):
+ * canHandleBlob(blob)
+ * getBlobIds
+ * deleteBlob(blob)
+ * stat(blob)
+ * stat(session)
+ * open(blob)
+ * close(session)
+ * writemeta(session)
+ * write(session)
+ * read(session)
+ * commit(session)
+ *
+ * Like the state verificationStarted, there is a file open in
+ * verificationCompleted. This state is transitioned to after a stat() command
+ * indicates a successful verification.
+ */
+
+class FirmwareHandlerVerificationCompletedTest :
+ public IpmiOnlyFirmwareStaticTest
+{};
+
+/*
+ * deleteBlob(blob)
+ */
+TEST_F(FirmwareHandlerVerificationCompletedTest, DeleteBlobReturnsFalse)
+{
+ /* Try deleting all blobs, it doesn't really matter which though because you
+ * cannot close out an open session, therefore you must fail to delete
+ * anything unless everything is closed.
+ */
+ getToVerificationCompleted(ActionStatus::success);
+ auto blobs = handler->getBlobIds();
+ for (const auto& b : blobs)
+ {
+ EXPECT_FALSE(handler->deleteBlob(b));
+ }
+}
+
+/*
+ * canHandleBlob
+ */
+TEST_F(FirmwareHandlerVerificationCompletedTest,
+ OnVerificationCompleteSuccessUpdateBlobIdNotPresent)
+{
+ /* the uploadBlobId is only added on close() of the verifyBlobId. This is a
+ * consistent behavior with verifyBlobId only added when closing the image
+ * or hash.
+ */
+ getToVerificationCompleted(ActionStatus::success);
+ EXPECT_FALSE(handler->canHandleBlob(updateBlobId));
+}
+
+TEST_F(FirmwareHandlerVerificationCompletedTest,
+ OnVerificationCompleteFailureUpdateBlobIdNotPresent)
+{
+ getToVerificationCompleted(ActionStatus::failed);
+ EXPECT_FALSE(handler->canHandleBlob(updateBlobId));
+}
+
+/*
+ * getBlobIds
+ */
+TEST_F(FirmwareHandlerVerificationCompletedTest, GetBlobIdsReturnsExpectedList)
+{
+ getToVerificationCompleted(ActionStatus::success);
+ EXPECT_THAT(
+ handler->getBlobIds(),
+ UnorderedElementsAreArray(
+ {verifyBlobId, hashBlobId, activeImageBlobId, staticLayoutBlobId}));
+}
+
+/*
+ * stat(blob)
+ */
+TEST_F(FirmwareHandlerVerificationCompletedTest,
+ StatOnActiveImageReturnsFailure)
+{
+ getToVerificationCompleted(ActionStatus::success);
+ ASSERT_TRUE(handler->canHandleBlob(activeImageBlobId));
+
+ blobs::BlobMeta meta;
+ EXPECT_FALSE(handler->stat(activeImageBlobId, &meta));
+}
+
+TEST_F(FirmwareHandlerVerificationCompletedTest,
+ VerifyActiveHashIdMissingInThisCase)
+{
+ /* The path taken to get to this state never opened the hash blob Id, which
+ * is fine. But let's verify it behaved as intended.
+ */
+ getToVerificationCompleted(ActionStatus::success);
+ EXPECT_FALSE(handler->canHandleBlob(activeHashBlobId));
+}
+
+/* TODO: Add sufficient warning that you can get to verificationCompleted
+ * without ever opening the image blob id (or the tarball one).
+ *
+ * Although in this case, it's expected that any verification triggered would
+ * certainly fail. So, although it's possible, it's uninteresting.
+ */
+
+TEST_F(FirmwareHandlerVerificationCompletedTest, StatOnVerifyBlobReturnsFailure)
+{
+ getToVerificationCompleted(ActionStatus::success);
+ ASSERT_TRUE(handler->canHandleBlob(verifyBlobId));
+
+ blobs::BlobMeta meta;
+ EXPECT_FALSE(handler->stat(verifyBlobId, &meta));
+}
+
+TEST_F(FirmwareHandlerVerificationCompletedTest,
+ StatOnNormalBlobsReturnsSuccess)
+{
+ getToVerificationCompleted(ActionStatus::success);
+
+ std::vector<std::string> testBlobs = {staticLayoutBlobId, hashBlobId};
+ for (const auto& blob : testBlobs)
+ {
+ ASSERT_TRUE(handler->canHandleBlob(blob));
+
+ blobs::BlobMeta meta = {};
+ EXPECT_TRUE(handler->stat(blob, &meta));
+ EXPECT_EQ(expectedIdleMeta, meta);
+ }
+}
+
+/*
+ * stat(session) - the verify blobid is open in this state, so stat on that once
+ * completed should have no effect.
+ */
+TEST_F(FirmwareHandlerVerificationCompletedTest,
+ SessionStatOnVerifyAfterSuccessDoesNothing)
+{
+ /* Every time you stat() once it's triggered, it checks the state again
+ * until it's completed.
+ */
+ getToVerificationCompleted(ActionStatus::success);
+ EXPECT_CALL(*verifyMockPtr, status()).Times(0);
+
+ blobs::BlobMeta meta, expectedMeta = {};
+ expectedMeta.size = 0;
+ expectedMeta.blobState = flags | blobs::StateFlags::committed;
+ expectedMeta.metadata.push_back(
+ static_cast<std::uint8_t>(ActionStatus::success));
+
+ EXPECT_TRUE(handler->stat(session, &meta));
+ EXPECT_EQ(expectedMeta, meta);
+ expectedState(FirmwareBlobHandler::UpdateState::verificationCompleted);
+}
+
+TEST_F(FirmwareHandlerVerificationCompletedTest,
+ SessionStatOnVerifyAfterFailureDoesNothing)
+{
+ getToVerificationCompleted(ActionStatus::failed);
+ EXPECT_CALL(*verifyMockPtr, status()).Times(0);
+
+ blobs::BlobMeta meta, expectedMeta = {};
+ expectedMeta.size = 0;
+ expectedMeta.blobState = flags | blobs::StateFlags::commit_error;
+ expectedMeta.metadata.push_back(
+ static_cast<std::uint8_t>(ActionStatus::failed));
+
+ EXPECT_TRUE(handler->stat(session, &meta));
+ EXPECT_EQ(expectedMeta, meta);
+ expectedState(FirmwareBlobHandler::UpdateState::verificationCompleted);
+}
+
+/*
+ * open(blob) - all open should fail
+ */
+TEST_F(FirmwareHandlerVerificationCompletedTest,
+ OpeningAnyBlobAvailableFailsAfterSuccess)
+{
+ getToVerificationCompleted(ActionStatus::success);
+
+ auto blobs = handler->getBlobIds();
+ for (const auto& blob : blobs)
+ {
+ EXPECT_FALSE(handler->open(session + 1, flags, blob));
+ }
+}
+
+TEST_F(FirmwareHandlerVerificationCompletedTest,
+ OpeningAnyBlobAvailableFailsAfterFailure)
+{
+ getToVerificationCompleted(ActionStatus::failed);
+
+ auto blobs = handler->getBlobIds();
+ for (const auto& blob : blobs)
+ {
+ EXPECT_FALSE(handler->open(session + 1, flags, blob));
+ }
+}
+
+/*
+ * writemeta(session) - write meta should fail.
+ */
+TEST_F(FirmwareHandlerVerificationCompletedTest,
+ WriteMetaToVerifyBlobReturnsFailure)
+{
+ getToVerificationCompleted(ActionStatus::success);
+
+ std::vector<std::uint8_t> bytes = {0x01, 0x02};
+ EXPECT_FALSE(handler->writeMeta(session, 0, bytes));
+}
+
+/*
+ * write(session) - write should fail.
+ */
+TEST_F(FirmwareHandlerVerificationCompletedTest,
+ WriteToVerifyBlobReturnsFailure)
+{
+ getToVerificationCompleted(ActionStatus::success);
+
+ std::vector<std::uint8_t> bytes = {0x01, 0x02};
+ EXPECT_FALSE(handler->write(session, 0, bytes));
+}
+
+/*
+ * read(session) - read returns empty.
+ */
+TEST_F(FirmwareHandlerVerificationCompletedTest, ReadOfVerifyBlobReturnsEmpty)
+{
+ getToVerificationCompleted(ActionStatus::success);
+ EXPECT_THAT(handler->read(session, 0, 1), IsEmpty());
+}
+
+/*
+ * commit(session) - returns failure
+ */
+TEST_F(FirmwareHandlerVerificationCompletedTest,
+ CommitOnVerifyBlobAfterSuccessReturnsFailure)
+{
+ /* If you've started this'll return success, but if it's finished, it won't
+ * let you try-again.
+ */
+ getToVerificationCompleted(ActionStatus::success);
+ EXPECT_CALL(*verifyMockPtr, trigger()).Times(0);
+
+ EXPECT_FALSE(handler->commit(session, {}));
+}
+
+TEST_F(FirmwareHandlerVerificationCompletedTest,
+ CommitOnVerifyBlobAfterFailureReturnsFailure)
+{
+ getToVerificationCompleted(ActionStatus::failed);
+ EXPECT_CALL(*verifyMockPtr, trigger()).Times(0);
+
+ EXPECT_FALSE(handler->commit(session, {}));
+}
+
+/*
+ * close(session) - close on the verify blobid:
+ * 1. if successful adds update blob id, changes state to UpdatePending
+ */
+TEST_F(FirmwareHandlerVerificationCompletedTest,
+ CloseAfterSuccessChangesStateAddsUpdateBlob)
+{
+ getToVerificationCompleted(ActionStatus::success);
+ ASSERT_FALSE(handler->canHandleBlob(updateBlobId));
+
+ handler->close(session);
+ EXPECT_TRUE(handler->canHandleBlob(updateBlobId));
+ expectedState(FirmwareBlobHandler::UpdateState::updatePending);
+}
+
+/*
+ * close(session) - close on the verify blobid:
+ * 2. if unsuccessful it aborts.
+ */
+TEST_F(FirmwareHandlerVerificationCompletedTest, CloseAfterFailureAborts)
+{
+ getToVerificationCompleted(ActionStatus::failed);
+ ASSERT_FALSE(handler->canHandleBlob(updateBlobId));
+
+ handler->close(session);
+ ASSERT_FALSE(handler->canHandleBlob(updateBlobId));
+ expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(startingBlobs));
+}
+
+/*
+ * expire(session)
+ */
+TEST_F(FirmwareHandlerVerificationCompletedTest,
+ ExpireAfterVerificationCompletedAborts)
+{
+ getToVerificationCompleted(ActionStatus::failed);
+
+ ASSERT_TRUE(handler->expire(session));
+ expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(startingBlobs));
+}
+
+} // namespace
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/firmware_state_verificationpending_unittest.cpp b/bmc/firmware-handler/test/firmware_state_verificationpending_unittest.cpp
new file mode 100644
index 0000000..dc6f3d3
--- /dev/null
+++ b/bmc/firmware-handler/test/firmware_state_verificationpending_unittest.cpp
@@ -0,0 +1,353 @@
+/**
+ * The goal of these tests is to verify the behavior of all blob commands given
+ * the current state is verificationPending. This state is achieved as a
+ * transition out of uploadInProgress.
+ */
+#include "firmware_handler.hpp"
+#include "firmware_unittest.hpp"
+#include "status.hpp"
+#include "util.hpp"
+
+#include <algorithm>
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+
+using ::testing::IsEmpty;
+using ::testing::Return;
+using ::testing::UnorderedElementsAreArray;
+
+/*
+ * There are the following calls (parameters may vary):
+ * canHandleBlob(blob)
+ * getBlobIds
+ * deleteBlob(blob)
+ * stat(blob)
+ * stat(session)
+ * open(blob)
+ * close(session)
+ * writemeta(session)
+ * write(session)
+ * read(session)
+ * commit(session)
+ *
+ * Testing canHandleBlob is uninteresting in this state. Getting the BlobIDs
+ * will inform what canHandleBlob will return.
+ */
+
+class FirmwareHandlerVerificationPendingTest : public IpmiOnlyFirmwareStaticTest
+{};
+
+/*
+ * getBlobIds
+ */
+TEST_F(FirmwareHandlerVerificationPendingTest, VerifyBlobIdAvailableInState)
+{
+ /* Only in the verificationPending state (and later), should the
+ * verifyBlobId be present.
+ */
+ EXPECT_FALSE(handler->canHandleBlob(verifyBlobId));
+
+ getToVerificationPending(staticLayoutBlobId);
+
+ EXPECT_TRUE(handler->canHandleBlob(verifyBlobId));
+ EXPECT_TRUE(handler->canHandleBlob(activeImageBlobId));
+ EXPECT_FALSE(handler->canHandleBlob(updateBlobId));
+}
+
+/*
+ * delete(blob)
+ */
+TEST_F(FirmwareHandlerVerificationPendingTest, DeleteVerifyPendingAbortsProcess)
+{
+ /* It doesn't matter what blob id is used to delete in the design, so just
+ * delete the verify blob id
+ */
+ getToVerificationPending(staticLayoutBlobId);
+
+ EXPECT_CALL(*verifyMockPtr, abort()).Times(0);
+
+ ASSERT_TRUE(handler->canHandleBlob(verifyBlobId));
+ EXPECT_TRUE(handler->deleteBlob(verifyBlobId));
+
+ std::vector<std::string> expectedBlobs = {staticLayoutBlobId, hashBlobId};
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(expectedBlobs));
+ expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
+}
+
+TEST_F(FirmwareHandlerVerificationPendingTest, DeleteActiveImageAbortsProcess)
+{
+ getToVerificationPending(staticLayoutBlobId);
+
+ EXPECT_CALL(*verifyMockPtr, abort()).Times(0);
+
+ ASSERT_TRUE(handler->canHandleBlob(activeImageBlobId));
+ EXPECT_TRUE(handler->deleteBlob(activeImageBlobId));
+
+ std::vector<std::string> expectedBlobs = {staticLayoutBlobId, hashBlobId};
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(expectedBlobs));
+ expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
+}
+
+TEST_F(FirmwareHandlerVerificationPendingTest, DeleteStaticLayoutAbortsProcess)
+{
+ getToVerificationPending(staticLayoutBlobId);
+
+ EXPECT_CALL(*verifyMockPtr, abort()).Times(0);
+
+ ASSERT_TRUE(handler->canHandleBlob(staticLayoutBlobId));
+ EXPECT_TRUE(handler->deleteBlob(staticLayoutBlobId));
+
+ std::vector<std::string> expectedBlobs = {staticLayoutBlobId, hashBlobId};
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(expectedBlobs));
+ expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
+}
+
+TEST_F(FirmwareHandlerVerificationPendingTest, DeleteHashAbortsProcess)
+{
+ getToVerificationPending(staticLayoutBlobId);
+
+ EXPECT_CALL(*verifyMockPtr, abort()).Times(0);
+
+ ASSERT_TRUE(handler->canHandleBlob(hashBlobId));
+ EXPECT_TRUE(handler->deleteBlob(hashBlobId));
+
+ std::vector<std::string> expectedBlobs = {staticLayoutBlobId, hashBlobId};
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(expectedBlobs));
+ expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
+}
+
+/*
+ * expire(session)
+ */
+TEST_F(FirmwareHandlerVerificationPendingTest,
+ ExpireVerificationPendingAbortsProcess)
+{
+ getToVerificationPending(staticLayoutBlobId);
+
+ EXPECT_CALL(*verifyMockPtr, abort()).Times(0);
+
+ EXPECT_TRUE(handler->expire(session));
+
+ std::vector<std::string> expectedBlobs = {staticLayoutBlobId, hashBlobId};
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(expectedBlobs));
+ expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
+}
+
+/*
+ * stat(blob)
+ */
+TEST_F(FirmwareHandlerVerificationPendingTest, StatOnActiveImageReturnsFailure)
+{
+ getToVerificationPending(staticLayoutBlobId);
+ ASSERT_TRUE(handler->canHandleBlob(activeImageBlobId));
+
+ blobs::BlobMeta meta;
+ EXPECT_FALSE(handler->stat(activeImageBlobId, &meta));
+}
+
+TEST_F(FirmwareHandlerVerificationPendingTest, StatOnActiveHashReturnsFailure)
+{
+ getToVerificationPending(hashBlobId);
+ ASSERT_TRUE(handler->canHandleBlob(activeHashBlobId));
+
+ blobs::BlobMeta meta;
+ EXPECT_FALSE(handler->stat(activeHashBlobId, &meta));
+}
+
+TEST_F(FirmwareHandlerVerificationPendingTest,
+ StatOnVerificationBlobReturnsFailure)
+{
+ getToVerificationPending(staticLayoutBlobId);
+ ASSERT_TRUE(handler->canHandleBlob(verifyBlobId));
+
+ blobs::BlobMeta meta;
+ EXPECT_FALSE(handler->stat(verifyBlobId, &meta));
+}
+
+TEST_F(FirmwareHandlerVerificationPendingTest,
+ VerificationBlobNotFoundWithoutStaticDataAsWell)
+{
+ /* If you only ever open the hash blob id, and never the firmware blob id,
+ * the verify blob isn't added.
+ */
+ getToVerificationPending(hashBlobId);
+ EXPECT_FALSE(handler->canHandleBlob(verifyBlobId));
+}
+
+TEST_F(FirmwareHandlerVerificationPendingTest, StatOnNormalBlobsReturnsSuccess)
+{
+ getToVerificationPending(staticLayoutBlobId);
+
+ std::vector<std::string> testBlobs = {staticLayoutBlobId, hashBlobId};
+ for (const auto& blob : testBlobs)
+ {
+ ASSERT_TRUE(handler->canHandleBlob(blob));
+ blobs::BlobMeta meta = {};
+ EXPECT_TRUE(handler->stat(blob, &meta));
+ EXPECT_EQ(expectedIdleMeta, meta);
+ }
+}
+
+/*
+ * open(blob)
+ */
+TEST_F(FirmwareHandlerVerificationPendingTest, OpenVerifyBlobSucceeds)
+{
+ getToVerificationPending(staticLayoutBlobId);
+
+ /* the session is safe because it was already closed to get to this state.
+ */
+ EXPECT_TRUE(handler->open(session, flags, verifyBlobId));
+}
+
+TEST_F(FirmwareHandlerVerificationPendingTest, OpenActiveBlobsFail)
+{
+ /* Try opening the active blob Id. This test is equivalent to trying to
+ * open the active hash blob id, in that neither are ever allowed.
+ */
+ getToVerificationPending(staticLayoutBlobId);
+ EXPECT_FALSE(handler->open(session, flags, activeImageBlobId));
+ EXPECT_FALSE(handler->open(session, flags, activeHashBlobId));
+}
+
+TEST_F(FirmwareHandlerVerificationPendingTest,
+ OpenImageBlobTransitionsToUploadInProgress)
+{
+ getToVerificationPending(staticLayoutBlobId);
+
+ /* Verify the active blob for the image is in the list once to start.
+ * Note: This is truly tested under the notYetStarted::open() test.
+ */
+ std::vector<std::string> expectedBlobs = {staticLayoutBlobId, hashBlobId,
+ verifyBlobId, activeImageBlobId};
+
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(expectedBlobs));
+
+ /* Verifies it isn't triggered again. */
+ EXPECT_CALL(*prepareMockPtr, trigger()).Times(0);
+
+ EXPECT_CALL(*imageMock2, open(staticLayoutBlobId)).WillOnce(Return(true));
+ EXPECT_TRUE(handler->open(session, flags, staticLayoutBlobId));
+ expectedState(FirmwareBlobHandler::UpdateState::uploadInProgress);
+
+ expectedBlobs.erase(
+ std::remove(expectedBlobs.begin(), expectedBlobs.end(), verifyBlobId),
+ expectedBlobs.end());
+
+ /* Verify the active blob ID was not added to the list twice and
+ * verifyBlobId is removed
+ */
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(expectedBlobs));
+}
+
+/*
+ * close(session)
+ */
+TEST_F(FirmwareHandlerVerificationPendingTest,
+ ClosingVerifyBlobWithoutCommitDoesNotChangeState)
+{
+ /* commit() will change the state, closing post-commit is part of
+ * verificationStarted testing.
+ */
+ getToVerificationPending(staticLayoutBlobId);
+ EXPECT_TRUE(handler->open(session, flags, verifyBlobId));
+ expectedState(FirmwareBlobHandler::UpdateState::verificationPending);
+
+ handler->close(session);
+ expectedState(FirmwareBlobHandler::UpdateState::verificationPending);
+}
+
+/*
+ * commit(session)
+ */
+TEST_F(FirmwareHandlerVerificationPendingTest,
+ CommitOnVerifyBlobTriggersVerificationAndStateTransition)
+{
+ getToVerificationPending(staticLayoutBlobId);
+ EXPECT_TRUE(handler->open(session, flags, verifyBlobId));
+ EXPECT_CALL(*verifyMockPtr, trigger()).WillOnce(Return(true));
+
+ EXPECT_TRUE(handler->commit(session, {}));
+ expectedState(FirmwareBlobHandler::UpdateState::verificationStarted);
+}
+
+/*
+ * stat(session) - in this state, you can only open(verifyBlobId) without
+ * changing state.
+ */
+TEST_F(FirmwareHandlerVerificationPendingTest, StatOnVerifyBlobIdReturnsState)
+{
+ /* If this is called before commit(), it's still verificationPending, so it
+ * just returns the state is other
+ */
+ getToVerificationPending(staticLayoutBlobId);
+ EXPECT_TRUE(handler->open(session, flags, verifyBlobId));
+ EXPECT_CALL(*verifyMockPtr, trigger()).Times(0);
+ EXPECT_CALL(*verifyMockPtr, status()).Times(0);
+
+ blobs::BlobMeta meta, expectedMeta = {};
+ expectedMeta.size = 0;
+ expectedMeta.blobState = flags;
+ expectedMeta.metadata.push_back(
+ static_cast<std::uint8_t>(ActionStatus::unknown));
+
+ EXPECT_TRUE(handler->stat(session, &meta));
+ EXPECT_EQ(expectedMeta, meta);
+}
+
+/*
+ * writemeta(session)
+ */
+TEST_F(FirmwareHandlerVerificationPendingTest, WriteMetaAgainstVerifyFails)
+{
+ /* The verifyBlobId has no data handler, which means write meta fails. */
+ getToVerificationPending(staticLayoutBlobId);
+
+ EXPECT_TRUE(handler->open(session, flags, verifyBlobId));
+
+ std::vector<std::uint8_t> bytes = {0x01, 0x02};
+ EXPECT_FALSE(handler->writeMeta(session, 0, bytes));
+}
+
+/*
+ * write(session)
+ */
+TEST_F(FirmwareHandlerVerificationPendingTest, WriteAgainstVerifyBlobIdFails)
+{
+ getToVerificationPending(staticLayoutBlobId);
+
+ EXPECT_TRUE(handler->open(session, flags, verifyBlobId));
+
+ std::vector<std::uint8_t> bytes = {0x01, 0x02};
+ EXPECT_FALSE(handler->write(session, 0, bytes));
+}
+
+/*
+ * read(session)
+ */
+TEST_F(FirmwareHandlerVerificationPendingTest,
+ ReadAgainstVerifyBlobIdReturnsEmpty)
+{
+ getToVerificationPending(staticLayoutBlobId);
+
+ EXPECT_TRUE(handler->open(session, flags, verifyBlobId));
+ EXPECT_THAT(handler->read(session, 0, 1), IsEmpty());
+}
+
+} // namespace
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/firmware_state_verificationstarted_unittest.cpp b/bmc/firmware-handler/test/firmware_state_verificationstarted_unittest.cpp
new file mode 100644
index 0000000..4f69c89
--- /dev/null
+++ b/bmc/firmware-handler/test/firmware_state_verificationstarted_unittest.cpp
@@ -0,0 +1,305 @@
+/* The goal of these tests is to verify the behavior of all blob commands given
+ * the current state is verificationStarted. This state is achieved as a out of
+ * verificationPending.
+ */
+#include "firmware_handler.hpp"
+#include "firmware_unittest.hpp"
+#include "status.hpp"
+#include "util.hpp"
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+
+using ::testing::IsEmpty;
+using ::testing::Return;
+using ::testing::UnorderedElementsAreArray;
+
+/*
+ * There are the following calls (parameters may vary):
+ * canHandleBlob(blob)
+ * getBlobIds
+ * deleteBlob(blob)
+ * stat(blob)
+ * stat(session)
+ * open(blob)
+ * close(session)
+ * writemeta(session)
+ * write(session)
+ * read(session)
+ * commit(session)
+ *
+ * Testing canHandleBlob is uninteresting in this state. Getting the BlobIDs
+ * will inform what canHandleBlob will return.
+ */
+
+class FirmwareHandlerVerificationStartedTest : public IpmiOnlyFirmwareStaticTest
+{};
+
+/*
+ * canHandleBlob(blob)
+ * getBlobIds()
+ */
+TEST_F(FirmwareHandlerVerificationStartedTest, GetBlobIdsReturnsExpectedList)
+{
+ getToVerificationStarted(staticLayoutBlobId);
+
+ auto blobs = handler->getBlobIds();
+ EXPECT_THAT(
+ blobs, UnorderedElementsAreArray({activeImageBlobId, staticLayoutBlobId,
+ hashBlobId, verifyBlobId}));
+
+ for (const auto& blob : blobs)
+ {
+ EXPECT_TRUE(handler->canHandleBlob(blob));
+ }
+}
+
+/*
+ * stat(session)
+ */
+TEST_F(FirmwareHandlerVerificationStartedTest,
+ StatOnVerifyBlobIdAfterCommitChecksStateAndReturnsRunning)
+{
+ getToVerificationStarted(staticLayoutBlobId);
+ EXPECT_CALL(*verifyMockPtr, status())
+ .WillOnce(Return(ActionStatus::running));
+
+ blobs::BlobMeta meta, expectedMeta = {};
+ expectedMeta.size = 0;
+ expectedMeta.blobState = flags | blobs::StateFlags::committing;
+ expectedMeta.metadata.push_back(
+ static_cast<std::uint8_t>(ActionStatus::running));
+
+ EXPECT_TRUE(handler->stat(session, &meta));
+ EXPECT_EQ(expectedMeta, meta);
+}
+
+TEST_F(FirmwareHandlerVerificationStartedTest,
+ StatOnVerifyBlobIdAfterCommitChecksStateAndReturnsOther)
+{
+ getToVerificationStarted(staticLayoutBlobId);
+ EXPECT_CALL(*verifyMockPtr, status())
+ .WillOnce(Return(ActionStatus::unknown));
+
+ blobs::BlobMeta meta, expectedMeta = {};
+ expectedMeta.size = 0;
+ expectedMeta.blobState = flags | blobs::StateFlags::committing;
+ expectedMeta.metadata.push_back(
+ static_cast<std::uint8_t>(ActionStatus::unknown));
+
+ EXPECT_TRUE(handler->stat(session, &meta));
+ EXPECT_EQ(expectedMeta, meta);
+}
+
+TEST_F(FirmwareHandlerVerificationStartedTest,
+ StatOnVerifyBlobIdAfterCommitCheckStateAndReturnsFailed)
+{
+ /* If the returned state from the verification handler is failed, it sets
+ * commit_error and transitions to verificationCompleted.
+ */
+ getToVerificationStarted(staticLayoutBlobId);
+ EXPECT_CALL(*verifyMockPtr, status())
+ .WillOnce(Return(ActionStatus::failed));
+
+ blobs::BlobMeta meta, expectedMeta = {};
+ expectedMeta.size = 0;
+ expectedMeta.blobState = flags | blobs::StateFlags::commit_error;
+ expectedMeta.metadata.push_back(
+ static_cast<std::uint8_t>(ActionStatus::failed));
+
+ EXPECT_TRUE(handler->stat(session, &meta));
+ EXPECT_EQ(expectedMeta, meta);
+ expectedState(FirmwareBlobHandler::UpdateState::verificationCompleted);
+}
+
+TEST_F(FirmwareHandlerVerificationStartedTest,
+ StatOnVerifyBlobIdAfterCommitCheckStateAndReturnsSuccess)
+{
+ /* If the returned state from the verification handler is success, it sets
+ * committed and transitions to verificationCompleted.
+ */
+ getToVerificationStarted(staticLayoutBlobId);
+ EXPECT_CALL(*verifyMockPtr, status())
+ .WillOnce(Return(ActionStatus::success));
+
+ blobs::BlobMeta meta, expectedMeta = {};
+ expectedMeta.size = 0;
+ expectedMeta.blobState = flags | blobs::StateFlags::committed;
+ expectedMeta.metadata.push_back(
+ static_cast<std::uint8_t>(ActionStatus::success));
+
+ EXPECT_TRUE(handler->stat(session, &meta));
+ EXPECT_EQ(expectedMeta, meta);
+ expectedState(FirmwareBlobHandler::UpdateState::verificationCompleted);
+}
+
+/*
+ * deleteBlob(blob)
+ */
+TEST_F(FirmwareHandlerVerificationStartedTest, DeleteBlobReturnsFalse)
+{
+ /* Try deleting all blobs, it doesn't really matter which though because you
+ * cannot close out an open session, therefore you must fail to delete
+ * anything unless everything is closed.
+ */
+ getToVerificationStarted(staticLayoutBlobId);
+ auto blobs = handler->getBlobIds();
+ for (const auto& b : blobs)
+ {
+ EXPECT_FALSE(handler->deleteBlob(b));
+ }
+}
+
+/*
+ * stat(blob)
+ */
+TEST_F(FirmwareHandlerVerificationStartedTest, StatOnActiveImageReturnsFailure)
+{
+ getToVerificationStarted(staticLayoutBlobId);
+ ASSERT_TRUE(handler->canHandleBlob(activeImageBlobId));
+
+ blobs::BlobMeta meta;
+ EXPECT_FALSE(handler->stat(activeImageBlobId, &meta));
+}
+
+TEST_F(FirmwareHandlerVerificationStartedTest, StatOnActiveHashReturnsFailure)
+{
+ getToVerificationStartedWitHashBlob();
+ ASSERT_TRUE(handler->canHandleBlob(activeHashBlobId));
+
+ blobs::BlobMeta meta;
+ EXPECT_FALSE(handler->stat(activeHashBlobId, &meta));
+}
+
+TEST_F(FirmwareHandlerVerificationStartedTest, StatOnVerifyBlobReturnsFailure)
+{
+ /* the verifyBlobId is available starting at verificationPending. */
+ getToVerificationStarted(staticLayoutBlobId);
+ ASSERT_TRUE(handler->canHandleBlob(verifyBlobId));
+
+ blobs::BlobMeta meta;
+ EXPECT_FALSE(handler->stat(verifyBlobId, &meta));
+}
+
+TEST_F(FirmwareHandlerVerificationStartedTest, StatOnNormalBlobsReturnsSuccess)
+{
+ getToVerificationStarted(staticLayoutBlobId);
+
+ std::vector<std::string> testBlobs = {staticLayoutBlobId, hashBlobId};
+ for (const auto& blob : testBlobs)
+ {
+ ASSERT_TRUE(handler->canHandleBlob(blob));
+
+ blobs::BlobMeta meta = {};
+ EXPECT_TRUE(handler->stat(blob, &meta));
+ EXPECT_EQ(expectedIdleMeta, meta);
+ }
+}
+
+/*
+ * writemeta(session)
+ */
+TEST_F(FirmwareHandlerVerificationStartedTest,
+ WriteMetaOnVerifySessionReturnsFailure)
+{
+ getToVerificationStarted(staticLayoutBlobId);
+
+ std::vector<std::uint8_t> bytes = {0x01, 0x02};
+ EXPECT_FALSE(handler->writeMeta(session, 0, bytes));
+}
+
+/*
+ * write(session)
+ */
+TEST_F(FirmwareHandlerVerificationStartedTest,
+ WriteOnVerifySessionReturnsFailure)
+{
+ getToVerificationStarted(staticLayoutBlobId);
+
+ std::vector<std::uint8_t> bytes = {0x01, 0x02};
+ EXPECT_FALSE(handler->write(session, 0, bytes));
+}
+
+/*
+ * open(blob) - there is nothing you can open, this state has an open file.
+ */
+TEST_F(FirmwareHandlerVerificationStartedTest,
+ AttemptToOpenImageFileReturnsFailure)
+{
+ /* Attempt to open a file one normally can open, however, as there is
+ * already a file open, this will fail.
+ */
+ getToVerificationStarted(staticLayoutBlobId);
+
+ auto blobsToOpen = handler->getBlobIds();
+ for (const auto& blob : blobsToOpen)
+ {
+ EXPECT_FALSE(handler->open(session + 1, flags, blob));
+ }
+}
+
+/*
+ * read(session)
+ */
+TEST_F(FirmwareHandlerVerificationStartedTest, ReadOfVerifyBlobReturnsEmpty)
+{
+ getToVerificationStarted(staticLayoutBlobId);
+ EXPECT_THAT(handler->read(session, 0, 1), IsEmpty());
+}
+
+/*
+ * commit(session)
+ */
+TEST_F(FirmwareHandlerVerificationStartedTest,
+ CommitOnVerifyDuringVerificationHasNoImpact)
+{
+ getToVerificationStarted(staticLayoutBlobId);
+ EXPECT_TRUE(handler->commit(session, {}));
+ expectedState(FirmwareBlobHandler::UpdateState::verificationStarted);
+}
+
+/*
+ * close(session) - close while state if verificationStarted without calling
+ * stat first will abort.
+ */
+TEST_F(FirmwareHandlerVerificationStartedTest,
+ CloseOnVerifyDuringVerificationAbortsProcess)
+{
+ getToVerificationStarted(staticLayoutBlobId);
+ EXPECT_CALL(*verifyMockPtr, abort()).Times(1);
+
+ EXPECT_TRUE(handler->close(session));
+
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(startingBlobs));
+
+ expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
+}
+
+/*
+ * expire(session)
+ */
+TEST_F(FirmwareHandlerVerificationStartedTest,
+ ExpireOnSessionDuringVerificationAbortsProcess)
+{
+ getToVerificationStarted(staticLayoutBlobId);
+ EXPECT_CALL(*verifyMockPtr, abort()).Times(0);
+
+ EXPECT_TRUE(handler->expire(session));
+
+ EXPECT_THAT(handler->getBlobIds(),
+ UnorderedElementsAreArray(startingBlobs));
+
+ expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
+}
+
+} // namespace
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/firmware_unittest.hpp b/bmc/firmware-handler/test/firmware_unittest.hpp
new file mode 100644
index 0000000..e9db343
--- /dev/null
+++ b/bmc/firmware-handler/test/firmware_unittest.hpp
@@ -0,0 +1,254 @@
+#pragma once
+
+#include "data_mock.hpp"
+#include "firmware_handler.hpp"
+#include "flags.hpp"
+#include "image_mock.hpp"
+#include "triggerable_mock.hpp"
+#include "util.hpp"
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+
+using ::testing::Return;
+
+class IpmiOnlyFirmwareStaticTest : public ::testing::Test
+{
+ protected:
+ void SetUp() override
+ {
+ /* Unfortunately, since the FirmwareHandler object ends up owning the
+ * handlers, we can't just share handlers.
+ */
+ std::unique_ptr<ImageHandlerInterface> image =
+ std::make_unique<ImageHandlerMock>();
+ hashImageMock = reinterpret_cast<ImageHandlerMock*>(image.get());
+ blobs.emplace_back(hashBlobId, std::move(image));
+
+ image = std::make_unique<ImageHandlerMock>();
+ imageMock2 = reinterpret_cast<ImageHandlerMock*>(image.get());
+ blobs.emplace_back(staticLayoutBlobId, std::move(image));
+
+ std::unique_ptr<TriggerableActionInterface> prepareMock =
+ std::make_unique<TriggerMock>();
+ prepareMockPtr = reinterpret_cast<TriggerMock*>(prepareMock.get());
+
+ std::unique_ptr<TriggerableActionInterface> verifyMock =
+ std::make_unique<TriggerMock>();
+ verifyMockPtr = reinterpret_cast<TriggerMock*>(verifyMock.get());
+
+ std::unique_ptr<TriggerableActionInterface> updateMock =
+ std::make_unique<TriggerMock>();
+ updateMockPtr = reinterpret_cast<TriggerMock*>(updateMock.get());
+
+ std::unique_ptr<ActionPack> actionPack = std::make_unique<ActionPack>();
+ actionPack->preparation = std::move(prepareMock);
+ actionPack->verification = std::move(verifyMock);
+ actionPack->update = std::move(updateMock);
+
+ ActionMap packs;
+ packs[staticLayoutBlobId] = std::move(actionPack);
+
+ std::vector<DataHandlerPack> data;
+ data.emplace_back(FirmwareFlags::UpdateFlags::ipmi, nullptr);
+
+ handler = FirmwareBlobHandler::CreateFirmwareBlobHandler(
+ std::move(blobs), std::move(data), std::move(packs));
+ }
+
+ void expectedState(FirmwareBlobHandler::UpdateState state)
+ {
+ auto realHandler = dynamic_cast<FirmwareBlobHandler*>(handler.get());
+ EXPECT_EQ(state, realHandler->getCurrentState());
+ }
+
+ void openToInProgress(const std::string& blobId)
+ {
+ if (blobId == hashBlobId)
+ {
+ EXPECT_CALL(*hashImageMock, open(blobId)).WillOnce(Return(true));
+ }
+ else
+ {
+ EXPECT_CALL(*imageMock2, open(blobId)).WillOnce(Return(true));
+ }
+
+ if (blobId != hashBlobId)
+ {
+ EXPECT_CALL(*prepareMockPtr, trigger()).WillOnce(Return(true));
+ }
+ EXPECT_TRUE(handler->open(session, flags, blobId));
+ expectedState(FirmwareBlobHandler::UpdateState::uploadInProgress);
+ }
+
+ void getToVerificationPending(const std::string& blobId)
+ {
+ openToInProgress(blobId);
+
+ if (blobId == hashBlobId)
+ {
+ EXPECT_CALL(*hashImageMock, close()).WillRepeatedly(Return());
+ }
+ else
+ {
+ EXPECT_CALL(*imageMock2, close()).WillRepeatedly(Return());
+ }
+ handler->close(session);
+ expectedState(FirmwareBlobHandler::UpdateState::verificationPending);
+ }
+
+ void getToVerificationStarted(const std::string& blobId)
+ {
+ getToVerificationPending(blobId);
+
+ EXPECT_TRUE(handler->open(session, flags, verifyBlobId));
+ EXPECT_CALL(*verifyMockPtr, trigger()).WillOnce(Return(true));
+
+ EXPECT_TRUE(handler->commit(session, {}));
+ expectedState(FirmwareBlobHandler::UpdateState::verificationStarted);
+ }
+
+ void getToVerificationStartedWitHashBlob()
+ {
+ /* Open both static and hash to check for activeHashBlobId. */
+ getToVerificationPending(staticLayoutBlobId);
+
+ openToInProgress(hashBlobId);
+ EXPECT_CALL(*hashImageMock, close()).WillRepeatedly(Return());
+ handler->close(session);
+ expectedState(FirmwareBlobHandler::UpdateState::verificationPending);
+
+ /* Now the hash is active AND the static image is active. */
+ EXPECT_TRUE(handler->open(session, flags, verifyBlobId));
+ EXPECT_CALL(*verifyMockPtr, trigger()).WillOnce(Return(true));
+
+ EXPECT_TRUE(handler->commit(session, {}));
+ expectedState(FirmwareBlobHandler::UpdateState::verificationStarted);
+ }
+
+ void getToVerificationCompleted(ActionStatus checkResponse)
+ {
+ getToVerificationStarted(staticLayoutBlobId);
+
+ EXPECT_CALL(*verifyMockPtr, status()).WillOnce(Return(checkResponse));
+ blobs::BlobMeta meta;
+ EXPECT_TRUE(handler->stat(session, &meta));
+ expectedState(FirmwareBlobHandler::UpdateState::verificationCompleted);
+ }
+
+ void getToUpdatePending()
+ {
+ getToVerificationCompleted(ActionStatus::success);
+
+ handler->close(session);
+ expectedState(FirmwareBlobHandler::UpdateState::updatePending);
+ }
+
+ void getToUpdateStarted()
+ {
+ getToUpdatePending();
+ EXPECT_TRUE(handler->open(session, flags, updateBlobId));
+
+ EXPECT_CALL(*updateMockPtr, trigger()).WillOnce(Return(true));
+ EXPECT_TRUE(handler->commit(session, {}));
+ expectedState(FirmwareBlobHandler::UpdateState::updateStarted);
+ }
+
+ void getToUpdateCompleted(ActionStatus result)
+ {
+ getToUpdateStarted();
+ EXPECT_CALL(*updateMockPtr, status()).WillOnce(Return(result));
+
+ blobs::BlobMeta meta;
+ EXPECT_TRUE(handler->stat(session, &meta));
+ expectedState(FirmwareBlobHandler::UpdateState::updateCompleted);
+ }
+
+ ImageHandlerMock *hashImageMock, *imageMock2;
+
+ std::vector<HandlerPack> blobs;
+
+ std::unique_ptr<blobs::GenericBlobInterface> handler;
+
+ TriggerMock* prepareMockPtr;
+ TriggerMock* verifyMockPtr;
+ TriggerMock* updateMockPtr;
+
+ std::uint16_t session = 1;
+ std::uint16_t flags =
+ blobs::OpenFlags::write | FirmwareFlags::UpdateFlags::ipmi;
+
+ blobs::BlobMeta expectedIdleMeta = {0xff00, 0, {}};
+
+ std::vector<std::string> startingBlobs = {staticLayoutBlobId, hashBlobId};
+};
+
+class IpmiOnlyFirmwareTest : public ::testing::Test
+{
+ protected:
+ ImageHandlerMock *hashImageMock, *imageMock;
+ std::vector<HandlerPack> blobs;
+ std::unique_ptr<blobs::GenericBlobInterface> handler;
+
+ void SetUp() override
+ {
+ std::unique_ptr<ImageHandlerInterface> image =
+ std::make_unique<ImageHandlerMock>();
+ hashImageMock = reinterpret_cast<ImageHandlerMock*>(image.get());
+ blobs.emplace_back(hashBlobId, std::move(image));
+
+ image = std::make_unique<ImageHandlerMock>();
+ imageMock = reinterpret_cast<ImageHandlerMock*>(image.get());
+ blobs.emplace_back("asdf", std::move(image));
+
+ std::vector<DataHandlerPack> data;
+ data.emplace_back(FirmwareFlags::UpdateFlags::ipmi, nullptr);
+
+ handler = FirmwareBlobHandler::CreateFirmwareBlobHandler(
+ std::move(blobs), std::move(data), CreateActionMap("asdf"));
+ }
+};
+
+class FakeLpcFirmwareTest : public ::testing::Test
+{
+ protected:
+ DataHandlerMock* dataMock;
+ ImageHandlerMock *hashImageMock, *imageMock;
+ std::vector<HandlerPack> blobs;
+ std::unique_ptr<blobs::GenericBlobInterface> handler;
+
+ void SetUp() override
+ {
+ std::unique_ptr<ImageHandlerInterface> image =
+ std::make_unique<ImageHandlerMock>();
+ hashImageMock = reinterpret_cast<ImageHandlerMock*>(image.get());
+ blobs.emplace_back(hashBlobId, std::move(image));
+
+ image = std::make_unique<ImageHandlerMock>();
+ imageMock = reinterpret_cast<ImageHandlerMock*>(image.get());
+ blobs.emplace_back("asdf", std::move(image));
+
+ auto dataMockInstance = std::make_unique<DataHandlerMock>();
+ dataMock = dataMockInstance.get();
+
+ std::vector<DataHandlerPack> data;
+ data.emplace_back(FirmwareFlags::UpdateFlags::ipmi, nullptr);
+ data.emplace_back(FirmwareFlags::UpdateFlags::lpc,
+ std::move(dataMockInstance));
+ handler = FirmwareBlobHandler::CreateFirmwareBlobHandler(
+ std::move(blobs), std::move(data), CreateActionMap("asdf"));
+ }
+};
+
+} // namespace
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/firmware_write_unittest.cpp b/bmc/firmware-handler/test/firmware_write_unittest.cpp
new file mode 100644
index 0000000..e9b70ba
--- /dev/null
+++ b/bmc/firmware-handler/test/firmware_write_unittest.cpp
@@ -0,0 +1,88 @@
+#include "data.hpp"
+#include "data_mock.hpp"
+#include "firmware_handler.hpp"
+#include "firmware_unittest.hpp"
+#include "image_mock.hpp"
+#include "triggerable_mock.hpp"
+#include "util.hpp"
+
+#include <cstdint>
+#include <cstring>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+
+using ::testing::Eq;
+using ::testing::Return;
+
+class FirmwareHandlerWriteTestIpmiOnly : public IpmiOnlyFirmwareTest
+{};
+
+class FirmwareHandlerWriteTestLpc : public FakeLpcFirmwareTest
+{};
+
+TEST_F(FirmwareHandlerWriteTestIpmiOnly, DataTypeIpmiWriteSuccess)
+{
+ /* Verify if data type ipmi, it calls write with the bytes. */
+ EXPECT_CALL(*imageMock, open("asdf")).WillOnce(Return(true));
+
+ EXPECT_TRUE(handler->open(
+ 0, blobs::OpenFlags::write | FirmwareFlags::UpdateFlags::ipmi, "asdf"));
+
+ std::vector<std::uint8_t> bytes = {0xaa, 0x55};
+
+ EXPECT_CALL(*imageMock, write(0, Eq(bytes))).WillOnce(Return(true));
+ EXPECT_TRUE(handler->write(0, 0, bytes));
+}
+
+TEST_F(FirmwareHandlerWriteTestLpc, DataTypeNonIpmiWriteSuccess)
+{
+ /* Verify if data type non-ipmi, it calls write with the length. */
+ EXPECT_CALL(*dataMock, open()).WillOnce(Return(true));
+ EXPECT_CALL(*imageMock, open("asdf")).WillOnce(Return(true));
+
+ EXPECT_TRUE(handler->open(
+ 0, blobs::OpenFlags::write | FirmwareFlags::UpdateFlags::lpc, "asdf"));
+
+ struct ExtChunkHdr request;
+ request.length = 4; /* number of bytes to read. */
+ std::vector<std::uint8_t> ipmiRequest;
+ ipmiRequest.resize(sizeof(request));
+ std::memcpy(ipmiRequest.data(), &request, sizeof(request));
+
+ std::vector<std::uint8_t> bytes = {0x01, 0x02, 0x03, 0x04};
+
+ EXPECT_CALL(*dataMock, copyFrom(request.length)).WillOnce(Return(bytes));
+ EXPECT_CALL(*imageMock, write(0, Eq(bytes))).WillOnce(Return(true));
+ EXPECT_TRUE(handler->write(0, 0, ipmiRequest));
+}
+
+TEST_F(FirmwareHandlerWriteTestLpc, DataTypeNonIpmiWriteFailsBadRequest)
+{
+ /* Verify the data type non-ipmi, if the request's structure doesn't match,
+ * return failure. */
+ EXPECT_CALL(*dataMock, open()).WillOnce(Return(true));
+ EXPECT_CALL(*imageMock, open("asdf")).WillOnce(Return(true));
+
+ EXPECT_TRUE(handler->open(
+ 0, blobs::OpenFlags::write | FirmwareFlags::UpdateFlags::lpc, "asdf"));
+
+ struct ExtChunkHdr request;
+ request.length = 4; /* number of bytes to read. */
+
+ std::vector<std::uint8_t> ipmiRequest;
+ ipmiRequest.resize(sizeof(request));
+ std::memcpy(ipmiRequest.data(), &request, sizeof(request));
+ ipmiRequest.push_back(1);
+
+ /* ipmiRequest is too large by one byte. */
+ EXPECT_FALSE(handler->write(0, 0, ipmiRequest));
+}
+
+} // namespace
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/firmware_writemeta_unittest.cpp b/bmc/firmware-handler/test/firmware_writemeta_unittest.cpp
new file mode 100644
index 0000000..512754a
--- /dev/null
+++ b/bmc/firmware-handler/test/firmware_writemeta_unittest.cpp
@@ -0,0 +1,51 @@
+#include "data_mock.hpp"
+#include "firmware_handler.hpp"
+#include "firmware_unittest.hpp"
+#include "image_mock.hpp"
+#include "triggerable_mock.hpp"
+#include "util.hpp"
+
+#include <memory>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+
+using ::testing::Eq;
+using ::testing::Return;
+
+class FirmwareHandlerWriteMetaTest : public FakeLpcFirmwareTest
+{};
+
+TEST_F(FirmwareHandlerWriteMetaTest, WriteConfigParametersFailIfOverIPMI)
+{
+ EXPECT_CALL(*imageMock, open("asdf")).WillOnce(Return(true));
+
+ EXPECT_TRUE(handler->open(
+ 0, blobs::OpenFlags::write | FirmwareFlags::UpdateFlags::ipmi, "asdf"));
+
+ std::vector<std::uint8_t> bytes = {0xaa, 0x55};
+
+ EXPECT_FALSE(handler->writeMeta(0, 0, bytes));
+}
+
+TEST_F(FirmwareHandlerWriteMetaTest, WriteConfigParametersPassedThrough)
+{
+ EXPECT_CALL(*dataMock, open()).WillOnce(Return(true));
+ EXPECT_CALL(*imageMock, open("asdf")).WillOnce(Return(true));
+
+ EXPECT_TRUE(handler->open(
+ 0, blobs::OpenFlags::write | FirmwareFlags::UpdateFlags::lpc, "asdf"));
+
+ std::vector<std::uint8_t> bytes = {0x01, 0x02, 0x03, 0x04};
+
+ EXPECT_CALL(*dataMock, writeMeta(Eq(bytes))).WillOnce(Return(true));
+ EXPECT_TRUE(handler->writeMeta(0, 0, bytes));
+}
+
+} // namespace
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/image_mock.hpp b/bmc/firmware-handler/test/image_mock.hpp
new file mode 100644
index 0000000..fb10c39
--- /dev/null
+++ b/bmc/firmware-handler/test/image_mock.hpp
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "image_handler.hpp"
+
+#include <gmock/gmock.h>
+
+namespace ipmi_flash
+{
+
+class ImageHandlerMock : public ImageHandlerInterface
+{
+ public:
+ virtual ~ImageHandlerMock() = default;
+
+ MOCK_METHOD1(open, bool(const std::string&));
+ MOCK_METHOD0(close, void());
+ MOCK_METHOD2(write, bool(std::uint32_t, const std::vector<std::uint8_t>&));
+ MOCK_METHOD0(getSize, int());
+};
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/triggerable_mock.hpp b/bmc/firmware-handler/test/triggerable_mock.hpp
new file mode 100644
index 0000000..3469127
--- /dev/null
+++ b/bmc/firmware-handler/test/triggerable_mock.hpp
@@ -0,0 +1,41 @@
+#pragma once
+
+#include "firmware_handler.hpp"
+#include "status.hpp"
+
+#include <memory>
+#include <string>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+// TriggerableActionInterface
+
+class TriggerMock : public TriggerableActionInterface
+{
+ public:
+ MOCK_METHOD0(trigger, bool());
+ MOCK_METHOD0(abort, void());
+ MOCK_METHOD0(status, ActionStatus());
+};
+
+std::unique_ptr<TriggerableActionInterface> CreateTriggerMock()
+{
+ return std::make_unique<TriggerMock>();
+}
+
+ActionMap CreateActionMap(const std::string& blobPath)
+{
+ std::unique_ptr<ActionPack> actionPack = std::make_unique<ActionPack>();
+ actionPack->preparation = CreateTriggerMock();
+ actionPack->verification = CreateTriggerMock();
+ actionPack->update = CreateTriggerMock();
+
+ ActionMap map;
+ map[blobPath] = std::move(actionPack);
+ return map;
+}
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/test/window_mapper_mock.hpp b/bmc/firmware-handler/test/window_mapper_mock.hpp
new file mode 100644
index 0000000..dd3a89b
--- /dev/null
+++ b/bmc/firmware-handler/test/window_mapper_mock.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "window_hw_interface.hpp"
+
+#include <cstdint>
+#include <utility>
+#include <vector>
+
+#include <gmock/gmock.h>
+
+namespace ipmi_flash
+{
+
+class HardwareInterfaceMock : public HardwareMapperInterface
+{
+ public:
+ virtual ~HardwareInterfaceMock() = default;
+
+ MOCK_METHOD0(open, MemorySet());
+ MOCK_METHOD0(close, ());
+ MOCK_METHOD2(mapWindow, WindowMapResult(std::uint32_t, std::uint32_t));
+};
+
+} // namespace ipmi_flash
diff --git a/bmc/firmware-handler/window_hw_interface.hpp b/bmc/firmware-handler/window_hw_interface.hpp
new file mode 100644
index 0000000..7d15521
--- /dev/null
+++ b/bmc/firmware-handler/window_hw_interface.hpp
@@ -0,0 +1,63 @@
+#pragma once
+
+#include <cstdint>
+#include <utility>
+#include <vector>
+
+namespace ipmi_flash
+{
+
+struct MemorySet
+{
+ int mappedFd = -1;
+ std::uint8_t* mapped = nullptr;
+};
+
+/** The result from the mapWindow command. */
+struct WindowMapResult
+{
+ /* The response can validly be 0, or EFBIG. If it's EFBIG that means the
+ * region available is within the requested region. If the value is anything
+ * else, it's a complete failure.
+ */
+ std::uint8_t response;
+ std::uint32_t windowOffset;
+ std::uint32_t windowSize;
+};
+
+/**
+ * Different LPC (or P2a) memory map implementations may require different
+ * mechanisms for specific tasks such as mapping the memory window or copying
+ * out data.
+ */
+class HardwareMapperInterface
+{
+ public:
+ virtual ~HardwareMapperInterface() = default;
+
+ /**
+ * Open the driver or whatever and map the region.
+ */
+ virtual MemorySet open() = 0;
+
+ /**
+ * Close the mapper. This could mean, send an ioctl to turn off the region,
+ * or unmap anything mmapped.
+ */
+ virtual void close() = 0;
+
+ /**
+ * Returns a windowOffset and windowSize if the requested window was mapped.
+ *
+ * TODO: If the length requested is too large, windowSize will be written
+ * with the max size that the BMC can map and returns false.
+ *
+ * @param[in] address - The address for mapping (passed to LPC window)
+ * @param[in] length - The length of the region
+ * @return WindowMapResult - the result of the call
+ */
+ virtual WindowMapResult mapWindow(std::uint32_t address,
+ std::uint32_t length) = 0;
+};
+
+} // namespace ipmi_flash