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 */