Add update agent implementation

Adds an implementation of the generic update agent component. This
implements the FWU state machine that ensures that a valid update
sequence is followed.

Signed-off-by: Julian Hall <julian.hall@arm.com>
Change-Id: Icf51b6527f1f84eb0f693e1629f4d2b3b075d43a
diff --git a/components/service/fwu/agent/component.cmake b/components/service/fwu/agent/component.cmake
index a0d89ef..f5dddc6 100644
--- a/components/service/fwu/agent/component.cmake
+++ b/components/service/fwu/agent/component.cmake
@@ -10,4 +10,7 @@
 
 target_sources(${TGT} PRIVATE
 	"${CMAKE_CURRENT_LIST_DIR}/fw_directory.c"
+	"${CMAKE_CURRENT_LIST_DIR}/update_agent.c"
+	"${CMAKE_CURRENT_LIST_DIR}/stream_manager.c"
+	"${CMAKE_CURRENT_LIST_DIR}/img_dir_serializer.c"
 	)
diff --git a/components/service/fwu/agent/img_dir_serializer.c b/components/service/fwu/agent/img_dir_serializer.c
new file mode 100644
index 0000000..db210bc
--- /dev/null
+++ b/components/service/fwu/agent/img_dir_serializer.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2022-2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#include <assert.h>
+#include <string.h>
+#include <protocols/service/fwu/packed-c/fwu_proto.h>
+#include <service/fwu/fw_store/fw_store.h>
+#include "fw_directory.h"
+#include "img_dir_serializer.h"
+
+int img_dir_serializer_serialize(
+	const struct fw_directory *fw_dir,
+	const struct fw_store *fw_store,
+	uint8_t *buf,
+	size_t buf_size,
+	size_t *data_len)
+{
+	size_t serialized_len = img_dir_serializer_get_len(fw_dir);
+
+	*data_len = 0;
+
+	if (buf_size < serialized_len)
+		return FWU_STATUS_OUT_OF_BOUNDS;
+
+	struct ts_fwu_image_directory *output = (struct ts_fwu_image_directory *)buf;
+
+	/* Clear the output buffer */
+	memset(buf, 0, serialized_len);
+
+	/* Serialize boot info */
+	const struct boot_info *boot_info = fw_directory_get_boot_info(fw_dir);
+
+	assert(boot_info);
+
+	output->directory_version = 2;
+	output->img_info_offset = offsetof(struct ts_fwu_image_directory, img_info_entry);
+	output->num_images = fw_directory_num_images(fw_dir);
+	output->correct_boot = (boot_info->active_index == boot_info->boot_index);
+	output->img_info_size = sizeof(struct ts_fwu_image_info_entry);
+	output->reserved = 0;
+
+	/* Serialize image info for each image */
+	for (size_t image_index = 0; image_index < output->num_images; image_index++) {
+
+		const struct image_info *image_info =
+			fw_directory_get_image_info(fw_dir, image_index);
+
+		assert(image_info);
+
+		memcpy(output->img_info_entry[image_index].img_type_uuid,
+			image_info->img_type_uuid.octets,
+			OSF_UUID_OCTET_LEN);
+
+		output->img_info_entry[image_index].client_permissions =
+			image_info->permissions;
+		output->img_info_entry[image_index].img_max_size =
+			image_info->max_size;
+		output->img_info_entry[image_index].lowest_accepted_version =
+			image_info->lowest_accepted_version;
+		output->img_info_entry[image_index].img_version =
+			image_info->active_version;
+		output->img_info_entry[image_index].accepted =
+			(uint32_t)fw_store_is_accepted(fw_store, image_info);
+	}
+
+	*data_len = serialized_len;
+
+	return FWU_STATUS_SUCCESS;
+}
+
+size_t img_dir_serializer_get_len(
+	const struct fw_directory *fw_dir)
+{
+	return
+		offsetof(struct ts_fwu_image_directory, img_info_entry) +
+		sizeof(struct ts_fwu_image_info_entry) * fw_directory_num_images(fw_dir);
+}
diff --git a/components/service/fwu/agent/img_dir_serializer.h b/components/service/fwu/agent/img_dir_serializer.h
new file mode 100644
index 0000000..28274f2
--- /dev/null
+++ b/components/service/fwu/agent/img_dir_serializer.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2022, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef IMG_DIR_SERIALIZER_H
+#define IMG_DIR_SERIALIZER_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Interface dependencies
+ */
+struct fw_directory;
+struct fw_store;
+
+/**
+ * \brief Serialize the public image directory
+ *
+ *  Using content from the internal fw_directory and fw_store, create
+ *  a serialized image_directory that conforms to the FWU-A specification
+ *  format.
+ *
+ * \param[in]  fw_dir         Source fw_directory
+ * \param[in]  fw_store       Source fw_store
+ * \param[in]  buf            Serialize into this buffer
+ * \param[in]  buf_size       Size of buffer
+ * \param[out] data_len       Length of serialized data
+ *
+ * \return Status
+ */
+int img_dir_serializer_serialize(
+	const struct fw_directory *fw_dir,
+	const struct fw_store *fw_store,
+	uint8_t *buf,
+	size_t buf_size,
+	size_t *data_len);
+
+/**
+ * \brief Return the length in bytes of the serialized image directory
+ *
+ * \param[in]  fw_dir         Source fw_directory
+ *
+ * \return Size in bytes
+ */
+size_t img_dir_serializer_get_len(
+	const struct fw_directory *fw_dir);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* IMG_DIR_SERIALIZER_H */
diff --git a/components/service/fwu/agent/stream_manager.c b/components/service/fwu/agent/stream_manager.c
new file mode 100644
index 0000000..5284f71
--- /dev/null
+++ b/components/service/fwu/agent/stream_manager.c
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <stddef.h>
+#include <string.h>
+#include <protocols/service/fwu/packed-c/status.h>
+#include <service/fwu/fw_store/fw_store.h>
+#include "stream_manager.h"
+
+static uint32_t generate_handle(
+	struct stream_manager *subject,
+	struct stream_context *context)
+{
+	/* Handle includes rolling count value to protect against use of a stale handle */
+	uint32_t new_handle = context - subject->contexts;
+
+	new_handle = (new_handle & 0xffff) | (subject->rolling_count << 16);
+	++subject->rolling_count;
+	return new_handle;
+}
+
+static uint32_t index_from_handle(uint32_t handle)
+{
+	return handle & 0xffff;
+}
+
+static void add_to_free_list(
+	struct stream_manager *subject,
+	struct stream_context *context)
+{
+	context->type = FWU_STREAM_TYPE_NONE;
+	context->handle = 0;
+	context->next = subject->free;
+	context->prev = NULL;
+	subject->free = context;
+}
+
+static struct stream_context *alloc_stream_context(
+	struct stream_manager *subject,
+	enum fwu_stream_type type,
+	uint32_t *handle)
+{
+	struct stream_context *context = NULL;
+
+	/* Re-cycle least-recently used context if there are no free contexts */
+	if (!subject->free && subject->active_tail) {
+
+		stream_manager_close(
+			subject,
+			subject->active_tail->handle,
+			false);
+	}
+
+	/* Active contexts are held in a linked list in most recently allocated order */
+	if (subject->free) {
+
+		context = subject->free;
+		subject->free = context->next;
+
+		context->next = subject->active_head;
+		context->prev = NULL;
+		subject->active_head = context;
+
+		if (!subject->active_tail)
+			subject->active_tail = context;
+
+		if (context->next)
+			context->next->prev = context;
+
+		context->type = type;
+
+		context->handle = generate_handle(subject, context);
+		*handle = context->handle;
+	}
+
+	return context;
+}
+
+static void free_stream_context(
+	struct stream_manager *subject,
+	struct stream_context *context)
+{
+	/* Remove from active list */
+	if (context->prev)
+		context->prev->next = context->next;
+	else
+		subject->active_head = context->next;
+
+	if (context->next)
+		context->next->prev = context->prev;
+	else
+		subject->active_tail = context->prev;
+
+	/* Add to free list */
+	add_to_free_list(subject, context);
+}
+
+static struct stream_context *get_active_context(
+	struct stream_manager *subject,
+	uint32_t handle)
+{
+	struct stream_context *context = NULL;
+	uint32_t index = index_from_handle(handle);
+
+	if ((index < FWU_STREAM_MANAGER_POOL_SIZE) &&
+		(subject->contexts[index].type != FWU_STREAM_TYPE_NONE) &&
+		(subject->contexts[index].handle == handle)) {
+
+		/* Handle qualifies an active stream context */
+		context = &subject->contexts[index];
+	}
+
+	return context;
+}
+
+void stream_manager_init(
+	struct stream_manager *subject)
+{
+	subject->free = NULL;
+	subject->active_head = NULL;
+	subject->active_tail = NULL;
+	subject->rolling_count = 0;
+
+	for (size_t i = 0; i < FWU_STREAM_MANAGER_POOL_SIZE; i++)
+		add_to_free_list(subject, &subject->contexts[i]);
+}
+
+void stream_manager_deinit(
+	struct stream_manager *subject)
+{
+	(void)subject;
+}
+
+int stream_manager_open_buffer_stream(
+	struct stream_manager *subject,
+	const uint8_t *data,
+	size_t data_len,
+	uint32_t *handle)
+{
+	struct stream_context *context;
+	int status = FWU_STATUS_UNKNOWN;
+
+	/* First cancel any buffer streams left open associated with
+	 * the same source buffer. Concurrent stream access to data in
+	 * a buffer is prevented to avoid the possibility of a buffer
+	 * being updated while a read stream is open.
+	 */
+	for (size_t i = 0; i < FWU_STREAM_MANAGER_POOL_SIZE; i++) {
+
+		context = &subject->contexts[i];
+
+		if ((context->type == FWU_STREAM_TYPE_BUFFER) &&
+			(context->variant.buffer.data == data))
+			free_stream_context(subject, context);
+	}
+
+	/* Allocate and initialize a new stream */
+	context = alloc_stream_context(
+		subject,
+		FWU_STREAM_TYPE_BUFFER,
+		handle);
+
+	if (context) {
+
+		context->variant.buffer.data = data;
+		context->variant.buffer.data_len = data_len;
+		context->variant.buffer.pos = 0;
+
+		status = FWU_STATUS_SUCCESS;
+	}
+
+	return status;
+}
+
+int stream_manager_open_install_stream(
+	struct stream_manager *subject,
+	struct fw_store *fw_store,
+	struct installer *installer,
+	const struct image_info *image_info,
+	uint32_t *stream_handle)
+{
+	struct stream_context *context;
+	int status = FWU_STATUS_UNKNOWN;
+
+	/* First cancel any install streams left open associated with
+	 * the same fw_store and installer. This defends against the
+	 * possibility of data written via two streams being written to the
+	 * same installer, resulting in image corruption.
+	 */
+	for (size_t i = 0; i < FWU_STREAM_MANAGER_POOL_SIZE; i++) {
+
+		context = &subject->contexts[i];
+
+		if ((context->type == FWU_STREAM_TYPE_INSTALL) &&
+			(context->variant.install.fw_store == fw_store) &&
+			(context->variant.install.installer == installer))
+			free_stream_context(subject, context);
+	}
+
+	/* Allocate and initialize a new stream */
+	context = alloc_stream_context(
+		subject,
+		FWU_STREAM_TYPE_INSTALL,
+		stream_handle);
+
+	if (context) {
+
+		context->variant.install.fw_store = fw_store;
+		context->variant.install.installer = installer;
+		context->variant.install.image_info = image_info;
+
+		status = FWU_STATUS_SUCCESS;
+	}
+
+	return status;
+}
+
+int stream_manager_close(
+	struct stream_manager *subject,
+	uint32_t handle,
+	bool accepted)
+{
+	int status = FWU_STATUS_UNKNOWN;
+	struct stream_context *context = get_active_context(subject, handle);
+
+	if (context) {
+
+		status = FWU_STATUS_SUCCESS;
+
+		if (context->type == FWU_STREAM_TYPE_INSTALL) {
+
+			status = fw_store_commit_image(
+				context->variant.install.fw_store,
+				context->variant.install.installer,
+				context->variant.install.image_info,
+				accepted);
+		}
+
+		free_stream_context(subject, context);
+	}
+
+	return status;
+}
+
+void stream_manager_cancel_streams(
+	struct stream_manager *subject,
+	enum fwu_stream_type type)
+{
+	for (size_t i = 0; i < FWU_STREAM_MANAGER_POOL_SIZE; i++) {
+
+		struct stream_context *context = &subject->contexts[i];
+
+		if (context->type == type)
+			free_stream_context(subject, context);
+	}
+}
+
+bool stream_manager_is_open_streams(
+	const struct stream_manager *subject,
+	enum fwu_stream_type type)
+{
+	bool any_open = false;
+
+	for (size_t i = 0; i < FWU_STREAM_MANAGER_POOL_SIZE; i++) {
+
+		const struct stream_context *context = &subject->contexts[i];
+
+		if (context->type == type) {
+
+			any_open = true;
+			break;
+		}
+	}
+
+	return any_open;
+}
+
+int stream_manager_write(
+	struct stream_manager *subject,
+	uint32_t handle,
+	const uint8_t *data,
+	size_t data_len)
+{
+	int status = FWU_STATUS_UNKNOWN;
+	struct stream_context *context = get_active_context(subject, handle);
+
+	if (context && context->type == FWU_STREAM_TYPE_INSTALL) {
+
+		status = fw_store_write_image(
+			context->variant.install.fw_store,
+			context->variant.install.installer,
+			data, data_len);
+	}
+
+	return status;
+}
+
+int stream_manager_read(
+	struct stream_manager *subject,
+	uint32_t handle,
+	uint8_t *buf,
+	size_t buf_size,
+	size_t *read_len,
+	size_t *total_len)
+{
+	int status = FWU_STATUS_UNKNOWN;
+	struct stream_context *context = get_active_context(subject, handle);
+
+	if (context) {
+
+		if (context->type == FWU_STREAM_TYPE_BUFFER) {
+
+			size_t pos = context->variant.buffer.pos;
+			size_t remaining_len = context->variant.buffer.data_len - pos;
+			size_t len_to_read =
+				(remaining_len <= buf_size) ? remaining_len : buf_size;
+
+			memcpy(buf, &context->variant.buffer.data[pos], len_to_read);
+
+			*read_len = len_to_read;
+			*total_len = context->variant.buffer.data_len;
+			context->variant.buffer.pos = pos + len_to_read;
+
+			status = FWU_STATUS_SUCCESS;
+		} else {
+
+			/* Reading from other types of stream is forbidden */
+			status = FWU_STATUS_DENIED;
+		}
+	}
+
+	return status;
+}
diff --git a/components/service/fwu/agent/stream_manager.h b/components/service/fwu/agent/stream_manager.h
new file mode 100644
index 0000000..422080d
--- /dev/null
+++ b/components/service/fwu/agent/stream_manager.h
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef FWU_STREAM_MANAGER_H
+#define FWU_STREAM_MANAGER_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
+
+/**
+ * Manages the set of streams used by the update_agent for image installation
+ * and accessing other FWU related objects. A subject of stream objects is managed
+ * to allow for concurrent streams if needed. To defend against a badly behaved
+ * client that fails to close streams, if necessary, the least recently used
+ * open stream will be reused if necessary to prevent a denial of service attack
+ * where a rogue client opens streams but doesn't close them.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Interface dependencies
+ */
+struct fw_store;
+struct installer;
+struct image_info;
+
+/**
+ *  The default stream subject size
+ */
+#ifndef FWU_STREAM_MANAGER_POOL_SIZE
+#define FWU_STREAM_MANAGER_POOL_SIZE        (4)
+#endif
+
+/**
+ *  Identifier for the type of stream
+ */
+enum fwu_stream_type {
+	FWU_STREAM_TYPE_NONE,
+	FWU_STREAM_TYPE_BUFFER,
+	FWU_STREAM_TYPE_INSTALL
+};
+
+/**
+ * A stream context
+ */
+struct stream_context {
+	enum fwu_stream_type type;
+	uint32_t handle;
+	struct stream_context *next;
+	struct stream_context *prev;
+
+	union stream_variant {
+
+		/* Buffer stream variant */
+		struct buffer_variant {
+			size_t pos;
+			const uint8_t *data;
+			size_t data_len;
+
+		} buffer;
+
+		/* Install stream variant */
+		struct install_variant {
+			struct fw_store *fw_store;
+			struct installer *installer;
+			const struct image_info *image_info;
+
+		} install;
+
+	} variant;
+};
+
+/**
+ * The stream_manager structure.
+ */
+struct stream_manager {
+	struct stream_context contexts[FWU_STREAM_MANAGER_POOL_SIZE];
+	struct stream_context *free;
+	struct stream_context *active_head;
+	struct stream_context *active_tail;
+	uint16_t rolling_count;
+};
+
+/**
+ * \brief One-time initialization
+ *
+ * \param[in]  subject    The subject stream_manager
+ */
+void stream_manager_init(struct stream_manager *subject);
+
+/**
+ * \brief De-initializes a stream_manager
+ *
+ * \param[in]  subject    The subject stream_manager
+ */
+void stream_manager_deinit(struct stream_manager *subject);
+
+/**
+ * \brief Open a buffer stream
+ *
+ * Opens a stream for reading from a buffer containing the complete object
+ * to access.
+ *
+ * \param[in]  subject    The subject stream_manager
+ * \param[in]  data       Pointer to the buffer containing data
+ * \param[in]  data_len   The length of the data
+ * \param[out] stream_handle   The stream handle to use for subsequent operations
+ *
+ * \return FWU status
+ */
+int stream_manager_open_buffer_stream(
+	struct stream_manager *subject,
+	const uint8_t *data,
+	size_t data_len,
+	uint32_t *stream_handle);
+
+/**
+ * \brief Open an install stream
+ *
+ * Open a stream for writing an image to an installer. A concrete installer
+ * is responsible for installing the image into storage.
+ *
+ * \param[in]  subject       The subject stream_manager
+ * \param[in]  fw_store      The image_info for the image to install
+ * \param[in]  installer     The installer
+ * \param[in]  image_info    The image_info corresponding to the image to install
+ * \param[out] stream_handle The stream_handle to use for subsequent operations
+ *
+ * \return FWU status
+ */
+int stream_manager_open_install_stream(
+	struct stream_manager *subject,
+	struct fw_store *fw_store,
+	struct installer *installer,
+	const struct image_info *image_info,
+	uint32_t *stream_handle);
+
+/**
+ * \brief Close a previously opened stream
+ *
+ * \param[in]  subject    The subject stream_manager
+ * \param[in]  stream_handle   Stream handle
+ * \param[in]  accepted   The initial accepted state for an installed image
+ *
+ * \return FWU status
+ */
+int stream_manager_close(
+	struct stream_manager *subject,
+	uint32_t stream_handle,
+	bool accepted);
+
+/**
+ * \brief Cancel all streams of the specified type
+ *
+ * \param[in]  subject    The subject stream_manager
+ * \param[in]  type       Type of stream to cancel
+ */
+void stream_manager_cancel_streams(
+	struct stream_manager *subject,
+	enum fwu_stream_type type);
+
+/**
+ * \brief Check for any open streams of the specified type
+ *
+ * \param[in]  subject    The subject stream_manager
+ * \param[in]  type       Type of stream to check
+ *
+ * \return True is any are open
+ */
+bool stream_manager_is_open_streams(
+	const struct stream_manager *subject,
+	enum fwu_stream_type type);
+
+/**
+ * \brief Write to a previously opened stream
+ *
+ * \param[in]  subject    The subject stream_manager
+ * \param[in]  stream_handle  The handle returned by open
+ * \param[in]  data       Pointer to data
+ * \param[in]  data_len   The data length
+ *
+ * \return Status (0 on success)
+ */
+int stream_manager_write(
+	struct stream_manager *subject,
+	uint32_t stream_handle,
+	const uint8_t *data,
+	size_t data_len);
+
+/**
+ * \brief Read from a previously opened stream
+ *
+ * \param[in]  subject    The subject stream_manager
+ * \param[in]  stream_handle The handle returned by open
+ * \param[in]  buf        Pointer to buffer to copy to
+ * \param[in]  buf_size   The size of the buffer
+ * \param[out] read_len   The length of data read
+ * \param[out] total_len  The total length of object to read
+ *
+ * \return Status (0 on success)
+ */
+int stream_manager_read(
+	struct stream_manager *subject,
+	uint32_t handle,
+	uint8_t *buf,
+	size_t buf_size,
+	size_t *read_len,
+	size_t *total_len);
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* FWU_STREAM_MANAGER_H */
diff --git a/components/service/fwu/agent/update_agent.c b/components/service/fwu/agent/update_agent.c
new file mode 100644
index 0000000..c54e9e5
--- /dev/null
+++ b/components/service/fwu/agent/update_agent.c
@@ -0,0 +1,395 @@
+/*
+ * Copyright (c) 2022, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <common/uuid/uuid.h>
+#include <protocols/service/fwu/packed-c/status.h>
+#include <protocols/service/fwu/packed-c/fwu_proto.h>
+#include <service/fwu/fw_store/fw_store.h>
+#include <service/fwu/inspector/fw_inspector.h>
+#include "img_dir_serializer.h"
+#include "update_agent.h"
+
+
+static bool open_image_directory(
+	struct update_agent *update_agent,
+	const struct uuid_octets *uuid,
+	uint32_t *handle,
+	int *status);
+
+static bool open_fw_store_object(
+	struct update_agent *update_agent,
+	const struct uuid_octets *uuid,
+	uint32_t *handle,
+	int *status);
+
+static bool open_fw_image(
+	struct update_agent *update_agent,
+	const struct uuid_octets *uuid,
+	uint32_t *handle,
+	int *status);
+
+
+int update_agent_init(
+	struct update_agent *update_agent,
+	unsigned int boot_index,
+	fw_inspector_inspect fw_inspect_method,
+	struct fw_store *fw_store)
+{
+	assert(update_agent);
+	assert(fw_inspect_method);
+	assert(fw_store);
+
+	int status = FWU_STATUS_UNKNOWN;
+
+	update_agent->state = FWU_STATE_INITIALIZING;
+	update_agent->fw_inspect_method = fw_inspect_method;
+	update_agent->fw_store = fw_store;
+	update_agent->image_dir_buf_size = 0;
+	update_agent->image_dir_buf = NULL;
+
+	stream_manager_init(&update_agent->stream_manager);
+
+	/* Initialize and populate the fw_directory. The fw_inspector will
+	 * obtain trustworthy information about the booted firmware and
+	 * populate the fw_directory to reflect information about the booted
+	 * firmware.
+	 */
+	fw_directory_init(&update_agent->fw_directory);
+
+	status = update_agent->fw_inspect_method(
+		&update_agent->fw_directory,
+		boot_index);
+	if (status != FWU_STATUS_SUCCESS)
+		return status;
+
+	/* Allow the associated fw_store to synchronize its state to the
+	 * state of the booted firmware reflected by the fw_directory.
+	 */
+	status = fw_store_synchronize(
+		update_agent->fw_store,
+		&update_agent->fw_directory,
+		boot_index);
+	if (status != FWU_STATUS_SUCCESS)
+		return status;
+
+	/* Allocate a buffer for holding the serialized image directory  */
+	update_agent->image_dir_buf_size = img_dir_serializer_get_len(&update_agent->fw_directory);
+	update_agent->image_dir_buf = malloc(update_agent->image_dir_buf_size);
+	if (!update_agent->image_dir_buf)
+		return FWU_STATUS_UNKNOWN;
+
+	/* Transition to initial state */
+	update_agent->state =
+		fw_store_is_trial(update_agent->fw_store) ?
+		FWU_STATE_TRIAL : FWU_STATE_REGULAR;
+
+	return FWU_STATUS_SUCCESS;
+}
+
+void update_agent_deinit(
+	struct update_agent *update_agent)
+{
+	update_agent->state = FWU_STATE_DEINITIALZED;
+
+	free(update_agent->image_dir_buf);
+	fw_directory_deinit(&update_agent->fw_directory);
+	stream_manager_deinit(&update_agent->stream_manager);
+}
+
+int update_agent_begin_staging(
+	struct update_agent *update_agent)
+{
+	int status = FWU_STATUS_DENIED;
+
+	/* If already staging, any previous installation state is discarded */
+	update_agent_cancel_staging(update_agent);
+
+	if (update_agent->state == FWU_STATE_REGULAR) {
+
+		status = fw_store_begin_install(update_agent->fw_store);
+
+		/* Check if ready to install images */
+		if (status == FWU_STATUS_SUCCESS)
+			update_agent->state = FWU_STATE_STAGING;
+	}
+
+	return status;
+}
+
+int update_agent_end_staging(
+	struct update_agent *update_agent)
+{
+	int status = FWU_STATUS_DENIED;
+
+	if (update_agent->state == FWU_STATE_STAGING) {
+
+		/* The client is responsible for committing each installed image. If any
+		 * install streams have been left open, not all images were committed.
+		 */
+		bool any_uncommitted = stream_manager_is_open_streams(
+			&update_agent->stream_manager, FWU_STREAM_TYPE_INSTALL);
+
+		if (!any_uncommitted) {
+
+			/* All installed images have been committed so we're
+			 * ready for a trial.
+			 */
+			status = fw_store_finalize_install(update_agent->fw_store);
+
+			if (status == FWU_STATUS_SUCCESS)
+				/* Transition to TRAIL_PENDING state. The trial actually starts
+				 * when installed images are activated through a system restart.
+				 */
+				update_agent->state = FWU_STATE_TRIAL_PENDING;
+
+		} else {
+
+			/* Client failed to commit all images installed */
+			status = FWU_STATUS_BUSY;
+		}
+	}
+
+	return status;
+}
+
+int update_agent_cancel_staging(
+	struct update_agent *update_agent)
+{
+	int status = FWU_STATUS_DENIED;
+
+	if (update_agent->state == FWU_STATE_STAGING) {
+
+		stream_manager_cancel_streams(
+			&update_agent->stream_manager, FWU_STREAM_TYPE_INSTALL);
+
+		fw_store_cancel_install(update_agent->fw_store);
+
+		update_agent->state = FWU_STATE_REGULAR;
+
+		status = FWU_STATUS_SUCCESS;
+	}
+
+	return status;
+}
+
+int update_agent_accept(
+	struct update_agent *update_agent,
+	const struct uuid_octets *image_type_uuid)
+{
+	int status = FWU_STATUS_DENIED;
+
+	if (update_agent->state == FWU_STATE_TRIAL) {
+
+		const struct image_info *image_info = fw_directory_find_image_info(
+			&update_agent->fw_directory,
+			image_type_uuid);
+
+		if (image_info) {
+
+			if (fw_store_notify_accepted(
+				update_agent->fw_store, image_info)) {
+
+				/* From the fw_store perspective, the update has
+				 * been fully accepted.
+				 */
+				status = fw_store_commit_to_update(update_agent->fw_store);
+				update_agent->state = FWU_STATE_REGULAR;
+
+			} else
+				/* Still more images to accept */
+				status = FWU_STATUS_SUCCESS;
+		} else
+			/* Unrecognised image uuid */
+			status = FWU_STATUS_UNKNOWN;
+	}
+
+	return status;
+}
+
+int update_agent_select_previous(
+	struct update_agent *update_agent)
+{
+	int status = FWU_STATUS_DENIED;
+
+	if ((update_agent->state == FWU_STATE_TRIAL) ||
+		(update_agent->state == FWU_STATE_TRIAL_PENDING)) {
+
+		status = fw_store_revert_to_previous(update_agent->fw_store);
+		update_agent->state = FWU_STATE_REGULAR;
+	}
+
+	return status;
+}
+
+int update_agent_open(
+	struct update_agent *update_agent,
+	const struct uuid_octets *uuid,
+	uint32_t *handle)
+{
+	int status;
+
+	/* Pass UUID along a chain-of-responsibility until it's handled */
+	if (!open_image_directory(update_agent, uuid, handle, &status) &&
+		!open_fw_store_object(update_agent, uuid, handle, &status) &&
+		!open_fw_image(update_agent, uuid, handle, &status)) {
+
+		/* UUID not recognised */
+		status = FWU_STATUS_UNKNOWN;
+	}
+
+	return status;
+}
+
+int update_agent_commit(
+	struct update_agent *update_agent,
+	uint32_t handle,
+	bool accepted)
+{
+	return stream_manager_close(
+		&update_agent->stream_manager,
+		handle,
+		accepted);
+}
+
+int update_agent_write_stream(
+	struct update_agent *update_agent,
+	uint32_t handle,
+	const uint8_t *data,
+	size_t data_len)
+{
+	return stream_manager_write(
+		&update_agent->stream_manager,
+		handle,
+		data, data_len);
+}
+
+int update_agent_read_stream(
+	struct update_agent *update_agent,
+	uint32_t handle,
+	uint8_t *buf,
+	size_t buf_size,
+	size_t *read_len,
+	size_t *total_len)
+{
+	return stream_manager_read(
+		&update_agent->stream_manager,
+		handle,
+		buf, buf_size,
+		read_len, total_len);
+}
+
+static bool open_image_directory(
+	struct update_agent *update_agent,
+	const struct uuid_octets *uuid,
+	uint32_t *handle,
+	int *status)
+{
+	struct uuid_octets target_uuid;
+
+	uuid_guid_octets_from_canonical(&target_uuid, FWU_DIRECTORY_CANONICAL_UUID);
+
+	if (uuid_is_equal(uuid->octets, target_uuid.octets)) {
+
+		/* Serialize a fresh view of the image directory */
+		size_t serialized_len = 0;
+
+		*status = img_dir_serializer_serialize(
+			&update_agent->fw_directory,
+			update_agent->fw_store,
+			update_agent->image_dir_buf,
+			update_agent->image_dir_buf_size,
+			&serialized_len);
+
+		if (*status == FWU_STATUS_SUCCESS) {
+
+			*status = stream_manager_open_buffer_stream(
+				&update_agent->stream_manager,
+				update_agent->image_dir_buf,
+				serialized_len,
+				handle);
+		}
+
+		return true;
+	}
+
+	return false;
+}
+
+static bool open_fw_store_object(
+	struct update_agent *update_agent,
+	const struct uuid_octets *uuid,
+	uint32_t *handle,
+	int *status)
+{
+	const uint8_t *exported_data;
+	size_t exported_data_len;
+
+	if (fw_store_export(
+			update_agent->fw_store,
+			uuid,
+			&exported_data, &exported_data_len,
+			status)) {
+
+		if (*status == FWU_STATUS_SUCCESS) {
+
+			*status = stream_manager_open_buffer_stream(
+				&update_agent->stream_manager,
+				exported_data,
+				exported_data_len,
+				handle);
+		}
+
+		return true;
+	}
+
+	return false;
+}
+
+static bool open_fw_image(
+	struct update_agent *update_agent,
+	const struct uuid_octets *uuid,
+	uint32_t *handle,
+	int *status)
+{
+	const struct image_info *image_info = fw_directory_find_image_info(
+		&update_agent->fw_directory,
+		uuid);
+
+	if (image_info) {
+
+		if (update_agent->state == FWU_STATE_STAGING) {
+
+			struct installer *installer;
+
+			*status = fw_store_select_installer(
+				update_agent->fw_store,
+				image_info,
+				&installer);
+
+			if (*status == FWU_STATUS_SUCCESS) {
+
+				*status = stream_manager_open_install_stream(
+					&update_agent->stream_manager,
+					update_agent->fw_store,
+					installer,
+					image_info,
+					handle);
+			}
+		} else {
+
+			/* Attempting to open a fw image when not staging */
+			*status = FWU_STATUS_DENIED;
+		}
+
+		return true;
+	}
+
+	return false;
+}
diff --git a/components/service/fwu/agent/update_agent.h b/components/service/fwu/agent/update_agent.h
new file mode 100644
index 0000000..274e71a
--- /dev/null
+++ b/components/service/fwu/agent/update_agent.h
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2022, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef FW_UPDATE_AGENT_H
+#define FW_UPDATE_AGENT_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <common/uuid/uuid.h>
+#include <service/fwu/inspector/fw_inspector.h>
+#include "fw_directory.h"
+#include "stream_manager.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Interface dependencies
+ */
+struct fw_store;
+
+/**
+ * \brief Update process states
+ *
+ * The update_agent is responsible for ensuring that only a valid update flow
+ * is followed by a client. To enforce the flow, public operations can only be
+ * used in a valid state that reflects the FWU-A behavioral model.
+ */
+enum fwu_state {
+	FWU_STATE_DEINITIALZED,
+	FWU_STATE_INITIALIZING,
+	FWU_STATE_REGULAR,
+	FWU_STATE_STAGING,
+	FWU_STATE_TRIAL_PENDING,
+	FWU_STATE_TRIAL
+};
+
+/**
+ * \brief update_agent structure definition
+ *
+ * An update_agent instance is responsible for coordinating firmware updates applied
+ * to a fw_store. An update_agent performs a security role by enforcing that a
+ * valid flow is performed to update the fw store.
+ */
+struct update_agent {
+	enum fwu_state state;
+	fw_inspector_inspect fw_inspect_method;
+	struct fw_store *fw_store;
+	struct fw_directory fw_directory;
+	struct stream_manager stream_manager;
+	uint8_t *image_dir_buf;
+	size_t image_dir_buf_size;
+};
+
+/**
+ * \brief Initialise the update_agent
+ *
+ * \param[in]  update_agent    The subject update_agent
+ * \param[in]  boot_index      The boot_index used by the bootloader
+ * \param[in]  fw_inspect_method  fw_inspector inspect method
+ * \param[in]  fw_store        The fw_store to manage
+ *
+ * \return Status (0 for success).  Uses fwu protocol status codes.
+ */
+int update_agent_init(
+	struct update_agent *update_agent,
+	unsigned int boot_index,
+	fw_inspector_inspect fw_inspect_method,
+	struct fw_store *fw_store);
+
+/**
+ * \brief De-initialise the update_agent
+ *
+ * \param[in]  update_agent    The subject update_agent
+ */
+void update_agent_deinit(
+	struct update_agent *update_agent);
+
+/**
+ * \brief Begin staging
+ *
+ * \param[in]  update_agent    The subject update_agent
+ *
+ * \return 0 on successfully transitioning to the STAGING state
+ */
+int update_agent_begin_staging(
+	struct update_agent *update_agent);
+
+/**
+ * \brief End staging
+ *
+ * \param[in]  update_agent    The subject update_agent
+ *
+ * \return 0 on successfully transitioning to the TRIAL state
+ */
+int update_agent_end_staging(
+	struct update_agent *update_agent);
+
+/**
+ * \brief Cancel staging
+ *
+ * \param[in]  update_agent    The subject update_agent
+ *
+ * \return 0 on successfully transitioning to the REGULAR state
+ */
+int update_agent_cancel_staging(
+	struct update_agent *update_agent);
+
+/**
+ * \brief Accept an updated image
+ *
+ * \param[in]  update_agent    The subject update_agent
+ * \param[in]  image_type_uuid Identifies the image to accept
+ *
+ * \return Status (0 on success)
+ */
+int update_agent_accept(
+	struct update_agent *update_agent,
+	const struct uuid_octets *image_type_uuid);
+
+/**
+ * \brief Select previous version
+ *
+ *  Revert to a previous good version (if possible).
+ *
+ * \param[in]  update_agent    The subject update_agent
+ *
+ * \return Status (0 on success)
+ */
+int update_agent_select_previous(
+	struct update_agent *update_agent);
+
+/**
+ * \brief Open a stream for accessing an fwu stream
+ *
+ * Used for reading or writing data for accessing images or other fwu
+ * related objects.
+ *
+ * \param[in]  update_agent    The subject update_agent
+ * \param[in]  uuid            Identifies the object to access
+ * \param[out] handle          For subsequent read/write operations
+ *
+ * \return Status (0 on success)
+ */
+int update_agent_open(
+	struct update_agent *update_agent,
+	const struct uuid_octets *uuid,
+	uint32_t *handle);
+
+/**
+ * \brief Close a stream and commit any writes to the stream
+ *
+ * \param[in]  update_agent    The subject update_agent
+ * \param[in]  handle          The handle returned by open
+ * \param[in]  accepted        Initial accepted state of an image
+ *
+ * \return Status (0 on success)
+ */
+int update_agent_commit(
+	struct update_agent *update_agent,
+	uint32_t handle,
+	bool accepted);
+
+/**
+ * \brief Write to a previously opened stream
+ *
+ * \param[in]  update_agent    The subject update_agent
+ * \param[in]  handle          The handle returned by open
+ * \param[in]  data            Pointer to data
+ * \param[in]  data_len        The data length
+ *
+ * \return Status (0 on success)
+ */
+int update_agent_write_stream(
+	struct update_agent *update_agent,
+	uint32_t handle,
+	const uint8_t *data,
+	size_t data_len);
+
+/**
+ * \brief Read from a previously opened stream
+ *
+ * \param[in]  update_agent    The subject update_agent
+ * \param[in]  handle          The handle returned by open
+ * \param[in]  buf             Pointer to buffer to copy to
+ * \param[in]  buf_size        The size of the buffer
+ * \param[out] read_len        The length of data read
+ * \param[out] total_len       The total length of the object to read
+ *
+ * \return Status (0 on success)
+ */
+int update_agent_read_stream(
+	struct update_agent *update_agent,
+	uint32_t handle,
+	uint8_t *buf,
+	size_t buf_size,
+	size_t *read_len,
+	size_t *total_len);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FW_UPDATE_AGENT_H */