Add fwu_app for test and demonstration

Adds the fwu_app class to facilitate deployment of the update agent
as part of an application e.g. a user-space or UEFI app. It uses
the standard FWU service components but operates on a disk image
file rather than on a flash device. The disk image file needs to
be a UEFI formated image with MBR+GPT with partitions for metadata
and firmware banks. Will be initially used to test operation with
boot images generated from build tools.

Signed-off-by: Julian Hall <julian.hall@arm.com>
Change-Id: Ia4151841115010333a922c217352614062f8f86a
diff --git a/components/service/fwu/app/component.cmake b/components/service/fwu/app/component.cmake
new file mode 100644
index 0000000..7b9e5ab
--- /dev/null
+++ b/components/service/fwu/app/component.cmake
@@ -0,0 +1,16 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2023, 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_app.cpp"
+	"${CMAKE_CURRENT_LIST_DIR}/metadata_reader.cpp"
+	"${CMAKE_CURRENT_LIST_DIR}/metadata_v1_reader.cpp"
+	"${CMAKE_CURRENT_LIST_DIR}/metadata_v2_reader.cpp"
+	)
diff --git a/components/service/fwu/app/fwu_app.cpp b/components/service/fwu/app/fwu_app.cpp
new file mode 100644
index 0000000..70059d0
--- /dev/null
+++ b/components/service/fwu/app/fwu_app.cpp
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <cassert>
+#include <cstddef>
+#include <cstring>
+#include <errno.h>
+#include <media/volume/factory/volume_factory.h>
+#include <service/block_storage/factory/file/block_store_factory.h>
+#include <service/fwu/installer/factory/installer_factory.h>
+#include <service/fwu/fw_store/banked/bank_scheme.h>
+#include <service/fwu/fw_store/banked/metadata_serializer/v1/metadata_serializer_v1.h>
+#include <service/fwu/fw_store/banked/metadata_serializer/v2/metadata_serializer_v2.h>
+#include <service/fwu/inspector/direct/direct_fw_inspector.h>
+#include <service/fwu/agent/update_agent.h>
+#include <service/fwu/fw_store/banked/banked_fw_store.h>
+#include <service/fwu/config/fwu_configure.h>
+#include "metadata_reader.h"
+#include "fwu_app.h"
+
+extern "C" {
+#include <trace.h>
+}
+
+fwu_app::fwu_app() :
+	m_update_agent(),
+	m_fw_store()
+{
+	memset(&m_update_agent, 0, sizeof(m_update_agent));
+	memset(&m_fw_store, 0, sizeof(m_fw_store));
+}
+
+fwu_app::~fwu_app()
+{
+	update_agent_deinit(&m_update_agent);
+	banked_fw_store_deinit(&m_fw_store);
+
+	fwu_deconfigure();
+	volume_factory_deinit();
+}
+
+int fwu_app::configure(
+	const char *disk_img_filename)
+{
+	if (disk_img_filename)
+		file_block_store_factory_set_filename(disk_img_filename);
+
+	struct uuid_octets device_uuids[MAX_STORAGE_DEVICES];
+	size_t num_storage_devices = 0;
+
+	int status = volume_factory_init(device_uuids,
+		MAX_STORAGE_DEVICES, &num_storage_devices);
+
+	if (status) {
+
+		EMSG("Failed to init volume factory: %d", status);
+		return -EIO;
+	}
+
+	status = fwu_configure(device_uuids, num_storage_devices);
+
+	if (status) {
+
+		EMSG("Failed to setup FWU configuration: %d", status);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+int fwu_app::get_boot_info(
+	unsigned int &active_index,
+	unsigned int &metadata_version)
+{
+	return metadata_reader::instance()->get_boot_info(active_index, metadata_version);
+}
+
+int fwu_app::init_update_agent(
+	unsigned int boot_index,
+	unsigned int metadata_version)
+{
+	if (boot_index >= BANK_SCHEME_NUM_BANKS) {
+
+		IMSG("Invalid boot index");
+		return -1;
+	}
+
+	const struct metadata_serializer *serializer =
+		select_metadata_serializer(metadata_version);
+
+	if (!serializer) {
+
+		IMSG("Unsupported FWU metadata version");
+		return -1;
+	}
+
+	/* Initialise update_agent */
+	int status = banked_fw_store_init(&m_fw_store, serializer);
+
+	if (status) {
+
+		IMSG("fw store initialisation error %d", status);
+		return -1;
+	}
+
+	status = update_agent_init(&m_update_agent, boot_index,
+		direct_fw_inspector_inspect, &m_fw_store);
+
+	if (status) {
+
+		IMSG("update agent initialisation error %d", status);
+		return -1;
+	}
+
+	/* Success */
+	return 0;
+}
+
+int fwu_app::update_image(
+	const struct uuid_octets &img_type_uuid,
+	const uint8_t *img_data,
+	size_t img_size)
+{
+	int status = update_agent_begin_staging(&m_update_agent);
+
+	if (status)
+		return status;
+
+	uint32_t stream_handle = 0;
+
+	status = update_agent_open(&m_update_agent,
+		&img_type_uuid, &stream_handle);
+
+	if (!status) {
+
+		status = update_agent_write_stream(&m_update_agent,
+			stream_handle, img_data, img_size);
+
+		if (!status)
+			status = update_agent_commit(&m_update_agent,
+				stream_handle, false);
+	}
+
+	if (!status)
+		status = update_agent_end_staging(&m_update_agent);
+	else
+		update_agent_cancel_staging(&m_update_agent);
+
+	return status;
+}
+
+int fwu_app::read_object(
+	const struct uuid_octets &object_uuid,
+	std::vector<uint8_t> &data)
+{
+	uint32_t stream_handle = 0;
+	int status = update_agent_open(&m_update_agent, &object_uuid, &stream_handle);
+
+	if (status)
+		return status;
+
+	/* Don't yet know how big the object will be so allocate some space to get
+	 * started with the initial read.
+	 */
+	size_t reported_total_len = 0;
+	size_t read_so_far = 0;
+	size_t vector_capacity = 512;
+
+	data.resize(vector_capacity);
+
+	do {
+
+		size_t data_len_read = 0;
+		size_t requested_read_len = vector_capacity - read_so_far;
+
+		status = update_agent_read_stream(
+			&m_update_agent,
+			stream_handle,
+			&data[read_so_far],
+			requested_read_len,
+			&data_len_read,
+			&reported_total_len);
+
+		read_so_far += data_len_read;
+		data.resize(read_so_far);
+
+		if (reported_total_len > vector_capacity) {
+
+			vector_capacity = reported_total_len;
+			data.resize(vector_capacity);
+		}
+
+		assert(read_so_far <= reported_total_len);
+
+		if (read_so_far == reported_total_len) {
+
+			/* Read all the data */
+			if (vector_capacity > reported_total_len)
+				data.resize(reported_total_len);
+
+			break;
+		}
+
+	} while (!status);
+
+	status = update_agent_commit(&m_update_agent, stream_handle, false);
+
+	return status;
+}
+
+struct update_agent *fwu_app::update_agent()
+{
+	return &m_update_agent;
+}
+
+const struct metadata_serializer *fwu_app::select_metadata_serializer(
+	unsigned int version)
+{
+	if (version == 1)
+		return metadata_serializer_v1();
+
+	if (version == 2)
+		return metadata_serializer_v2();
+
+	return NULL;
+}
diff --git a/components/service/fwu/app/fwu_app.h b/components/service/fwu/app/fwu_app.h
new file mode 100644
index 0000000..8e7f74c
--- /dev/null
+++ b/components/service/fwu/app/fwu_app.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef FWU_APP_H
+#define FWU_APP_H
+
+#include <stdint.h>
+#include <vector>
+#include <common/uuid/uuid.h>
+#include <service/fwu/agent/update_agent.h>
+#include <service/fwu/fw_store/banked/banked_fw_store.h>
+
+/*
+ * The fwu_app class is intended to provide the core for an application
+ * that uses the update_agent for updating the contents of a disk image
+ * file. The app uses standard fwu components, such as the update_agent,
+ * to manage updates. To support additional update methods (e.g.
+ * update_capsule), inherit from this class and add additional methods
+ * to the derived class. This allows the core app to be reused for
+ * different application areas without overloading the base app.
+ */
+class fwu_app {
+
+public:
+	fwu_app();
+	virtual ~fwu_app();
+
+	/**
+	 * \brief Configure storage and installers
+	 *
+	 * Configures a set of storage volumes and installers to manage the
+	 * contents of the specified disk image file.
+	 *
+	 * \param[in]  disk_img_filename   UEFI formatted disk image
+	 *
+	 * \return Status (0 on success)
+	 */
+	int configure(
+		const char *disk_img_filename);
+
+	/**
+	 * \brief Get boot info from the FWU metadata
+	 *
+	 * \param[out]  active_index   The active index seen by the bootloader
+	 * \param[out]  metadata_version Current metadata version
+	 *
+	 * \return Status (0 on success)
+	 */
+	int get_boot_info(
+		unsigned int &active_index,
+		unsigned int &metadata_version);
+
+	/**
+	 * \brief Initialise the update agent
+	 *
+	 * \param[in]  boot_index   The boot_index chosen by the bootloader
+	 * \param[in]  metadata_version Current metadata version
+	 *
+	 * \return Status (0 on success)
+	 */
+	int init_update_agent(
+		unsigned int boot_index,
+		unsigned int metadata_version);
+
+	/**
+	 * \brief Update a single image
+	 *
+	 * Begins staging, writes the raw contents of the image file and ends
+	 * staging.
+	 *
+	 * \param[in]  img_type_uuid   UUID of image to update
+	 * \param[in]  img_data        Buffer containing image data
+	 * \param[in]  img_size        Size in bytes of image
+	 *
+	 * \return Status (0 on success)
+	 */
+	int update_image(
+		const struct uuid_octets &img_type_uuid,
+		const uint8_t *img_data,
+		size_t img_size);
+
+	/**
+	 * \brief Read an object from the update agent
+	 *
+	 * \param[in]  object_uuid    UUID of object
+	 * \param[out] data           Read object data
+	 *
+	 * \return Status (0 on success)
+	 */
+	int read_object(
+		const struct uuid_octets &object_uuid,
+		std::vector<uint8_t> &data);
+
+protected:
+
+	/**
+	 * \brief Return pointer to update_agent struct
+	 *
+	 * \return Pointer to the core update_agent.
+	 */
+	struct update_agent *update_agent();
+
+private:
+
+	static const size_t MAX_STORAGE_DEVICES = 4;
+
+	static const struct metadata_serializer *select_metadata_serializer(
+		unsigned int version);
+
+	struct update_agent m_update_agent;
+	struct fw_store m_fw_store;
+};
+
+#endif /* FWU_APP_H */
diff --git a/components/service/fwu/app/metadata_reader.cpp b/components/service/fwu/app/metadata_reader.cpp
new file mode 100644
index 0000000..6423aa9
--- /dev/null
+++ b/components/service/fwu/app/metadata_reader.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <media/volume/volume.h>
+#include <media/volume/index/volume_index.h>
+#include <service/fwu/fw_store/banked/volume_id.h>
+#include "metadata_reader.h"
+
+extern "C" {
+#include <trace.h>
+}
+
+metadata_reader::metadata_reader() :
+	registered_readers()
+{
+
+}
+
+metadata_reader::~metadata_reader()
+{
+
+}
+
+metadata_reader *metadata_reader::instance()
+{
+	static metadata_reader the_instance;
+	return &the_instance;
+}
+
+void metadata_reader::register_reader(metadata_version_specific_reader *reader)
+{
+	registered_readers.push_back(reader);
+}
+
+int metadata_reader::get_boot_info(
+	unsigned int &active_index,
+	unsigned int &metadata_version) const
+{
+	struct volume *volume;
+
+	int status = volume_index_find(BANKED_VOLUME_ID_PRIMARY_METADATA, &volume);
+
+	if (status) {
+
+		IMSG("Failed to find metadata volume");
+		return status;
+	}
+
+	status = volume_open(volume);
+
+	if (!status) {
+
+		/* Assume whatever metadata version is in-use, it will fit in the buffer */
+		size_t len_read = 0;
+		uint8_t buf[1000];
+
+		status = volume_read(volume, (uintptr_t)buf, sizeof(buf), &len_read);
+
+		if (!status) {
+
+			bool is_handled = false;
+
+			for (unsigned int i = 0; i < registered_readers.size(); i++) {
+
+				metadata_version_specific_reader *reader = registered_readers[i];
+
+				if (reader->is_supported(buf, len_read)) {
+
+					reader->get_version(buf, len_read, metadata_version);
+					reader->get_active_index(buf, len_read, active_index);
+
+					is_handled = true;
+					break;
+				}
+			}
+
+			if (!is_handled)
+				IMSG("No metadata recognised");
+
+		} else
+			IMSG("Failed to read metadata volume");
+
+		volume_close(volume);
+
+	} else
+		IMSG("Failed to open metadata volume");
+
+	return status;
+}
+
diff --git a/components/service/fwu/app/metadata_reader.h b/components/service/fwu/app/metadata_reader.h
new file mode 100644
index 0000000..d84017c
--- /dev/null
+++ b/components/service/fwu/app/metadata_reader.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef FWU_METADATA_READER_H
+#define FWU_METADATA_READER_H
+
+#include <cstdint>
+#include <vector>
+
+/*
+ * A version specific metadata reader. Before using get methods to extract
+ * metadata attributes, is_supported() should be called to verify that the
+ * input metadata version is supported.
+ */
+class metadata_version_specific_reader {
+
+public:
+
+	virtual ~metadata_version_specific_reader() {}
+
+	virtual bool is_supported(
+		const uint8_t *buf,
+		size_t data_len) const = 0;
+
+	virtual void get_version(
+		const uint8_t *buf,
+		size_t data_len,
+		unsigned int &version) const = 0;
+
+	virtual void get_active_index(
+		const uint8_t *buf,
+		size_t data_len,
+		unsigned int &active_index) const = 0;
+};
+
+/*
+ * A singleton that provides a common interface for reading fwu metadata.
+ * The caller doesn't need to worry about the version of metadata being used.
+ */
+class metadata_reader {
+
+public:
+
+	static metadata_reader *instance();
+	~metadata_reader();
+
+	void register_reader(metadata_version_specific_reader *reader);
+
+	int get_boot_info(
+		unsigned int &active_index,
+		unsigned int &metadata_version) const;
+
+private:
+	metadata_reader();
+
+	std::vector<metadata_version_specific_reader *> registered_readers;
+};
+
+#endif /* FWU_METADATA_READER_H */
diff --git a/components/service/fwu/app/metadata_v1_reader.cpp b/components/service/fwu/app/metadata_v1_reader.cpp
new file mode 100644
index 0000000..58faa23
--- /dev/null
+++ b/components/service/fwu/app/metadata_v1_reader.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <assert.h>
+#include <protocols/service/fwu/packed-c/metadata_v1.h>
+#include "metadata_reader.h"
+
+class metadata_v1_reader : public metadata_version_specific_reader {
+
+public:
+
+	metadata_v1_reader();
+	~metadata_v1_reader();
+
+	bool is_supported(
+		const uint8_t *buf,
+		size_t data_len) const;
+
+	void get_version(
+		const uint8_t *buf,
+		size_t data_len,
+		unsigned int &version) const;
+
+	void get_active_index(
+		const uint8_t *buf,
+		size_t data_len,
+		unsigned int &active_index) const;
+};
+
+/* Registers on static construction */
+static metadata_v1_reader the_v1_reader;
+
+
+metadata_v1_reader::metadata_v1_reader() :
+	metadata_version_specific_reader()
+{
+	metadata_reader::instance()->register_reader(this);
+}
+
+metadata_v1_reader::~metadata_v1_reader()
+{
+
+}
+
+bool metadata_v1_reader::is_supported(
+	const uint8_t *buf,
+	size_t data_len) const
+{
+	assert(buf);
+
+	const struct fwu_metadata *metadata = (const struct fwu_metadata *)buf;
+
+	return
+		(data_len >= sizeof(struct fwu_metadata)) &&
+		(metadata->version == 1);
+}
+
+void metadata_v1_reader::get_version(
+	const uint8_t *buf,
+	size_t data_len,
+	unsigned int &version) const
+{
+	assert(buf);
+	assert(data_len >= sizeof(struct fwu_metadata));
+
+	const struct fwu_metadata *metadata = (const struct fwu_metadata *)buf;
+
+	version = metadata->version;
+}
+
+void metadata_v1_reader::get_active_index(
+	const uint8_t *buf,
+	size_t data_len,
+	unsigned int &active_index) const
+{
+	assert(buf);
+	assert(data_len >= sizeof(struct fwu_metadata));
+
+	const struct fwu_metadata *metadata = (const struct fwu_metadata *)buf;
+
+	active_index = metadata->active_index;
+}
\ No newline at end of file
diff --git a/components/service/fwu/app/metadata_v2_reader.cpp b/components/service/fwu/app/metadata_v2_reader.cpp
new file mode 100644
index 0000000..0bf7a6b
--- /dev/null
+++ b/components/service/fwu/app/metadata_v2_reader.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <assert.h>
+#include <protocols/service/fwu/packed-c/metadata_v2.h>
+#include "metadata_reader.h"
+
+class metadata_v2_reader : public metadata_version_specific_reader {
+
+public:
+
+	metadata_v2_reader();
+	~metadata_v2_reader();
+
+	bool is_supported(
+		const uint8_t *buf,
+		size_t data_len) const;
+
+	void get_version(
+		const uint8_t *buf,
+		size_t data_len,
+		unsigned int &version) const;
+
+	void get_active_index(
+		const uint8_t *buf,
+		size_t data_len,
+		unsigned int &active_index) const;
+};
+
+/* Registers on static construction */
+static metadata_v2_reader the_v2_reader;
+
+
+metadata_v2_reader::metadata_v2_reader() :
+	metadata_version_specific_reader()
+{
+	metadata_reader::instance()->register_reader(this);
+}
+
+metadata_v2_reader::~metadata_v2_reader()
+{
+
+}
+
+bool metadata_v2_reader::is_supported(
+	const uint8_t *buf,
+	size_t data_len) const
+{
+	assert(buf);
+
+	const struct fwu_metadata *metadata = (const struct fwu_metadata *)buf;
+
+	return
+		(data_len >= sizeof(struct fwu_metadata)) &&
+		(metadata->version == 2);
+}
+
+void metadata_v2_reader::get_version(
+	const uint8_t *buf,
+	size_t data_len,
+	unsigned int &version) const
+{
+	assert(buf);
+	assert(data_len >= sizeof(struct fwu_metadata));
+
+	const struct fwu_metadata *metadata = (const struct fwu_metadata *)buf;
+
+	version = metadata->version;
+}
+
+void metadata_v2_reader::get_active_index(
+	const uint8_t *buf,
+	size_t data_len,
+	unsigned int &active_index) const
+{
+	assert(buf);
+	assert(data_len >= sizeof(struct fwu_metadata));
+
+	const struct fwu_metadata *metadata = (const struct fwu_metadata *)buf;
+
+	active_index = metadata->active_index;
+}
\ No newline at end of file