Add fwu test framework components

Adds test support components to support integration test of
fwu service components. The test framework is intended to
allow for testcase reuse when testing in different environments.

Signed-off-by: Julian Hall <julian.hall@arm.com>
Change-Id: Ib269ee30615774ac1ad3b9aa7cb473be33807e98
diff --git a/components/service/fwu/test/fwu_client/direct/component.cmake b/components/service/fwu/test/fwu_client/direct/component.cmake
new file mode 100644
index 0000000..b9bbe1b
--- /dev/null
+++ b/components/service/fwu/test/fwu_client/direct/component.cmake
@@ -0,0 +1,13 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+if (NOT DEFINED TGT)
+	message(FATAL_ERROR "mandatory parameter TGT is not defined.")
+endif()
+
+target_sources(${TGT} PRIVATE
+	"${CMAKE_CURRENT_LIST_DIR}/direct_fwu_client.cpp"
+	)
diff --git a/components/service/fwu/test/fwu_client/direct/direct_fwu_client.cpp b/components/service/fwu/test/fwu_client/direct/direct_fwu_client.cpp
new file mode 100644
index 0000000..933258e
--- /dev/null
+++ b/components/service/fwu/test/fwu_client/direct/direct_fwu_client.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <cstring>
+#include <service/fwu/agent/update_agent.h>
+#include "direct_fwu_client.h"
+
+
+direct_fwu_client::direct_fwu_client(struct update_agent *update_agent) :
+	fwu_client(),
+	m_update_agent(update_agent),
+	m_read_buf()
+{
+	/* The read buffer represents a communication buffer that will
+	 * constrain the amount of data that may be read in a single read.
+	 */
+	memset(m_read_buf, 0, READ_BUF_SIZE);
+}
+
+direct_fwu_client::~direct_fwu_client()
+{
+
+}
+
+int direct_fwu_client::begin_staging(void)
+{
+	return update_agent_begin_staging(m_update_agent);
+}
+
+int direct_fwu_client::end_staging(void)
+{
+	return update_agent_end_staging(m_update_agent);
+}
+
+int direct_fwu_client::cancel_staging(void)
+{
+	return update_agent_cancel_staging(m_update_agent);
+}
+
+int direct_fwu_client::accept(
+	const struct uuid_octets *image_type_uuid)
+{
+	return update_agent_accept(m_update_agent, image_type_uuid);
+}
+
+int direct_fwu_client::select_previous(void)
+{
+	return update_agent_select_previous(m_update_agent);
+}
+
+int direct_fwu_client::open(
+	const struct uuid_octets *uuid,
+	uint32_t *handle)
+{
+	return update_agent_open(m_update_agent, uuid, handle);
+}
+
+int direct_fwu_client::commit(
+	uint32_t handle,
+	bool accepted)
+{
+	return update_agent_commit(m_update_agent, handle, accepted);
+}
+
+int direct_fwu_client::write_stream(
+	uint32_t handle,
+	const uint8_t *data,
+	size_t data_len)
+{
+	return update_agent_write_stream(m_update_agent, handle, data, data_len);
+}
+
+int direct_fwu_client::read_stream(
+	uint32_t handle,
+	uint8_t *buf,
+	size_t buf_size,
+	size_t *read_len,
+	size_t *total_len)
+{
+	int status = update_agent_read_stream(m_update_agent, handle,
+		m_read_buf, READ_BUF_SIZE,
+		read_len, total_len);
+
+	if (!status && buf && buf_size) {
+
+		size_t copy_len = (*read_len <= buf_size) ?
+			*read_len : buf_size;
+
+		memcpy(buf, m_read_buf, copy_len);
+	}
+
+	return status;
+}
+
diff --git a/components/service/fwu/test/fwu_client/direct/direct_fwu_client.h b/components/service/fwu/test/fwu_client/direct/direct_fwu_client.h
new file mode 100644
index 0000000..c28ecf3
--- /dev/null
+++ b/components/service/fwu/test/fwu_client/direct/direct_fwu_client.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef DIRECT_FWU_CLIENT_H
+#define DIRECT_FWU_CLIENT_H
+
+#include <service/fwu/test/fwu_client/fwu_client.h>
+
+/* Public interface dependencies */
+struct update_agent;
+
+/*
+ * An fwu_client that interfaces directly with an update_agent. Can be
+ * used for component level testing where tests and update_agent are
+ * combined in the same build.
+ */
+class direct_fwu_client : public fwu_client
+{
+public:
+
+	direct_fwu_client(struct update_agent *update_agent);
+	~direct_fwu_client();
+
+	int begin_staging(void);
+
+	int end_staging(void);
+
+	int cancel_staging(void);
+
+	int accept(
+		const struct uuid_octets *image_type_uuid);
+
+	int select_previous(void);
+
+	int open(
+		const struct uuid_octets *uuid,
+		uint32_t *handle);
+
+	int commit(
+		uint32_t handle,
+		bool accepted);
+
+	int write_stream(
+		uint32_t handle,
+		const uint8_t *data,
+		size_t data_len);
+
+	int read_stream(
+		uint32_t handle,
+		uint8_t *buf,
+		size_t buf_size,
+		size_t *read_len,
+		size_t *total_len);
+
+private:
+
+	static const size_t READ_BUF_SIZE = 512;
+
+	struct update_agent *m_update_agent;
+	uint8_t m_read_buf[READ_BUF_SIZE];
+};
+
+#endif /* DIRECT_FWU_CLIENT_H */
diff --git a/components/service/fwu/test/fwu_client/fwu_client.h b/components/service/fwu/test/fwu_client/fwu_client.h
new file mode 100644
index 0000000..d9773e9
--- /dev/null
+++ b/components/service/fwu/test/fwu_client/fwu_client.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef FWU_CLIENT_H
+#define FWU_CLIENT_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <common/uuid/uuid.h>
+
+/*
+ * Presents a client interface for interacting with a fwu service provider.
+ * Test cases that use this interface can potentially be reused with alternative
+ * service provider deployments.
+ */
+class fwu_client
+{
+public:
+
+	fwu_client()
+	{
+
+	}
+
+	virtual ~fwu_client()
+	{
+
+	}
+
+	virtual int begin_staging(void) = 0;
+
+	virtual int end_staging(void) = 0;
+
+	virtual int cancel_staging(void) = 0;
+
+	virtual int accept(
+		const struct uuid_octets *image_type_uuid) = 0;
+
+	virtual int select_previous(void) = 0;
+
+	virtual int open(
+		const struct uuid_octets *uuid,
+		uint32_t *handle) = 0;
+
+	virtual int commit(
+		uint32_t handle,
+		bool accepted) = 0;
+
+	virtual int write_stream(
+		uint32_t handle,
+		const uint8_t *data,
+		size_t data_len) = 0;
+
+	virtual int read_stream(
+		uint32_t handle,
+		uint8_t *buf,
+		size_t buf_size,
+		size_t *read_len,
+		size_t *total_len) = 0;
+};
+
+#endif /* FWU_CLIENT_H */
diff --git a/components/service/fwu/test/fwu_dut/component.cmake b/components/service/fwu/test/fwu_dut/component.cmake
new file mode 100644
index 0000000..3dcf12e
--- /dev/null
+++ b/components/service/fwu/test/fwu_dut/component.cmake
@@ -0,0 +1,13 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+if (NOT DEFINED TGT)
+	message(FATAL_ERROR "mandatory parameter TGT is not defined.")
+endif()
+
+target_sources(${TGT} PRIVATE
+	"${CMAKE_CURRENT_LIST_DIR}/fwu_dut.cpp"
+	)
diff --git a/components/service/fwu/test/fwu_dut/fwu_dut.cpp b/components/service/fwu/test/fwu_dut/fwu_dut.cpp
new file mode 100644
index 0000000..bfe2cef
--- /dev/null
+++ b/components/service/fwu/test/fwu_dut/fwu_dut.cpp
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2022-2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <cassert>
+#include <cstring>
+#include <string>
+#include <common/endian/le.h>
+#include <service/fwu/test/metadata_checker/metadata_checker_v1.h>
+#include <CppUTest/TestHarness.h>
+#include "fwu_dut.h"
+
+const char *fwu_dut::VALID_IMAGE_HEADER = "Valid-fw-image-";
+
+fwu_dut::fwu_dut() :
+	m_generated_image_count(0),
+	m_metadata_version(1)
+{
+
+}
+
+fwu_dut::fwu_dut(unsigned int metadata_version) :
+	m_generated_image_count(0),
+	m_metadata_version(metadata_version)
+{
+
+}
+
+fwu_dut::~fwu_dut()
+{
+
+}
+
+void fwu_dut::generate_image_data(
+	std::vector<uint8_t> *image_data,
+	size_t image_size)
+{
+	std::string fixed_header(VALID_IMAGE_HEADER);
+
+	/* Image header consists of:
+	 *    - Fixed header
+	 *    - Image size (32-bit)
+	 *    - Sequence count (32-bit)
+	 */
+	size_t header_len =
+		fixed_header.size() +
+		sizeof(uint32_t) +
+		sizeof(uint32_t);
+
+	/* Reserve space */
+	image_data->resize(image_size);
+
+	if (image_size >= header_len) {
+
+		/* Prepare image header */
+		memcpy(image_data->data(), fixed_header.data(), fixed_header.size());
+
+		store_u32_le(image_data->data(), fixed_header.size(),
+			static_cast<uint32_t>(image_size));
+
+		store_u32_le(image_data->data(), fixed_header.size() + sizeof(uint32_t),
+			static_cast<uint32_t>(m_generated_image_count));
+
+		/* Fill any remaining space */
+		if (image_size > header_len) {
+
+			uint8_t fill_val = static_cast<uint8_t>(m_generated_image_count);
+			size_t fill_len = image_size - header_len;
+
+			memset(image_data->data() + header_len, fill_val, fill_len);
+		}
+	} else if (image_size > 0) {
+
+		/* No room for header so just initialise to fixed value. This will
+		 * fail any image verification check.
+		 */
+		memset(image_data->data(), 0, image_size);
+	}
+
+	++m_generated_image_count;
+}
+
+void fwu_dut::verify_image(
+	struct volume *volume)
+{
+	std::string fixed_header(VALID_IMAGE_HEADER);
+	size_t header_len = fixed_header.size() + sizeof(uint32_t) + sizeof(uint32_t);
+
+	/* Read image header */
+	uint8_t header_buf[header_len];
+	size_t total_bytes_read = 0;
+
+	int status =  volume_read(volume, (uintptr_t)header_buf, header_len, &total_bytes_read);
+	LONGS_EQUAL(0, status);
+	CHECK_TRUE(total_bytes_read == header_len);
+
+	/* Verify header and extract values */
+	MEMCMP_EQUAL(fixed_header.data(), header_buf, fixed_header.size());
+
+	size_t image_size = load_u32_le(header_buf, fixed_header.size());
+	uint32_t seq_num = load_u32_le(header_buf, fixed_header.size() + sizeof(uint32_t));
+
+	CHECK_TRUE(image_size >= header_len);
+
+	/* Read the remainder of the image and check data is as expected */
+	uint8_t expected_fill_val = static_cast<uint8_t>(seq_num);
+
+	while (total_bytes_read < image_size) {
+
+		uint8_t read_buf[1024];
+		size_t bytes_read = 0;
+		size_t bytes_remaining = image_size - total_bytes_read;
+		size_t bytes_to_read = (bytes_remaining > sizeof(read_buf)) ?
+			sizeof(read_buf) : bytes_remaining;
+
+		status =  volume_read(volume, (uintptr_t)read_buf, bytes_to_read, &bytes_read);
+		LONGS_EQUAL(0, status);
+		UNSIGNED_LONGS_EQUAL(bytes_to_read, bytes_read);
+
+		for (size_t i = 0; i < bytes_read; i++)
+			BYTES_EQUAL(expected_fill_val, read_buf[i]);
+
+		total_bytes_read += bytes_read;
+	}
+
+	UNSIGNED_LONGS_EQUAL(image_size, total_bytes_read);
+}
+
+void fwu_dut::whole_volume_image_type_uuid(
+	unsigned int location_index,
+	struct uuid_octets *uuid) const
+{
+	static const char *img_type_guid[] = {
+		"cb0faf2f-f498-49fb-9810-cb09dac1184f",
+		"e9755079-db61-4d90-8a8a-45727eaa1c6e",
+		"d439bc29-83e6-40c7-babe-eb59e415c05e",
+		"433a6c93-8bb0-4966-b49c-dc0c56080d19"
+	};
+
+	CHECK_TRUE(location_index < sizeof(img_type_guid)/sizeof(char *));
+
+	uuid_guid_octets_from_canonical(uuid, img_type_guid[location_index]);
+}
+
+metadata_checker *fwu_dut::create_metadata_checker(
+	metadata_fetcher *metadata_fetcher,
+	unsigned int num_images) const
+{
+	if (m_metadata_version == 1)
+		return new metadata_checker_v1(metadata_fetcher, num_images);
+
+	/* Unsupported metadata version */
+	assert(false);
+
+	return NULL;
+}
+
+unsigned int fwu_dut::metadata_version(void) const
+{
+	return m_metadata_version;
+}
diff --git a/components/service/fwu/test/fwu_dut/fwu_dut.h b/components/service/fwu/test/fwu_dut/fwu_dut.h
new file mode 100644
index 0000000..87b3730
--- /dev/null
+++ b/components/service/fwu/test/fwu_dut/fwu_dut.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2022-2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef FWU_DUT_H
+#define FWU_DUT_H
+
+#include <vector>
+#include <common/uuid/uuid.h>
+#include <service/fwu/agent/fw_directory.h>
+#include <service/fwu/test/metadata_checker/metadata_checker.h>
+#include <service/fwu/test/fwu_client/fwu_client.h>
+#include <media/volume/volume.h>
+
+/*
+ * An fwu_dut represents a device-under-test for the purpose of testing
+ * firmware update functionality. The fwu_dut presents a common interface
+ * that decouples test cases from details of the concrete device under test.
+ */
+class fwu_dut
+{
+public:
+
+	fwu_dut();
+	fwu_dut(unsigned int metadata_version);
+	virtual ~fwu_dut();
+
+	/**
+	 * \brief Simulates a boot of the DUT.
+	 *
+	 * Mimics boot loader FWU metadata usage and performs a simple verification
+	 * of fw images installed in the bank corresponding to the selected boot
+	 * index. Initializes the update_agent in the same way as with a real
+	 * deployment.
+	 *
+	 * \param[in] from_active_bank   Where to boot from
+	 */
+	virtual void boot(bool from_active_bank = true) = 0;
+
+	/**
+	 * \brief Simulates a shutdown of the DUT
+	 *
+	 * The update_agent is de-initialized but image and metadata storage is
+	 * left alone. Simulates a physical device shutdown.
+	 */
+	virtual void shutdown(void) = 0;
+
+	/**
+	 * \brief Returns boot info seen by the boot loader
+	 *
+	 * \return A boot_info structure
+	 */
+	virtual struct boot_info get_boot_info(void) const = 0;
+
+	/**
+	 * \brief Factory method to construct a metadata_checker
+	 *
+	 * To decouple test code from different versions of FWU metadata, the
+	 * metadata_checker class can be used to provide a neutral interface.
+	 * This method creates a concrete metadata_checker that is compatible
+	 * with the version generated by the DUT. The caller is responsible for
+	 * destroying the metadata_checker using delete.
+	 *
+	 * \param[in] is_primary True to use the primary, false the backup copy
+	 *
+	 * \return The constructed metadata_checker
+	 */
+	virtual metadata_checker *create_metadata_checker(
+		bool is_primary = true) const = 0;
+
+	/**
+	 * \brief Factory method to construct a fwu_client
+	 *
+	 * Constructs a fwu_client to provide an interface for interacting with
+	 * the fwu service provider associated with the DUT. The caller is
+	 * responsible for destroying the fwu_client using delete.
+	 *
+	 * \return The constructed fwu_client
+	 */
+	virtual fwu_client *create_fwu_client(void) = 0;
+
+	/**
+	 * \brief Generate fw image data
+	 *
+	 * Generates a fake image that can be checked during dut boot for
+	 * validity. Use to provide image data for update testing
+	 *
+	 * \param[in] image_data   Image data written to the provided vector
+	 * \param[in] image_size   The required image size
+	 */
+	void generate_image_data(
+		std::vector<uint8_t> *image_data,
+		size_t image_size = 4096);
+
+	/**
+	 * \brief Returns image type UUIDs
+	 *
+	 *  Image type UUIDS for updatable whole volume images. Assumes
+	 *  one whole volume image type per location.
+	 *
+	 * \param[in]  location_index  0..num_locations-1
+	 * \param[out] uuid            Outputs the GUID octets
+	 */
+	virtual void whole_volume_image_type_uuid(
+		unsigned int location_index,
+		struct uuid_octets *uuid) const;
+
+protected:
+
+	/**
+	 * \brief Verifies an image
+	 *
+	 *  Simulates bootloader image verification. This checks that a valid header
+	 *  exists and if it does, checks that remaining payload data is as expected.
+	 *  The volume object must have already been opened with a seek position at the
+	 *  start of the volume.
+	 *
+	 * \param[in]  volume     The volume to read
+	 */
+	static void verify_image(
+		struct volume *volume);
+
+	/**
+	 * \brief Create a metadata_checker for the configured metadata version
+	 *
+	 *  Creates a metadata_checker using 'new' that is compatible with the metadata
+	 *  version specified for the DUT on construction. Uses the provided metadata_fetcher
+	 *  to fetch the metadata prior to checking.
+	 *
+	 * \param[in]  metadata_fetcher     To fetch the metadata
+	 * \param[in]  num_images           The expected number of images
+	 */
+	metadata_checker *create_metadata_checker(
+		metadata_fetcher *metadata_fetcher,
+		unsigned int num_images) const;
+
+	/**
+	 * \brief Returns the configured FWU metadata version
+	 *
+	 *  In a real system, the bootloader will advertise the metadata version that it
+	 *  understands.
+	 *
+	 * \return Metadata version
+	 */
+	unsigned int metadata_version(void) const;
+
+private:
+	static const char *VALID_IMAGE_HEADER;
+
+	unsigned int m_generated_image_count;
+	unsigned int m_metadata_version;
+};
+
+#endif /* FWU_DUT_H */
diff --git a/components/service/fwu/test/fwu_dut/sim/component.cmake b/components/service/fwu/test/fwu_dut/sim/component.cmake
new file mode 100644
index 0000000..310a2b0
--- /dev/null
+++ b/components/service/fwu/test/fwu_dut/sim/component.cmake
@@ -0,0 +1,13 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+if (NOT DEFINED TGT)
+	message(FATAL_ERROR "mandatory parameter TGT is not defined.")
+endif()
+
+target_sources(${TGT} PRIVATE
+	"${CMAKE_CURRENT_LIST_DIR}/sim_fwu_dut.cpp"
+	)
diff --git a/components/service/fwu/test/fwu_dut/sim/sim_fwu_dut.cpp b/components/service/fwu/test/fwu_dut/sim/sim_fwu_dut.cpp
new file mode 100644
index 0000000..ae4c1c0
--- /dev/null
+++ b/components/service/fwu/test/fwu_dut/sim/sim_fwu_dut.cpp
@@ -0,0 +1,431 @@
+/*
+ * Copyright (c) 2022-2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <sstream>
+#include <CppUTest/TestHarness.h>
+#include <media/volume/index/volume_index.h>
+#include <media/disk/guid.h>
+#include <service/fwu/installer/installer_index.h>
+#include <service/fwu/fw_store/banked/volume_id.h>
+#include <service/fwu/fw_store/banked/metadata_serializer/v1/metadata_serializer_v1.h>
+#include <service/fwu/inspector/direct/direct_fw_inspector.h>
+#include <service/fwu/test/fwu_client/direct/direct_fwu_client.h>
+#include <service/fwu/test/metadata_fetcher/volume/volume_metadata_fetcher.h>
+#include "sim_fwu_dut.h"
+
+sim_fwu_dut::sim_fwu_dut(
+	unsigned int num_locations,
+	bool allow_partial_updates) :
+	fwu_dut(),
+	m_is_booted(false),
+	m_is_first_boot(true),
+	m_boot_info(),
+	m_metadata_checker(NULL),
+	m_num_locations(num_locations),
+	m_fw_flash(),
+	m_partitioned_block_store(),
+	m_block_store(NULL),
+	m_fw_volume_used_count(0),
+	m_fw_volume_pool(),
+	m_raw_installer_used_count(0),
+	m_raw_installer_pool(),
+	m_copy_installer_used_count(0),
+	m_copy_installer_pool(),
+	m_update_agent(),
+	m_fw_store()
+{
+	m_boot_info = {0};
+
+	volume_index_init();
+	installer_index_init();
+
+	construct_storage(num_locations);
+	construct_fw_volumes(num_locations);
+	construct_installers(num_locations, allow_partial_updates);
+
+	install_factory_images(num_locations);
+
+	m_metadata_checker = create_metadata_checker();
+}
+
+sim_fwu_dut::~sim_fwu_dut()
+{
+	shutdown();
+
+	delete m_metadata_checker;
+	m_metadata_checker = NULL;
+
+	destroy_installers();
+	destroy_fw_volumes();
+	destroy_storage();
+
+	installer_index_clear();
+	volume_index_clear();
+}
+
+void sim_fwu_dut::boot(bool from_active_bank)
+{
+	if (m_is_booted)
+		return;
+
+	if (m_is_first_boot) {
+
+		/* First boot where valid FWU metadata does not yet exist. */
+		m_boot_info.boot_index =
+		m_boot_info.active_index =
+		m_boot_info.previous_active_index = FIRST_BOOT_BANK_INDEX;
+		m_is_first_boot = false;
+
+	} else {
+
+		/* On subsequent boots, mimic the boot loader and derive boot
+		 * info from the FWU metadata.
+		 */
+		m_metadata_checker->get_active_indices(
+			&m_boot_info.active_index,
+			&m_boot_info.previous_active_index);
+
+		m_boot_info.boot_index = (from_active_bank) ?
+			m_boot_info.active_index :
+			m_boot_info.previous_active_index;
+	}
+
+	/* Now mimic boot loader image verification */
+	verify_boot_images(m_boot_info.boot_index);
+
+	/* Performs the generic update agent initialization that occurs on
+	 * each system boot.
+	 */
+	int status = banked_fw_store_init(&m_fw_store, metadata_serializer_v1());
+	LONGS_EQUAL(0, status);
+
+	status = update_agent_init(
+		&m_update_agent,
+		m_boot_info.boot_index,
+		direct_fw_inspector_inspect,
+		&m_fw_store);
+	LONGS_EQUAL(0, status);
+
+	m_is_booted = true;
+}
+
+void sim_fwu_dut::shutdown(void)
+{
+	if (!m_is_booted)
+		return;
+
+	/* Ensure all install streams are closed */
+	update_agent_cancel_staging(&m_update_agent);
+
+	update_agent_deinit(&m_update_agent);
+	banked_fw_store_deinit(&m_fw_store);
+
+	m_is_booted = false;
+}
+
+struct boot_info sim_fwu_dut::get_boot_info(void) const
+{
+	return m_boot_info;
+}
+
+metadata_checker *sim_fwu_dut::create_metadata_checker(bool is_primary) const
+{
+	struct uuid_octets partition_guid;
+
+	fwu_metadata_partition_guid(is_primary, &partition_guid);
+
+	metadata_fetcher *metadata_fetcher =
+		new volume_metadata_fetcher(&partition_guid, m_block_store);
+
+	return fwu_dut::create_metadata_checker(metadata_fetcher, m_num_locations);
+}
+
+fwu_client *sim_fwu_dut::create_fwu_client(void)
+{
+	return new direct_fwu_client(&m_update_agent);
+}
+
+void sim_fwu_dut::fw_partition_guid(
+	unsigned int location_index,
+	unsigned int bank_index,
+	struct uuid_octets *uuid) const
+{
+	static const char *partition_guid[MAX_LOCATIONS][BANK_SCHEME_NUM_BANKS] = {
+		{"318757ec-82a0-48ce-a0d9-dfdeee6847a9",
+			"3455a361-4074-42a3-ac0f-0e217c58494a"},
+		{"5feb0dff-3d4b-42ab-9635-c112cf641f2b",
+			"d75c1efc-6c8c-458a-aa72-41810d1d8a99"},
+		{"0558ce63-db89-40ad-8039-1fafeb057fc8",
+			"f07f1be5-077d-487f-9bd9-1ae33ed580e9"},
+		{"3b00979c-e776-4e79-b675-f78681b4cce3",
+			"3de167ca-5e8c-4f05-9f87-e0c6a57538a5"}
+	};
+
+	CHECK_TRUE(location_index < MAX_LOCATIONS);
+	CHECK_TRUE(bank_index < BANK_SCHEME_NUM_BANKS);
+
+	uuid_guid_octets_from_canonical(uuid,
+		partition_guid[location_index][bank_index]);
+}
+
+void sim_fwu_dut::fwu_metadata_partition_guid(
+	bool is_primary,
+	struct uuid_octets *uuid) const
+{
+	if (is_primary)
+		uuid_guid_octets_from_canonical(uuid,
+			DISK_GUID_UNIQUE_PARTITION_PRIMARY_FWU_METADATA);
+	else
+		uuid_guid_octets_from_canonical(uuid,
+			DISK_GUID_UNIQUE_PARTITION_BACKUP_FWU_METADATA);
+}
+
+void sim_fwu_dut::disk_guid(
+	struct uuid_octets *uuid) const
+{
+	uuid_guid_octets_from_canonical(uuid,
+		"da92a93d-91d3-4b74-9102-7b45c21fe7db");
+}
+
+void sim_fwu_dut::construct_storage(unsigned int num_locations)
+{
+	size_t required_storage_blocks =
+		METADATA_VOLUME_NUM_BLOCKS * 2 +
+		FW_VOLUME_NUM_BLOCKS * BANK_SCHEME_NUM_BANKS * num_locations;
+
+	struct uuid_octets flash_store_guid;
+
+	disk_guid(&flash_store_guid);
+
+	/* Construct the 'flash' */
+	struct block_store *flash_store = ram_block_store_init(
+		&m_fw_flash,
+		&flash_store_guid,
+		required_storage_blocks,
+		FLASH_BLOCK_SIZE);
+
+	/* Stack a partitioned_block_store over the flash */
+	m_block_store = partitioned_block_store_init(
+		&m_partitioned_block_store,
+		0,
+		&flash_store_guid,
+		flash_store,
+		NULL);
+
+	/* Add all disk partitions */
+	unsigned int lba = 0;
+	struct uuid_octets partition_guid;
+
+	/* First the primary fwu metadata partition */
+	fwu_metadata_partition_guid(true, &partition_guid);
+	bool is_added = partitioned_block_store_add_partition(
+		&m_partitioned_block_store,
+		&partition_guid,
+		lba, lba + METADATA_VOLUME_NUM_BLOCKS - 1,
+		0, NULL);
+
+	CHECK_TRUE(is_added);
+	lba += METADATA_VOLUME_NUM_BLOCKS;
+
+	/* Add partitions for each fw location */
+	for (unsigned int location = 0; location < num_locations; location++) {
+
+		for (unsigned int bank = 0; bank < BANK_SCHEME_NUM_BANKS; bank++) {
+
+			fw_partition_guid(location, bank, &partition_guid);
+			is_added = partitioned_block_store_add_partition(
+					&m_partitioned_block_store,
+					&partition_guid,
+					lba, lba + FW_VOLUME_NUM_BLOCKS - 1,
+					0, NULL);
+
+			CHECK_TRUE(is_added);
+			lba += FW_VOLUME_NUM_BLOCKS;
+		}
+	}
+
+	/* Finally, add the backup fwu metadata partition */
+	fwu_metadata_partition_guid(false, &partition_guid);
+	is_added = partitioned_block_store_add_partition(
+		&m_partitioned_block_store,
+		&partition_guid,
+		lba, lba + METADATA_VOLUME_NUM_BLOCKS - 1,
+		0, NULL);
+
+	CHECK_TRUE(is_added);
+}
+
+void sim_fwu_dut::destroy_storage(void)
+{
+	partitioned_block_store_deinit(&m_partitioned_block_store);
+	ram_block_store_deinit(&m_fw_flash);
+}
+
+void sim_fwu_dut::construct_fw_volumes(unsigned int num_locations)
+{
+	int status = 0;
+	struct volume *volume = NULL;
+	struct uuid_octets partition_guid;
+
+	/* Construct volume for primary fwu metadata access */
+	fwu_metadata_partition_guid(true, &partition_guid);
+
+	status = block_volume_init(&m_fw_volume_pool[m_fw_volume_used_count],
+		m_block_store, &partition_guid, &volume);
+	LONGS_EQUAL(0, status);
+	CHECK_TRUE(volume);
+
+	status = volume_index_add(BANKED_VOLUME_ID_PRIMARY_METADATA, volume);
+	LONGS_EQUAL(0, status);
+	++m_fw_volume_used_count;
+
+	/* Construct volume for backup fwu metadata access */
+	fwu_metadata_partition_guid(false, &partition_guid);
+
+	status = block_volume_init(&m_fw_volume_pool[m_fw_volume_used_count],
+		m_block_store, &partition_guid, &volume);
+	LONGS_EQUAL(0, status);
+	CHECK_TRUE(volume);
+
+	status = volume_index_add(BANKED_VOLUME_ID_BACKUP_METADATA, volume);
+	LONGS_EQUAL(0, status);
+	++m_fw_volume_used_count;
+
+	/* Construct volumes for each fw storage partition */
+	for (unsigned int location = 0; location < num_locations; location++) {
+
+		for (unsigned int bank = 0; bank < BANK_SCHEME_NUM_BANKS; bank++) {
+
+			fw_partition_guid(location, bank, &partition_guid);
+
+			status = block_volume_init(&m_fw_volume_pool[m_fw_volume_used_count],
+				m_block_store, &partition_guid, &volume);
+			LONGS_EQUAL(0, status);
+			CHECK_TRUE(volume);
+
+			status = volume_index_add(
+				banked_volume_id(location, banked_usage_id(bank)),
+				volume);
+			LONGS_EQUAL(0, status);
+			++m_fw_volume_used_count;
+		}
+	}
+}
+
+void sim_fwu_dut::destroy_fw_volumes(void)
+{
+	for (unsigned int i = 0; i < m_fw_volume_used_count; i++)
+		block_volume_deinit(&m_fw_volume_pool[i]);
+
+	m_fw_volume_used_count = 0;
+}
+
+void sim_fwu_dut::construct_installers(
+	unsigned int num_locations,
+	bool allow_partial_updates)
+{
+	for (unsigned int location = 0; location < num_locations; location++) {
+
+		/* Provides a raw and optional copy installer per location. The raw_installer
+		 * is used for installing whole volume images using an externally streamed
+		 * image while the copy installer is used to copy the previously good whole
+		 * volume image to the update bank for cases where an incoming update
+		 * did not include images for all locations. Use of a copy_installer to
+		 * support this case is optional. By not registering a copy_installer for
+		 * a location, an update attempt will fail if and image for the location
+		 * was not included in an incoming update package.
+		 */
+		struct uuid_octets img_type_uuid;
+
+		whole_volume_image_type_uuid(location, &img_type_uuid);
+
+		struct raw_installer *raw_installer =
+			&m_raw_installer_pool[m_raw_installer_used_count];
+
+		raw_installer_init(raw_installer, &img_type_uuid, location);
+		installer_index_register(&raw_installer->base_installer);
+		++m_raw_installer_used_count;
+
+		if (allow_partial_updates) {
+
+			struct copy_installer *copy_installer =
+				&m_copy_installer_pool[m_copy_installer_used_count];
+
+			copy_installer_init(copy_installer, &img_type_uuid, location);
+			installer_index_register(&copy_installer->base_installer);
+			++m_copy_installer_used_count;
+		}
+	}
+}
+
+void sim_fwu_dut::destroy_installers(void)
+{
+	for (unsigned int i = 0; i < m_raw_installer_used_count; i++)
+		raw_installer_deinit(&m_raw_installer_pool[i]);
+
+	m_raw_installer_used_count = 0;
+
+	for (unsigned int i = 0; i < m_copy_installer_used_count; i++)
+		copy_installer_deinit(&m_copy_installer_pool[i]);
+
+	m_copy_installer_used_count = 0;
+}
+
+void sim_fwu_dut::install_factory_images(unsigned int num_locations)
+{
+	/* Install valid images into bank 0 to mimic the state of
+	 * a device with factory programmed flash.
+	 */
+	for (unsigned int location = 0; location < num_locations; location++) {
+
+		struct volume *volume = NULL;
+		size_t len_written = 0;
+
+		int status = volume_index_find(
+			banked_volume_id(location, banked_usage_id(FIRST_BOOT_BANK_INDEX)),
+			&volume);
+		LONGS_EQUAL(0, status);
+		CHECK_TRUE(volume);
+
+		std::vector<uint8_t> image_data;
+
+		generate_image_data(&image_data);
+
+		status = volume_open(volume);
+		LONGS_EQUAL(0, status);
+
+		status =  volume_write(volume,
+			(uintptr_t)image_data.data(), image_data.size(),
+			&len_written);
+		LONGS_EQUAL(0, status);
+		UNSIGNED_LONGS_EQUAL(image_data.size(), len_written);
+
+		status = volume_close(volume);
+		LONGS_EQUAL(0, status);
+	}
+}
+
+void sim_fwu_dut::verify_boot_images(unsigned int boot_index)
+{
+	for (unsigned int location = 0; location < m_num_locations; location++) {
+
+		struct volume *volume = NULL;
+
+		int status = volume_index_find(
+			banked_volume_id(location, banked_usage_id(boot_index)),
+			&volume);
+		LONGS_EQUAL(0, status);
+		CHECK_TRUE(volume);
+
+		status = volume_open(volume);
+		LONGS_EQUAL(0, status);
+
+		fwu_dut::verify_image(volume);
+
+		status = volume_close(volume);
+		LONGS_EQUAL(0, status);
+	}
+}
diff --git a/components/service/fwu/test/fwu_dut/sim/sim_fwu_dut.h b/components/service/fwu/test/fwu_dut/sim/sim_fwu_dut.h
new file mode 100644
index 0000000..5d94834
--- /dev/null
+++ b/components/service/fwu/test/fwu_dut/sim/sim_fwu_dut.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef SIM_FWU_DUT_H
+#define SIM_FWU_DUT_H
+
+#include <string>
+#include <cstddef>
+#include <common/uuid/uuid.h>
+#include <media/volume/block_volume/block_volume.h>
+#include <service/block_storage/block_store/device/ram/ram_block_store.h>
+#include <service/block_storage/block_store/partitioned/partitioned_block_store.h>
+#include <service/fwu/agent/update_agent.h>
+#include <service/fwu/agent/fw_directory.h>
+#include <service/fwu/fw_store/banked/banked_fw_store.h>
+#include <service/fwu/fw_store/banked/bank_scheme.h>
+#include <service/fwu/installer/raw/raw_installer.h>
+#include <service/fwu/installer/copy/copy_installer.h>
+#include <service/fwu/test/metadata_checker/metadata_checker.h>
+#include <service/fwu/test/fwu_client/fwu_client.h>
+#include <service/fwu/test/fwu_dut/fwu_dut.h>
+
+/*
+ * An sim_fwu_dut is an aggregate of components that simulates
+ * an updatable device that receives updates via the interface presented
+ * by an fwu_client. A certain amount of construction-time configuration is
+ * supported to allow for testing with alternative firmware store
+ * realizations. As much as possible, the set of components that forms
+ * the DUT is the same as what is used in real deployments.
+ */
+class sim_fwu_dut : public fwu_dut
+{
+public:
+
+	/**
+	 * \brief sim_fwu_dut constructor
+	 *
+	 * \param[in]  num_locations  The number of updatable fw locations
+	 * \param[in]  allow_partial_updates True if updating a subset of locations is permitted
+	 */
+	sim_fwu_dut(
+		unsigned int num_locations,
+		bool allow_partial_updates = false);
+
+	~sim_fwu_dut();
+
+	void boot(bool from_active_bank = true);
+	void shutdown(void);
+
+	struct boot_info get_boot_info(void) const;
+
+	metadata_checker *create_metadata_checker(bool is_primary = true) const;
+	fwu_client *create_fwu_client(void);
+
+private:
+
+	/* Maximum locations supported */
+	static const unsigned int MAX_LOCATIONS = 4;
+
+	/* Volumes needed for fwu metadata access */
+	static const unsigned int FWU_METADATA_VOLUMES = 2;
+
+	/* Boot index on first boot before valid FWU metadata exists */
+	static const unsigned int FIRST_BOOT_BANK_INDEX = 0;
+
+	/* Platform storage configuration */
+	static const size_t FLASH_BLOCK_SIZE = 512;
+	static const size_t FW_VOLUME_NUM_BLOCKS = 20;
+	static const size_t METADATA_VOLUME_NUM_BLOCKS = 4;
+
+	void fw_partition_guid(
+		unsigned int location_index,
+		unsigned int bank_index,
+		struct uuid_octets *uuid) const;
+
+	void fwu_metadata_partition_guid(
+		bool is_primary,
+		struct uuid_octets *uuid) const;
+
+	void disk_guid(
+		struct uuid_octets *uuid) const;
+
+	void construct_storage(unsigned int num_locations);
+	void destroy_storage(void);
+
+	void construct_fw_volumes(unsigned int num_locations);
+	void destroy_fw_volumes(void);
+
+	void construct_installers(unsigned int num_locations, bool allow_partial_updates);
+	void destroy_installers(void);
+
+	void install_factory_images(unsigned int num_locations);
+	void verify_boot_images(unsigned int boot_index);
+
+	bool m_is_booted;
+	bool m_is_first_boot;
+	struct boot_info m_boot_info;
+	metadata_checker *m_metadata_checker;
+	unsigned int m_num_locations;
+
+	/* Firmware storage */
+	struct ram_block_store m_fw_flash;
+	struct partitioned_block_store m_partitioned_block_store;
+	struct block_store *m_block_store;
+
+	/* Pools of volume objects */
+	size_t m_fw_volume_used_count;
+	struct block_volume m_fw_volume_pool[
+		MAX_LOCATIONS * BANK_SCHEME_NUM_BANKS + FWU_METADATA_VOLUMES];
+
+	/* Pools of different types of installer */
+	size_t m_raw_installer_used_count;
+	struct raw_installer m_raw_installer_pool[MAX_LOCATIONS];
+	size_t m_copy_installer_used_count;
+	struct copy_installer m_copy_installer_pool[MAX_LOCATIONS];
+
+	/* The core fwu service components */
+	struct update_agent m_update_agent;
+	struct fw_store m_fw_store;
+};
+
+#endif /* SIM_FWU_DUT_H */
diff --git a/components/service/fwu/test/fwu_dut_factory/fwu_dut_factory.h b/components/service/fwu/test/fwu_dut_factory/fwu_dut_factory.h
new file mode 100644
index 0000000..c0c99af
--- /dev/null
+++ b/components/service/fwu/test/fwu_dut_factory/fwu_dut_factory.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef FWU_DUT_FACTORY_H
+#define FWU_DUT_FACTORY_H
+
+#include <service/fwu/test/fwu_dut/fwu_dut.h>
+
+/*
+ * A factory for constructing fwu_dut objects. To allow for different test
+ * configurations, alternative implementations of the factory method are
+ * possible. The fwu_dut_factory provides a common interface to allow test
+ * cases that depend on a fwu_dut to be reused in different deployments.
+ */
+class fwu_dut_factory
+{
+public:
+
+	/**
+	 * \brief Factory method to construct concrete fwu_dut objects
+	 *
+	 * \param[in]  num_locations  The number of updatable fw locations
+	 * \param[in]  allow_partial_updates True if updating a subset of locations is permitted
+	 *
+	 * \return The constructed fwu_dut
+	 */
+	static fwu_dut *create(
+		unsigned int num_locations,
+		bool allow_partial_updates = false);
+};
+
+#endif /* FWU_DUT_FACTORY_H */
diff --git a/components/service/fwu/test/fwu_dut_factory/sim/component.cmake b/components/service/fwu/test/fwu_dut_factory/sim/component.cmake
new file mode 100644
index 0000000..3add66d
--- /dev/null
+++ b/components/service/fwu/test/fwu_dut_factory/sim/component.cmake
@@ -0,0 +1,13 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+if (NOT DEFINED TGT)
+	message(FATAL_ERROR "mandatory parameter TGT is not defined.")
+endif()
+
+target_sources(${TGT} PRIVATE
+	"${CMAKE_CURRENT_LIST_DIR}/fwu_dut_factory.cpp"
+	)
diff --git a/components/service/fwu/test/fwu_dut_factory/sim/fwu_dut_factory.cpp b/components/service/fwu/test/fwu_dut_factory/sim/fwu_dut_factory.cpp
new file mode 100644
index 0000000..be7dff8
--- /dev/null
+++ b/components/service/fwu/test/fwu_dut_factory/sim/fwu_dut_factory.cpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <service/fwu/test/fwu_dut/sim/sim_fwu_dut.h>
+#include <service/fwu/test/fwu_dut_factory/fwu_dut_factory.h>
+
+/*
+ * A factory for constructing sim_fwu_dut objects. A sim_fwu_dut is an
+ * aggregate of all storage and fwu related objects and is suitable for
+ * component level testing. The sim_fwu_dut simulates the role of the
+ * bootloader and device shutdown and boot-up.
+ */
+fwu_dut *fwu_dut_factory::create(
+	unsigned int num_locations,
+	bool allow_partial_updates)
+{
+	return new sim_fwu_dut(num_locations, allow_partial_updates);
+}
\ No newline at end of file
diff --git a/components/service/fwu/test/image_directory_checker/component.cmake b/components/service/fwu/test/image_directory_checker/component.cmake
new file mode 100644
index 0000000..81df3ea
--- /dev/null
+++ b/components/service/fwu/test/image_directory_checker/component.cmake
@@ -0,0 +1,13 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+if (NOT DEFINED TGT)
+	message(FATAL_ERROR "mandatory parameter TGT is not defined.")
+endif()
+
+target_sources(${TGT} PRIVATE
+	"${CMAKE_CURRENT_LIST_DIR}/image_directory_checker.cpp"
+	)
diff --git a/components/service/fwu/test/image_directory_checker/image_directory_checker.cpp b/components/service/fwu/test/image_directory_checker/image_directory_checker.cpp
new file mode 100644
index 0000000..6d4680d
--- /dev/null
+++ b/components/service/fwu/test/image_directory_checker/image_directory_checker.cpp
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <cassert>
+#include <cstdlib>
+#include <cstring>
+#include <common/uuid/uuid.h>
+#include "image_directory_checker.h"
+
+image_directory_checker::image_directory_checker() :
+	m_buf(NULL),
+	m_buf_size(0),
+	m_total_read_len(0)
+{
+	alloc_buffer();
+}
+
+image_directory_checker::~image_directory_checker()
+{
+	delete[] m_buf;
+}
+
+int image_directory_checker::fetch_image_directory(
+	fwu_client *fwu_client)
+{
+	int status = 0;
+	uint32_t stream_handle = 0;
+	size_t reported_total_len = 0;
+	struct uuid_octets uuid;
+
+	uuid_guid_octets_from_canonical(&uuid, FWU_DIRECTORY_CANONICAL_UUID);
+
+	status = fwu_client->open(&uuid, &stream_handle);
+	if (status)
+		return status;
+
+	/* Read stream until all data is read */
+	m_total_read_len = 0;
+
+	do {
+
+		size_t data_len_read = 0;
+		size_t requested_read_len = m_buf_size - m_total_read_len;
+
+		status = fwu_client->read_stream(
+			stream_handle,
+			&m_buf[m_total_read_len],
+			requested_read_len,
+			&data_len_read,
+			&reported_total_len);
+
+		m_total_read_len += data_len_read;
+
+		assert(m_total_read_len <= reported_total_len);
+
+		if (m_total_read_len == reported_total_len) {
+
+			/* Read all the data */
+			break;
+		}
+
+	} while (!status);
+
+	status = fwu_client->commit(stream_handle, false);
+
+	return status;
+}
+
+size_t image_directory_checker::num_images(void) const
+{
+	size_t num_images = 0;
+
+	if (m_total_read_len >= offsetof(struct ts_fwu_image_directory, img_info_entry)) {
+
+		const struct ts_fwu_image_directory *header =
+			(const struct ts_fwu_image_directory *)m_buf;
+
+		num_images = header->num_images;
+	}
+
+	return num_images;
+}
+
+bool image_directory_checker::is_contents_equal(
+	const image_directory_checker &rhs) const
+{
+	return
+		(this->m_total_read_len > 0) &&
+		(this->m_total_read_len == rhs.m_total_read_len) &&
+		(this->m_buf && rhs.m_buf) &&
+		(memcmp(this->m_buf, rhs.m_buf, this->m_total_read_len) == 0);
+}
+
+const struct ts_fwu_image_directory *image_directory_checker::get_header(void) const
+{
+	const struct ts_fwu_image_directory *header = NULL;
+
+	if (m_total_read_len >= offsetof(struct ts_fwu_image_directory, img_info_entry))
+		header = (const struct ts_fwu_image_directory *)m_buf;
+
+	return header;
+}
+
+const struct ts_fwu_image_info_entry *image_directory_checker::find_entry(
+	const struct uuid_octets *img_type_uuid) const
+{
+	const struct ts_fwu_image_info_entry *found_entry = NULL;
+
+	const struct ts_fwu_image_directory *header = get_header();
+
+	if (header) {
+
+		unsigned int index = 0;
+
+		while ((const uint8_t *)&header->img_info_entry[index + 1] <=
+			&m_buf[m_total_read_len]) {
+
+			if (uuid_is_equal(img_type_uuid->octets,
+					header->img_info_entry[index].img_type_uuid)) {
+
+				found_entry = &header->img_info_entry[index];
+				break;
+			}
+
+			++index;
+		}
+	}
+
+	return found_entry;
+}
+
+void image_directory_checker::alloc_buffer(void)
+{
+	m_buf_size =
+		offsetof(struct ts_fwu_image_directory, img_info_entry) +
+		MAX_IMAGES * sizeof(ts_fwu_image_info_entry);
+
+	m_buf = new uint8_t[m_buf_size];
+	assert(m_buf);
+}
diff --git a/components/service/fwu/test/image_directory_checker/image_directory_checker.h b/components/service/fwu/test/image_directory_checker/image_directory_checker.h
new file mode 100644
index 0000000..39dfd25
--- /dev/null
+++ b/components/service/fwu/test/image_directory_checker/image_directory_checker.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef IMAGE_DIRECTORY_CHECKER_H
+#define IMAGE_DIRECTORY_CHECKER_H
+
+#include <cstddef>
+#include <cstdint>
+#include <common/uuid/uuid.h>
+#include <service/fwu/test/fwu_client/fwu_client.h>
+#include <protocols/service/fwu/packed-c/fwu_proto.h>
+
+/*
+ * Provides check methods for checking the contents of the image
+ * directory fetched from the update agent.
+ */
+class image_directory_checker
+{
+public:
+
+	image_directory_checker();
+	~image_directory_checker();
+
+	int fetch_image_directory(fwu_client *fwu_client);
+
+	size_t num_images(void) const;
+
+	bool is_contents_equal(const image_directory_checker &rhs) const;
+
+	const struct ts_fwu_image_directory *get_header(void) const;
+	const struct ts_fwu_image_info_entry *find_entry(
+		const struct uuid_octets *img_type_uuid) const;
+
+private:
+
+	static const size_t MAX_IMAGES = 50;
+
+	void alloc_buffer(void);
+
+	uint8_t *m_buf;
+	size_t m_buf_size;
+	size_t m_total_read_len;
+};
+
+#endif /* IMAGE_DIRECTORY_CHECKER_H */
diff --git a/components/service/fwu/test/metadata_checker/component.cmake b/components/service/fwu/test/metadata_checker/component.cmake
new file mode 100644
index 0000000..033925f
--- /dev/null
+++ b/components/service/fwu/test/metadata_checker/component.cmake
@@ -0,0 +1,14 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+if (NOT DEFINED TGT)
+	message(FATAL_ERROR "mandatory parameter TGT is not defined.")
+endif()
+
+target_sources(${TGT} PRIVATE
+	"${CMAKE_CURRENT_LIST_DIR}/metadata_checker.cpp"
+	"${CMAKE_CURRENT_LIST_DIR}/metadata_checker_v1.cpp"
+	)
diff --git a/components/service/fwu/test/metadata_checker/metadata_checker.cpp b/components/service/fwu/test/metadata_checker/metadata_checker.cpp
new file mode 100644
index 0000000..618f97e
--- /dev/null
+++ b/components/service/fwu/test/metadata_checker/metadata_checker.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <CppUTest/TestHarness.h>
+#include "metadata_checker.h"
+
+metadata_checker::metadata_checker(
+	size_t max_metadata_size,
+	metadata_fetcher *metadata_fetcher) :
+	m_metadata_fetcher(metadata_fetcher),
+	m_meta_buf(NULL),
+	m_meta_buf_size(max_metadata_size)
+{
+	m_metadata_fetcher->open();
+
+	m_meta_buf = new uint8_t[m_meta_buf_size];
+	CHECK_TRUE(m_meta_buf);
+}
+
+metadata_checker::~metadata_checker()
+{
+	m_metadata_fetcher->close();
+
+	delete [] m_meta_buf;
+	m_meta_buf = NULL;
+
+	delete m_metadata_fetcher;
+	m_metadata_fetcher = NULL;
+}
+
+void metadata_checker::load_metadata(void)
+{
+	m_metadata_fetcher->fetch(m_meta_buf, m_meta_buf_size);
+}
\ No newline at end of file
diff --git a/components/service/fwu/test/metadata_checker/metadata_checker.h b/components/service/fwu/test/metadata_checker/metadata_checker.h
new file mode 100644
index 0000000..765897a
--- /dev/null
+++ b/components/service/fwu/test/metadata_checker/metadata_checker.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef METADATA_CHECKER_H
+#define METADATA_CHECKER_H
+
+#include <cstdint>
+#include <service/fwu/test/metadata_fetcher/metadata_fetcher.h>
+
+/*
+ * Provides check methods for verifying that the state of fwu metadata is
+ * as expected. To allow different metadata versions to be checked, some
+ * methods are virtual and will be provided by a version specific concrete
+ * metadata_checker.
+ */
+class metadata_checker
+{
+public:
+
+	metadata_checker(
+		size_t max_metadata_size,
+		metadata_fetcher *metadata_fetcher);
+
+	virtual ~metadata_checker();
+
+	virtual void get_active_indices(
+		uint32_t *active_index,
+		uint32_t *previous_active_index) = 0;
+
+	virtual void check_regular(unsigned int boot_index) = 0;
+	virtual void check_ready_for_staging(unsigned int boot_index) = 0;
+	virtual void check_ready_to_activate(unsigned int boot_index) = 0;
+	virtual void check_trial(unsigned int boot_index) = 0;
+	virtual void check_fallback_to_previous(unsigned int boot_index) = 0;
+
+protected:
+
+	void load_metadata(void);
+
+	metadata_fetcher *m_metadata_fetcher;
+	uint8_t *m_meta_buf;
+	size_t m_meta_buf_size;
+};
+
+#endif /* METADATA_CHECKER_H */
diff --git a/components/service/fwu/test/metadata_checker/metadata_checker_v1.cpp b/components/service/fwu/test/metadata_checker/metadata_checker_v1.cpp
new file mode 100644
index 0000000..57a545e
--- /dev/null
+++ b/components/service/fwu/test/metadata_checker/metadata_checker_v1.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2022-2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <protocols/service/fwu/packed-c/metadata_v1.h>
+#include <CppUTest/TestHarness.h>
+#include "metadata_checker_v1.h"
+
+metadata_checker_v1::metadata_checker_v1(
+	metadata_fetcher *metadata_fetcher,
+	unsigned int num_images) :
+	metadata_checker(MAX_FWU_METADATA_SIZE, metadata_fetcher),
+	m_num_images(num_images)
+{
+
+}
+
+metadata_checker_v1::~metadata_checker_v1()
+{
+
+}
+
+void metadata_checker_v1::get_active_indices(
+	uint32_t *active_index,
+	uint32_t *previous_active_index)
+{
+	load_metadata();
+
+	struct fwu_metadata *metadata = reinterpret_cast<struct fwu_metadata *>(m_meta_buf);
+
+	*active_index = metadata->active_index;
+	*previous_active_index = metadata->previous_active_index;
+}
+
+void metadata_checker_v1::check_regular(unsigned int boot_index)
+{
+	load_metadata();
+
+	struct fwu_metadata *metadata = reinterpret_cast<struct fwu_metadata *>(m_meta_buf);
+
+	UNSIGNED_LONGS_EQUAL(boot_index, metadata->active_index);
+	CHECK_TRUE(is_all_accepted(boot_index));
+}
+
+void metadata_checker_v1::check_ready_for_staging(unsigned int boot_index)
+{
+	load_metadata();
+
+	struct fwu_metadata *metadata = reinterpret_cast<struct fwu_metadata *>(m_meta_buf);
+
+	UNSIGNED_LONGS_EQUAL(boot_index, metadata->active_index);
+	UNSIGNED_LONGS_EQUAL(metadata->active_index, metadata->previous_active_index);
+}
+
+void metadata_checker_v1::check_ready_to_activate(unsigned int boot_index)
+{
+	load_metadata();
+
+	struct fwu_metadata *metadata = reinterpret_cast<struct fwu_metadata *>(m_meta_buf);
+
+	UNSIGNED_LONGS_EQUAL(boot_index, metadata->previous_active_index);
+	CHECK_TRUE(metadata->active_index != boot_index);
+}
+
+void metadata_checker_v1::check_trial(unsigned int boot_index)
+{
+	load_metadata();
+
+	struct fwu_metadata *metadata = reinterpret_cast<struct fwu_metadata *>(m_meta_buf);
+
+	UNSIGNED_LONGS_EQUAL(boot_index, metadata->active_index);
+	CHECK_TRUE(metadata->previous_active_index != boot_index);
+	CHECK_FALSE(is_all_accepted(boot_index));
+}
+
+void metadata_checker_v1::check_fallback_to_previous(unsigned int boot_index)
+{
+	load_metadata();
+
+	struct fwu_metadata *metadata = reinterpret_cast<struct fwu_metadata *>(m_meta_buf);
+
+	UNSIGNED_LONGS_EQUAL(boot_index, metadata->previous_active_index);
+	CHECK_TRUE(metadata->active_index != boot_index);
+}
+
+bool metadata_checker_v1::is_all_accepted(unsigned int boot_index) const
+{
+	bool result = true;
+	struct fwu_metadata *metadata = reinterpret_cast<struct fwu_metadata *>(m_meta_buf);
+
+	for (unsigned int i = 0; i < m_num_images; i++) {
+
+		if (!metadata->img_entry[i].img_props[boot_index].accepted) {
+
+			result = false;
+			break;
+		}
+	}
+
+	return result;
+}
diff --git a/components/service/fwu/test/metadata_checker/metadata_checker_v1.h b/components/service/fwu/test/metadata_checker/metadata_checker_v1.h
new file mode 100644
index 0000000..f705987
--- /dev/null
+++ b/components/service/fwu/test/metadata_checker/metadata_checker_v1.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2022-2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef METADATA_CHECKER_V1_H
+#define METADATA_CHECKER_V1_H
+
+#include <service/fwu/agent/fw_directory.h>
+#include <protocols/service/fwu/packed-c/metadata_v1.h>
+#include "metadata_checker.h"
+
+/*
+ * A metadata_checker for FWU-A V1 metadata
+ */
+class metadata_checker_v1 : public metadata_checker
+{
+public:
+
+	metadata_checker_v1(
+		metadata_fetcher *metadata_fetcher,
+		unsigned int num_images);
+
+	virtual ~metadata_checker_v1();
+
+	void get_active_indices(
+		uint32_t *active_index,
+		uint32_t *previous_active_index);
+
+	void check_regular(unsigned int boot_index);
+	void check_ready_for_staging(unsigned int boot_index);
+	void check_ready_to_activate(unsigned int boot_index);
+	void check_trial(unsigned int boot_index);
+	void check_fallback_to_previous(unsigned int boot_index);
+
+private:
+
+	static const size_t MAX_FWU_METADATA_SIZE =
+		offsetof(struct fwu_metadata, img_entry) +
+		FWU_MAX_FW_DIRECTORY_ENTRIES * sizeof(struct fwu_image_entry);
+
+	bool is_all_accepted(unsigned int boot_index) const;
+
+	unsigned int m_num_images;
+};
+
+#endif /* METADATA_CHECKER_V1_H */
diff --git a/components/service/fwu/test/metadata_fetcher/metadata_fetcher.h b/components/service/fwu/test/metadata_fetcher/metadata_fetcher.h
new file mode 100644
index 0000000..bbdc47c
--- /dev/null
+++ b/components/service/fwu/test/metadata_fetcher/metadata_fetcher.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef METADATA_FETCHER_H
+#define METADATA_FETCHER_H
+
+#include <cstddef>
+#include <cstdint>
+
+/*
+ * The metadata_fetcher provides an interface for fetching the current
+ * fwu metadata and writing it to a presented buffer. Different concrete
+ * fetching methods can be used for alternative test configurations.
+ */
+class metadata_fetcher
+{
+public:
+
+	metadata_fetcher()
+	{
+
+	}
+
+	virtual ~metadata_fetcher()
+	{
+
+	}
+
+	virtual void open(void) = 0;
+	virtual void close(void) = 0;
+	virtual void fetch(uint8_t *buf, size_t buf_size) = 0;
+};
+
+#endif /* METADATA_FETCHER_H */
diff --git a/components/service/fwu/test/metadata_fetcher/volume/component.cmake b/components/service/fwu/test/metadata_fetcher/volume/component.cmake
new file mode 100644
index 0000000..89fe63d
--- /dev/null
+++ b/components/service/fwu/test/metadata_fetcher/volume/component.cmake
@@ -0,0 +1,13 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+if (NOT DEFINED TGT)
+	message(FATAL_ERROR "mandatory parameter TGT is not defined.")
+endif()
+
+target_sources(${TGT} PRIVATE
+	"${CMAKE_CURRENT_LIST_DIR}/volume_metadata_fetcher.cpp"
+	)
diff --git a/components/service/fwu/test/metadata_fetcher/volume/volume_metadata_fetcher.cpp b/components/service/fwu/test/metadata_fetcher/volume/volume_metadata_fetcher.cpp
new file mode 100644
index 0000000..4acd766
--- /dev/null
+++ b/components/service/fwu/test/metadata_fetcher/volume/volume_metadata_fetcher.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <cstring>
+#include <media/volume/volume.h>
+#include <CppUTest/TestHarness.h>
+#include "volume_metadata_fetcher.h"
+
+volume_metadata_fetcher::volume_metadata_fetcher(
+	const struct uuid_octets *partition_guid,
+	struct block_store *block_store) :
+	metadata_fetcher(),
+	m_meta_block_volume(),
+	m_meta_volume(NULL)
+{
+	int status = block_volume_init(&m_meta_block_volume,
+		block_store, partition_guid, &m_meta_volume);
+	LONGS_EQUAL(0, status);
+	CHECK_TRUE(m_meta_volume);
+}
+
+volume_metadata_fetcher::~volume_metadata_fetcher()
+{
+	block_volume_deinit(&m_meta_block_volume);
+}
+
+void volume_metadata_fetcher::open(void)
+{
+	int status = volume_open(m_meta_volume);
+	LONGS_EQUAL(0, status);
+}
+
+void volume_metadata_fetcher::close(void)
+{
+	volume_close(m_meta_volume);
+}
+
+void volume_metadata_fetcher::fetch(uint8_t *buf, size_t buf_size)
+{
+	/* Trash the old data */
+	memset(buf, 0xff, buf_size);
+
+	int status = volume_seek(m_meta_volume, IO_SEEK_SET, 0);
+	LONGS_EQUAL(0, status);
+
+	size_t length_read = 0;
+
+	status =  volume_read(m_meta_volume,
+		(uintptr_t)buf, buf_size,
+		 &length_read);
+
+	LONGS_EQUAL(0, status);
+	UNSIGNED_LONGS_EQUAL(buf_size, length_read);
+}
diff --git a/components/service/fwu/test/metadata_fetcher/volume/volume_metadata_fetcher.h b/components/service/fwu/test/metadata_fetcher/volume/volume_metadata_fetcher.h
new file mode 100644
index 0000000..6656fb7
--- /dev/null
+++ b/components/service/fwu/test/metadata_fetcher/volume/volume_metadata_fetcher.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef VOLUME_METADATA_FETCHER_H
+#define VOLUME_METADATA_FETCHER_H
+
+#include <service/fwu/test/metadata_fetcher/metadata_fetcher.h>
+#include <media/volume/block_volume/block_volume.h>
+
+/*
+ * A metadata_fetcher that fetches fwu metadata from a storage volume
+ * that provides access to the disk partition used for fwu metadata.
+ */
+class volume_metadata_fetcher : public metadata_fetcher
+{
+public:
+
+	volume_metadata_fetcher(
+		const struct uuid_octets *partition_guid,
+		struct block_store *block_store);
+
+	~volume_metadata_fetcher();
+
+	void open(void);
+	void close(void);
+	void fetch(uint8_t *buf, size_t buf_size);
+
+private:
+	struct block_volume m_meta_block_volume;
+	struct volume *m_meta_volume;
+};
+
+#endif /* VOLUME_METADATA_FETCHER_H */
diff --git a/deployments/component-test/component-test.cmake b/deployments/component-test/component-test.cmake
index d9301e5..e91db89 100644
--- a/deployments/component-test/component-test.cmake
+++ b/deployments/component-test/component-test.cmake
@@ -121,6 +121,13 @@
 		"components/service/fwu/installer/factory/default/test"
 		"components/service/fwu/inspector/mock"
 		"components/service/fwu/inspector/direct"
+		"components/service/fwu/test/fwu_client/direct"
+		"components/service/fwu/test/fwu_dut"
+		"components/service/fwu/test/fwu_dut/sim"
+		"components/service/fwu/test/fwu_dut_factory/sim"
+		"components/service/fwu/test/metadata_checker"
+		"components/service/fwu/test/metadata_fetcher/volume"
+		"components/service/fwu/test/image_directory_checker"
 		"components/service/crypto/client/cpp"
 		"components/service/crypto/client/cpp/protocol/protobuf"
 		"components/service/crypto/client/cpp/protocol/packed-c"