Implement PSA FWU M update agent
Implement 'Platform Security Firmware Update for the A-profile Arm
Architecture' (DEN0118) update agent which uses 'PSA Certified
Firmware Update API 1.0' (IHI0093) as its backend. In practice this
covers the use case where the update agent is running in a Secure
Partition in the A class side and delegates the firmware update to an
M class core running Trusted Firmware-M.
The commit also contains the unit test for the update agent and its
integration to component-test.
Signed-off-by: Imre Kis <imre.kis@arm.com>
Change-Id: Iaea74be5b2ab7635942e3d3ca37f8b16cb9bc5f1
diff --git a/components/service/fwu/psa_fwu_m/agent/component.cmake b/components/service/fwu/psa_fwu_m/agent/component.cmake
new file mode 100644
index 0000000..adffc44
--- /dev/null
+++ b/components/service/fwu/psa_fwu_m/agent/component.cmake
@@ -0,0 +1,13 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2024, 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}/psa_fwu_m_update_agent.c"
+)
diff --git a/components/service/fwu/psa_fwu_m/agent/psa_fwu_m_update_agent.c b/components/service/fwu/psa_fwu_m/agent/psa_fwu_m_update_agent.c
new file mode 100644
index 0000000..6de9ba7
--- /dev/null
+++ b/components/service/fwu/psa_fwu_m/agent/psa_fwu_m_update_agent.c
@@ -0,0 +1,673 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#include "psa_fwu_m_update_agent.h"
+#include "common/uuid/uuid.h"
+#include "service/fwu/psa_fwu_m/interface/update.h"
+#include "protocols/service/fwu/fwu_proto.h"
+#include "protocols/service/fwu/status.h"
+#include "util.h"
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef PSA_FWU_M_MAX_HANDLES
+#define PSA_FWU_M_MAX_HANDLES (8)
+#endif /* PSA_FWU_M_MAX_HANDLES */
+
+#define FWU_INVALID_HANDLE (0xffffffff)
+
+enum psa_fwu_m_state {
+ regular,
+ staging,
+ trial,
+};
+
+struct psa_fwu_m_update_agent;
+
+struct psa_fwu_m_image {
+ struct uuid_octets uuid;
+ psa_fwu_component_t component;
+ bool selected_for_staging;
+ bool accepted;
+ int (*read)(struct psa_fwu_m_update_agent *agent, struct psa_fwu_m_image *image,
+ uint8_t *buf, size_t buf_size, size_t *read_len, size_t *total_len);
+ int (*write)(struct psa_fwu_m_update_agent *agent, struct psa_fwu_m_image *image,
+ size_t data_offset, const uint8_t *data, size_t data_len);
+};
+
+struct psa_fwu_m_handle {
+ bool used;
+ size_t current_offset;
+ uint8_t op_type;
+ struct psa_fwu_m_image *image;
+};
+
+struct psa_fwu_m_update_agent {
+ struct psa_fwu_m_image *images;
+ size_t image_count;
+ uint32_t max_payload_size;
+ enum psa_fwu_m_state state;
+ struct psa_fwu_m_handle handles[PSA_FWU_M_MAX_HANDLES];
+};
+
+static int cancel_staging(void *context);
+
+static int psa_status_to_fwu_status(psa_status_t psa_status)
+{
+ switch (psa_status) {
+ case PSA_SUCCESS:
+ return FWU_STATUS_SUCCESS;
+
+ case PSA_ERROR_DOES_NOT_EXIST:
+ return FWU_STATUS_UNKNOWN;
+
+ case PSA_ERROR_INVALID_ARGUMENT:
+ return FWU_STATUS_OUT_OF_BOUNDS;
+
+ case PSA_ERROR_INVALID_SIGNATURE:
+ return FWU_STATUS_AUTH_FAIL;
+
+ case PSA_ERROR_NOT_PERMITTED:
+ return FWU_STATUS_NO_PERMISSION;
+
+ case PSA_ERROR_BAD_STATE:
+ default:
+ return FWU_STATUS_DENIED;
+ }
+}
+
+/* Image functions */
+static struct psa_fwu_m_image *find_image(struct psa_fwu_m_update_agent *agent,
+ const struct uuid_octets *uuid)
+{
+ size_t i = 0;
+
+ for (i = 0; i < agent->image_count; i++)
+ if (uuid_is_equal(uuid->octets, agent->images[i].uuid.octets))
+ return &agent->images[i];
+
+ return NULL;
+}
+
+static int image_write(struct psa_fwu_m_update_agent *agent, struct psa_fwu_m_image *image,
+ size_t data_offset, const uint8_t *data, size_t data_len)
+{
+ return psa_status_to_fwu_status(
+ psa_fwu_write(image->component, data_offset, data, data_len));
+}
+
+/* Image directory functions */
+uint32_t image_version_to_uint(psa_fwu_image_version_t version)
+{
+ uint32_t result = 0;
+
+ result |= ((uint32_t)version.major) << 24;
+ result |= ((uint32_t)version.minor) << 16;
+ result |= (uint32_t)version.patch;
+ /* There's no room for the build number */
+
+ return result;
+}
+
+int image_directory_read(struct psa_fwu_m_update_agent *agent, struct psa_fwu_m_image *image,
+ uint8_t *buf, size_t buf_size, size_t *read_len, size_t *total_len)
+{
+ psa_status_t psa_status = PSA_ERROR_GENERIC_ERROR;
+ psa_fwu_component_info_t component_info = { 0 };
+ struct fwu_image_directory *directory = NULL;
+ size_t image_count = agent->image_count - 1; /* Do not return Image directory */
+ size_t image_info_size = 0;
+ size_t i = 0;
+
+ *read_len = 0;
+ *total_len = 0;
+
+ /* Calculate total length */
+ if (MUL_OVERFLOW(sizeof(struct fwu_image_info_entry), image_count, &image_info_size))
+ return FWU_STATUS_DENIED; /* LCOV_EXCL_LINE */
+
+ if (ADD_OVERFLOW(sizeof(struct fwu_image_directory), image_info_size, total_len))
+ return FWU_STATUS_DENIED; /* LCOV_EXCL_LINE */
+
+ /*
+ * If the directory structure doesn't fit into the buffer return SUCCESS with total_len set
+ * and read_len = 0.
+ */
+ if (*total_len > buf_size)
+ return FWU_STATUS_SUCCESS;
+
+ directory = (struct fwu_image_directory *)buf;
+ directory->directory_version = FWU_IMAGE_DIRECTORY_VERSION;
+ directory->img_info_offset = offsetof(struct fwu_image_directory, img_info_entry);
+ directory->num_images = image_count;
+ directory->correct_boot = 1; /* Set to 1 and then set to 0 if any image is not accepted */
+ directory->img_info_size = sizeof(struct fwu_image_info_entry);
+ directory->reserved = 0;
+
+ for (i = 0; i < image_count; i++) {
+ struct fwu_image_info_entry *entry = &directory->img_info_entry[i];
+ struct psa_fwu_m_image *image = &agent->images[i];
+
+ psa_status = psa_fwu_query(image->component, &component_info);
+ if (psa_status != PSA_SUCCESS)
+ return psa_status_to_fwu_status(psa_status);
+
+ memcpy(entry->img_type_uuid, image->uuid.octets, sizeof(entry->img_type_uuid));
+ entry->client_permissions = 0x1; /* Only write is supported by the API */
+ entry->img_max_size = component_info.max_size;
+ entry->lowest_accepted_version = 0; /* This information is not available */
+ entry->img_version = image_version_to_uint(component_info.version);
+ if (component_info.state == PSA_FWU_UPDATED) {
+ entry->accepted = 1;
+ } else {
+ entry->accepted = 0;
+ directory->correct_boot = 0;
+ }
+ entry->reserved = 0;
+ }
+
+ *read_len = *total_len;
+
+ return FWU_STATUS_SUCCESS;
+}
+
+/* Image handle functions */
+static uint32_t allocate_handle(struct psa_fwu_m_update_agent *agent, struct psa_fwu_m_image *image,
+ uint8_t op_type)
+{
+ size_t i = 0;
+
+ for (i = 0; i < ARRAY_SIZE(agent->handles); i++) {
+ struct psa_fwu_m_handle *handle = &agent->handles[i];
+
+ if (!handle->used) {
+ handle->used = true;
+ handle->current_offset = 0;
+ handle->op_type = op_type;
+ handle->image = image;
+
+ return i;
+ }
+ }
+
+ return FWU_INVALID_HANDLE;
+}
+
+static struct psa_fwu_m_handle *get_handle(struct psa_fwu_m_update_agent *agent,
+ uint32_t handle_index)
+{
+ if (handle_index >= ARRAY_SIZE(agent->handles))
+ return NULL;
+
+ if (!agent->handles[handle_index].used)
+ return NULL;
+
+ return &agent->handles[handle_index];
+}
+
+static void free_handle(struct psa_fwu_m_handle *handle)
+{
+ *handle = (struct psa_fwu_m_handle){ 0 };
+}
+
+/* Misc functions */
+static int clean(void *context)
+{
+ struct psa_fwu_m_update_agent *agent = (struct psa_fwu_m_update_agent *)context;
+ psa_status_t psa_status = PSA_ERROR_GENERIC_ERROR;
+ uint32_t i = 0;
+
+ for (i = 0; i < agent->image_count; i++) {
+ struct psa_fwu_m_image *image = &agent->images[i];
+
+ /* Skip read-only images */
+ if (!image->write)
+ continue;
+
+ psa_status = psa_fwu_clean(image->component);
+ if (psa_status != PSA_SUCCESS)
+ return psa_status_to_fwu_status(psa_status);
+
+ image->selected_for_staging = false;
+ image->accepted = false;
+ }
+
+ return FWU_STATUS_SUCCESS;
+}
+
+void set_agent_state(struct psa_fwu_m_update_agent *agent, enum psa_fwu_m_state state)
+{
+ struct psa_fwu_m_image *image = NULL;
+ size_t i = 0;
+
+ if (state == regular) {
+ for (i = 0; i < agent->image_count; i++) {
+ image = &agent->images[i];
+
+ image->selected_for_staging = false;
+ image->accepted = false;
+ }
+ }
+
+ agent->state = state;
+}
+
+/* Update agent interface */
+static int discover(void *context, struct fwu_discovery_result *result)
+{
+ struct psa_fwu_m_update_agent *agent = (struct psa_fwu_m_update_agent *)context;
+
+ result->service_status = 0;
+ result->version_major = FWU_PROTOCOL_VERSION_MAJOR;
+ result->version_minor = FWU_PROTOCOL_VERSION_MINOR;
+ result->max_payload_size = agent->max_payload_size;
+ result->flags = FWU_FLAG_PARTIAL_UPDATE;
+ result->vendor_specific_flags = 0;
+
+ return FWU_STATUS_SUCCESS;
+}
+
+static int begin_staging(void *context, uint32_t vendor_flags, uint32_t partial_update_count,
+ const struct uuid_octets *update_guid)
+{
+ struct psa_fwu_m_update_agent *agent = (struct psa_fwu_m_update_agent *)context;
+ psa_status_t psa_status = PSA_ERROR_GENERIC_ERROR;
+ int result = FWU_STATUS_DENIED;
+ uint32_t i = 0;
+
+ switch (agent->state) {
+ case staging:
+ /* Discard pending state */
+ result = cancel_staging(context);
+ if (result != FWU_STATUS_SUCCESS)
+ goto error;
+
+ result = clean(context);
+ if (result != FWU_STATUS_SUCCESS)
+ return result;
+
+ /* fallthrough */
+
+ case regular:
+ if (partial_update_count) {
+ /* Put selected images into staging state */
+ for (i = 0; i < partial_update_count; i++) {
+ struct psa_fwu_m_image *image = NULL;
+
+ image = find_image(agent, &update_guid[i]);
+ if (!image)
+ goto error;
+
+ /* Deny explicitly asked read-only images */
+ if (!image->write)
+ goto error;
+
+ psa_status = psa_fwu_start(image->component, NULL, 0);
+ if (psa_status != PSA_SUCCESS)
+ goto error;
+
+ image->selected_for_staging = true;
+ }
+ } else {
+ /* Put all images into staging state */
+ for (i = 0; i < agent->image_count; i++) {
+ struct psa_fwu_m_image *image = &agent->images[i];
+
+ /* Skip read-only images */
+ if (!image->write)
+ continue;
+
+ psa_status = psa_fwu_start(image->component, NULL, 0);
+ if (psa_status != PSA_SUCCESS)
+ goto error;
+
+ image->selected_for_staging = true;
+ }
+ }
+
+ set_agent_state(agent, staging);
+
+ return FWU_STATUS_SUCCESS;
+
+ default:
+ /* Calling begin_staging in other states is deined */
+ return FWU_STATUS_DENIED;
+ }
+
+error:
+ /* Revert everything to regular state */
+ result = clean(context);
+ if (result != FWU_STATUS_SUCCESS)
+ return result;
+
+ return FWU_STATUS_UNKNOWN;
+}
+
+static int end_staging(void *context)
+{
+ struct psa_fwu_m_update_agent *agent = (struct psa_fwu_m_update_agent *)context;
+ psa_status_t psa_status = PSA_ERROR_GENERIC_ERROR;
+ struct psa_fwu_m_image *image = NULL;
+ bool all_images_accepted = true;
+ size_t i = 0;
+
+ if (agent->state != staging)
+ return FWU_STATUS_DENIED;
+
+ /* Check if there are open image handles */
+ for (i = 0; i < ARRAY_SIZE(agent->handles); i++)
+ if (agent->handles[i].used)
+ return FWU_STATUS_BUSY;
+
+ /* Finish images which were selected for staging */
+ for (i = 0; i < agent->image_count; i++) {
+ image = &agent->images[i];
+
+ if (!image->selected_for_staging)
+ continue;
+
+ psa_status = psa_fwu_finish(image->component);
+ if (psa_status != PSA_SUCCESS)
+ return psa_status_to_fwu_status(psa_status);
+
+ if (!image->accepted)
+ all_images_accepted = false;
+ }
+
+ /* Invoke install step */
+ psa_status = psa_fwu_install();
+ if (psa_status != PSA_SUCCESS)
+ return psa_status_to_fwu_status(psa_status);
+
+ if (all_images_accepted) {
+ /* If all images are accepted then accept the update and jump to regular state */
+ psa_status = psa_fwu_accept();
+ if (psa_status != PSA_SUCCESS)
+ return psa_status_to_fwu_status(psa_status);
+
+ set_agent_state(agent, regular);
+ } else {
+ /* There are images which are not accepted, switch to trial state */
+ set_agent_state(agent, trial);
+ }
+
+ return FWU_STATUS_SUCCESS;
+}
+
+static int cancel_staging(void *context)
+{
+ struct psa_fwu_m_update_agent *agent = (struct psa_fwu_m_update_agent *)context;
+ psa_status_t psa_status = PSA_ERROR_GENERIC_ERROR;
+ size_t i = 0;
+
+ if (agent->state != staging)
+ return FWU_STATUS_DENIED;
+
+ /* Close all images */
+ for (i = 0; i < ARRAY_SIZE(agent->handles); i++)
+ free_handle(&agent->handles[i]);
+
+ /* Cancel all images */
+ for (i = 0; i < agent->image_count; i++) {
+ struct psa_fwu_m_image *image = &agent->images[i];
+
+ if (!image->selected_for_staging)
+ continue;
+
+ psa_status = psa_fwu_cancel(image->component);
+ if (psa_status != PSA_SUCCESS)
+ return psa_status_to_fwu_status(psa_status);
+
+ image->selected_for_staging = false;
+ image->accepted = false;
+ }
+
+ set_agent_state(agent, regular);
+
+ return FWU_STATUS_SUCCESS;
+}
+
+static int open(void *context, const struct uuid_octets *uuid, uint8_t op_type, uint32_t *handle)
+{
+ struct psa_fwu_m_update_agent *agent = (struct psa_fwu_m_update_agent *)context;
+ struct psa_fwu_m_image *image = NULL;
+
+ /* Write operation is only allowed in staging state */
+ if (op_type == FWU_OP_TYPE_WRITE && agent->state != staging)
+ return FWU_STATUS_DENIED;
+
+ image = find_image(agent, uuid);
+ if (!image)
+ return FWU_STATUS_UNKNOWN;
+
+ /* Check if the image was selected for staging of opening for write */
+ if (op_type == FWU_OP_TYPE_WRITE && !image->selected_for_staging)
+ return FWU_STATUS_DENIED;
+
+ /* Check if the image supports the required operation type */
+ if ((op_type == FWU_OP_TYPE_READ && !image->read) ||
+ (op_type == FWU_OP_TYPE_WRITE && !image->write))
+ return FWU_STATUS_NOT_AVAILABLE;
+
+ *handle = allocate_handle(agent, image, op_type);
+ if (*handle == FWU_INVALID_HANDLE)
+ return FWU_STATUS_NOT_AVAILABLE;
+
+ return FWU_STATUS_SUCCESS;
+}
+
+static int write_stream(void *context, uint32_t handle, const uint8_t *data, size_t data_len)
+{
+ struct psa_fwu_m_update_agent *agent = (struct psa_fwu_m_update_agent *)context;
+ struct psa_fwu_m_handle *handle_desc = NULL;
+ int result = FWU_STATUS_DENIED;
+ size_t offset_after_write = 0;
+
+ if (agent->state != staging)
+ return FWU_STATUS_DENIED;
+
+ handle_desc = get_handle(agent, handle);
+ if (!handle_desc)
+ return FWU_STATUS_UNKNOWN;
+
+ if (handle_desc->op_type != FWU_OP_TYPE_WRITE)
+ return FWU_STATUS_NO_PERMISSION;
+
+ if (ADD_OVERFLOW(handle_desc->current_offset, data_len, &offset_after_write))
+ return FWU_STATUS_OUT_OF_BOUNDS;
+
+ if (!handle_desc->image->selected_for_staging || !handle_desc->image->write)
+ return FWU_STATUS_DENIED; /* LCOV_EXCL_LINE */
+
+ result = handle_desc->image->write(agent, handle_desc->image, handle_desc->current_offset,
+ data, data_len);
+ if (result != FWU_STATUS_SUCCESS)
+ return result;
+
+ handle_desc->current_offset = offset_after_write;
+
+ return FWU_STATUS_SUCCESS;
+}
+
+static int read_stream(void *context, uint32_t handle, uint8_t *buf, size_t buf_size,
+ size_t *read_len, size_t *total_len)
+{
+ struct psa_fwu_m_update_agent *agent = (struct psa_fwu_m_update_agent *)context;
+ struct psa_fwu_m_handle *handle_desc = NULL;
+
+ handle_desc = get_handle(agent, handle);
+ if (!handle_desc)
+ return FWU_STATUS_UNKNOWN;
+
+ if (handle_desc->op_type != FWU_OP_TYPE_READ)
+ return FWU_STATUS_NO_PERMISSION;
+
+ if (!handle_desc->image->read)
+ return FWU_STATUS_DENIED; /* LCOV_EXCL_LINE */
+
+ return handle_desc->image->read(agent, handle_desc->image, buf, buf_size, read_len,
+ total_len);
+}
+
+static int commit(void *context, uint32_t handle, bool accepted, uint32_t max_atomic_len,
+ uint32_t *progress, uint32_t *total_work)
+{
+ struct psa_fwu_m_update_agent *agent = (struct psa_fwu_m_update_agent *)context;
+ struct psa_fwu_m_handle *handle_desc = NULL;
+
+ handle_desc = get_handle(agent, handle);
+ if (!handle_desc)
+ return FWU_STATUS_UNKNOWN;
+
+ if (handle_desc->image->selected_for_staging)
+ handle_desc->image->accepted = accepted;
+
+ free_handle(handle_desc);
+
+ *progress = 1;
+ *total_work = 1;
+
+ return FWU_STATUS_SUCCESS;
+}
+
+static int accept(void *context, const struct uuid_octets *image_type_uuid)
+{
+ struct psa_fwu_m_update_agent *agent = (struct psa_fwu_m_update_agent *)context;
+ psa_status_t psa_status = PSA_ERROR_GENERIC_ERROR;
+ struct psa_fwu_m_image *image = NULL;
+ size_t i = 0;
+
+ if (agent->state != trial)
+ return FWU_STATUS_DENIED;
+
+ image = find_image(agent, image_type_uuid);
+ if (!image)
+ return FWU_STATUS_UNKNOWN;
+
+ if (!image->selected_for_staging)
+ return FWU_STATUS_DENIED;
+
+ image->accepted = true;
+
+ /* Accept update if all images has been accepted */
+ for (i = 0; i < agent->image_count; i++) {
+ image = &agent->images[i];
+
+ if (!image->selected_for_staging)
+ continue;
+
+ if (!image->accepted)
+ return FWU_STATUS_SUCCESS;
+ }
+
+ psa_status = psa_fwu_accept();
+ if (psa_status != PSA_SUCCESS)
+ return psa_status_to_fwu_status(psa_status);
+
+ set_agent_state(agent, regular);
+
+ return FWU_STATUS_SUCCESS;
+}
+
+static int select_previous(void *context)
+{
+ struct psa_fwu_m_update_agent *agent = (struct psa_fwu_m_update_agent *)context;
+ psa_status_t psa_status = PSA_ERROR_GENERIC_ERROR;
+
+ if (agent->state != trial)
+ return FWU_STATUS_DENIED;
+
+ psa_status = psa_fwu_reject(0);
+ if (psa_status != PSA_SUCCESS)
+ return psa_status_to_fwu_status(psa_status);
+
+ set_agent_state(agent, regular);
+
+ return FWU_STATUS_SUCCESS;
+}
+
+static const struct update_agent_interface interface = {
+ .discover = discover,
+ .begin_staging = begin_staging,
+ .end_staging = end_staging,
+ .cancel_staging = cancel_staging,
+ .open = open,
+ .write_stream = write_stream,
+ .read_stream = read_stream,
+ .commit = commit,
+ .accept_image = accept,
+ .select_previous = select_previous,
+};
+
+struct update_agent *psa_fwu_m_update_agent_init(
+ const struct psa_fwu_m_image_mapping image_mapping[], size_t image_count,
+ uint32_t max_payload_size)
+{
+ struct psa_fwu_m_update_agent *context = NULL;
+ struct psa_fwu_m_image *images = NULL;
+ struct update_agent *agent = NULL;
+ size_t i = 0;
+
+ /* Allocate +1 image for the Image directory */
+ images = (struct psa_fwu_m_image *)calloc(image_count + 1, sizeof(*images));
+ if (!images)
+ return NULL; /* LCOV_EXCL_LINE */
+
+ context = (struct psa_fwu_m_update_agent *)calloc(1, sizeof(*context));
+ if (!context) {
+ /* LCOV_EXCL_START */
+ free(images);
+ return NULL;
+ /* LCOV_EXCL_STOP */
+ }
+
+ agent = (struct update_agent *)calloc(1, sizeof(*agent));
+ if (!agent) {
+ /* LCOV_EXCL_START */
+ free(images);
+ free(context);
+ return NULL;
+ /* LCOV_EXCL_STOP */
+ }
+
+ for (i = 0; i < image_count; i++) {
+ images[i].uuid = image_mapping[i].uuid;
+ images[i].component = image_mapping[i].component;
+ images[i].selected_for_staging = false;
+ images[i].read = NULL; /* Cannot read images */
+ images[i].write = image_write;
+ }
+
+ /* Insert Image directory as the last image */
+ uuid_octets_from_canonical(&images[image_count].uuid, FWU_DIRECTORY_CANONICAL_UUID);
+ images[image_count].component = 0;
+ images[image_count].selected_for_staging = false;
+ images[i].read = image_directory_read;
+ images[i].write = NULL; /* Cannot write Images directory */
+
+ context->images = images;
+ context->image_count = image_count + 1;
+ context->max_payload_size = max_payload_size;
+ context->state = regular;
+
+ agent->context = context;
+ agent->interface = &interface;
+
+ return agent;
+}
+
+void psa_fwu_m_update_agent_deinit(struct update_agent *update_agent)
+{
+ struct psa_fwu_m_update_agent *context =
+ (struct psa_fwu_m_update_agent *)update_agent->context;
+
+ free(context->images);
+ free(context);
+ free(update_agent);
+}
diff --git a/components/service/fwu/psa_fwu_m/agent/psa_fwu_m_update_agent.h b/components/service/fwu/psa_fwu_m/agent/psa_fwu_m_update_agent.h
new file mode 100644
index 0000000..3c06570
--- /dev/null
+++ b/components/service/fwu/psa_fwu_m/agent/psa_fwu_m_update_agent.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef PSA_FWU_M_UPDATE_AGENT_H
+#define PSA_FWU_M_UPDATE_AGENT_H
+
+#include "service/fwu/common/update_agent_interface.h"
+#include "service/fwu/psa_fwu_m/interface/update.h"
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct psa_fwu_m_image_mapping {
+ struct uuid_octets uuid;
+ psa_fwu_component_t component;
+};
+
+/**
+ * \brief Initialise the PSA FWU M update_agent
+ *
+ * \param[in] image_mapping Component mapping array
+ * \param[in] image_count Component mapping count
+ * \param[in] max_payload_size The maximum number of bytes that a payload can contain
+ *
+ * \return The update_agent
+ */
+struct update_agent *psa_fwu_m_update_agent_init(
+ const struct psa_fwu_m_image_mapping image_mapping[], size_t image_count,
+ uint32_t max_payload_size);
+
+/**
+ * \brief De-initialise the update agent
+ *
+ * \param[in] update_agent The subject update_agent
+ */
+void psa_fwu_m_update_agent_deinit(struct update_agent *update_agent);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PSA_FWU_M_UPDATE_AGENT_H */
diff --git a/components/service/fwu/psa_fwu_m/agent/test/component.cmake b/components/service/fwu/psa_fwu_m/agent/test/component.cmake
new file mode 100644
index 0000000..9ba929f
--- /dev/null
+++ b/components/service/fwu/psa_fwu_m/agent/test/component.cmake
@@ -0,0 +1,13 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2024, 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}/test_psa_fwu_m_update_agent.cpp"
+)
diff --git a/components/service/fwu/psa_fwu_m/agent/test/test_psa_fwu_m_update_agent.cpp b/components/service/fwu/psa_fwu_m/agent/test/test_psa_fwu_m_update_agent.cpp
new file mode 100644
index 0000000..de289ff
--- /dev/null
+++ b/components/service/fwu/psa_fwu_m/agent/test/test_psa_fwu_m_update_agent.cpp
@@ -0,0 +1,670 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#include "../psa_fwu_m_update_agent.h"
+#include "service/fwu/psa_fwu_m/interface/mock/mock_psa_fwu_m.h"
+#include "protocols/service/fwu/fwu_proto.h"
+#include <string.h>
+#include <CppUTest/TestHarness.h>
+#include <CppUTestExt/MockSupport.h>
+
+TEST_GROUP(psa_fwu_m_update_agent) {
+ TEST_SETUP() {
+ agent = psa_fwu_m_update_agent_init(mapping, 2, 4096);
+ handle = 0;
+ progress = 0;
+ total_work = 0;
+ }
+
+ TEST_TEARDOWN() {
+ psa_fwu_m_update_agent_deinit(agent);
+
+ mock().checkExpectations();
+ mock().clear();
+ }
+
+ void begin_staging() {
+ expect_mock_psa_fwu_start(mapping[0].component, NULL, 0, PSA_SUCCESS);
+ expect_mock_psa_fwu_start(mapping[1].component, NULL, 0, PSA_SUCCESS);
+ LONGS_EQUAL(FWU_STATUS_SUCCESS, update_agent_begin_staging(agent, 0, 0, NULL));
+ }
+
+ void end_staging() {
+ expect_mock_psa_fwu_finish(mapping[0].component, PSA_SUCCESS);
+ expect_mock_psa_fwu_finish(mapping[1].component, PSA_SUCCESS);
+
+ expect_mock_psa_fwu_install(PSA_SUCCESS);
+
+ LONGS_EQUAL(FWU_STATUS_SUCCESS, update_agent_end_staging(agent));
+ }
+
+ void open() {
+ LONGS_EQUAL(FWU_STATUS_SUCCESS,
+ update_agent_open(agent, &mapping[0].uuid, FWU_OP_TYPE_WRITE, &handle));
+ }
+
+ void write(const uint8_t *data, size_t data_len) {
+ expect_mock_psa_fwu_write(mapping[0].component, 0, NULL, 0, PSA_SUCCESS);
+ LONGS_EQUAL(FWU_STATUS_SUCCESS,
+ update_agent_write_stream(agent, handle, data, data_len));
+ }
+
+ struct update_agent *agent;
+ uint32_t handle;
+ uint32_t progress;
+ uint32_t total_work;
+
+ const psa_fwu_m_image_mapping mapping[2] = {
+ {
+ .uuid = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+ },
+ .component = 3
+ },
+ {
+ .uuid = {
+ 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08,
+ 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00
+ },
+ .component = 2
+ },
+ };
+
+ const struct uuid_octets image_directory_uuid = {
+ 0xde, 0xee, 0x58, 0xd9, 0x51, 0x47, 0x4a, 0xd3,
+ 0xa2, 0x90, 0x77, 0x66, 0x6e, 0x23, 0x41, 0xa5
+ };
+};
+
+TEST(psa_fwu_m_update_agent, discover)
+{
+ fwu_discovery_result result = { 0 };
+
+ LONGS_EQUAL(FWU_STATUS_SUCCESS, update_agent_discover(agent, &result));
+
+ UNSIGNED_LONGS_EQUAL(0, result.service_status);
+ UNSIGNED_LONGS_EQUAL(1, result.version_major);
+ UNSIGNED_LONGS_EQUAL(0, result.version_minor);
+ UNSIGNED_LONGS_EQUAL(1, result.flags);
+}
+
+TEST(psa_fwu_m_update_agent, begin_staging_start_fail)
+{
+ expect_mock_psa_fwu_start(mapping[0].component, NULL, 0, PSA_ERROR_GENERIC_ERROR);
+ expect_mock_psa_fwu_clean(mapping[0].component, PSA_SUCCESS);
+ expect_mock_psa_fwu_clean(mapping[1].component, PSA_SUCCESS);
+ LONGS_EQUAL(FWU_STATUS_UNKNOWN, update_agent_begin_staging(agent, 0, 0, NULL));
+}
+
+TEST(psa_fwu_m_update_agent, begin_staging_start_and_clean_fail)
+{
+ expect_mock_psa_fwu_start(mapping[0].component, NULL, 0, PSA_ERROR_GENERIC_ERROR);
+ expect_mock_psa_fwu_clean(mapping[0].component, PSA_ERROR_GENERIC_ERROR);
+ LONGS_EQUAL(FWU_STATUS_DENIED, update_agent_begin_staging(agent, 0, 0, NULL));
+}
+
+TEST(psa_fwu_m_update_agent, begin_staging_partial_invalid_uuid)
+{
+ const struct uuid_octets update_guid = { 0 };
+
+ expect_mock_psa_fwu_clean(mapping[0].component, PSA_SUCCESS);
+ expect_mock_psa_fwu_clean(mapping[1].component, PSA_SUCCESS);
+
+ LONGS_EQUAL(FWU_STATUS_UNKNOWN, update_agent_begin_staging(agent, 0, 1, &update_guid));
+}
+
+TEST(psa_fwu_m_update_agent, begin_staging_partial_image_directory_uuid)
+{
+ expect_mock_psa_fwu_clean(mapping[0].component, PSA_SUCCESS);
+ expect_mock_psa_fwu_clean(mapping[1].component, PSA_SUCCESS);
+
+ LONGS_EQUAL(FWU_STATUS_UNKNOWN, update_agent_begin_staging(agent, 0, 1, &image_directory_uuid));
+}
+
+TEST(psa_fwu_m_update_agent, begin_staging_partial_start_fail)
+{
+ expect_mock_psa_fwu_start(mapping[0].component, NULL, 0, PSA_ERROR_GENERIC_ERROR);
+ expect_mock_psa_fwu_clean(mapping[0].component, PSA_SUCCESS);
+ expect_mock_psa_fwu_clean(mapping[1].component, PSA_SUCCESS);
+
+ LONGS_EQUAL(FWU_STATUS_UNKNOWN, update_agent_begin_staging(agent, 0, 1, &mapping[0].uuid));
+}
+
+TEST(psa_fwu_m_update_agent, begin_staging_partial_start_and_clean_fail)
+{
+ expect_mock_psa_fwu_start(mapping[0].component, NULL, 0, PSA_ERROR_GENERIC_ERROR);
+ expect_mock_psa_fwu_clean(mapping[0].component, PSA_ERROR_GENERIC_ERROR);
+
+ LONGS_EQUAL(FWU_STATUS_DENIED, update_agent_begin_staging(agent, 0, 1, &mapping[0].uuid));
+}
+
+TEST(psa_fwu_m_update_agent, begin_staging_partial)
+{
+ expect_mock_psa_fwu_start(mapping[0].component, NULL, 0, PSA_SUCCESS);
+
+ LONGS_EQUAL(FWU_STATUS_SUCCESS, update_agent_begin_staging(agent, 0, 1, &mapping[0].uuid));
+}
+
+TEST(psa_fwu_m_update_agent, begin_staging)
+{
+ begin_staging();
+}
+
+TEST(psa_fwu_m_update_agent, begin_staging_repeated_cancel_fail)
+{
+ begin_staging();
+
+ expect_mock_psa_fwu_cancel(mapping[0].component, PSA_SUCCESS);
+ expect_mock_psa_fwu_cancel(mapping[1].component, PSA_ERROR_GENERIC_ERROR);
+
+ expect_mock_psa_fwu_clean(mapping[0].component, PSA_SUCCESS);
+ expect_mock_psa_fwu_clean(mapping[1].component, PSA_SUCCESS);
+
+ LONGS_EQUAL(FWU_STATUS_UNKNOWN, update_agent_begin_staging(agent, 0, 0, NULL));
+}
+
+TEST(psa_fwu_m_update_agent, begin_staging_repeated_clean_fail)
+{
+ begin_staging();
+
+ expect_mock_psa_fwu_cancel(mapping[0].component, PSA_SUCCESS);
+ expect_mock_psa_fwu_cancel(mapping[1].component, PSA_SUCCESS);
+
+ expect_mock_psa_fwu_clean(mapping[0].component, PSA_ERROR_GENERIC_ERROR);
+
+ LONGS_EQUAL(FWU_STATUS_DENIED, update_agent_begin_staging(agent, 0, 0, NULL));
+}
+
+TEST(psa_fwu_m_update_agent, begin_staging_repeated)
+{
+ begin_staging();
+
+ expect_mock_psa_fwu_cancel(mapping[0].component, PSA_SUCCESS);
+ expect_mock_psa_fwu_cancel(mapping[1].component, PSA_SUCCESS);
+
+ expect_mock_psa_fwu_clean(mapping[0].component, PSA_SUCCESS);
+ expect_mock_psa_fwu_clean(mapping[1].component, PSA_SUCCESS);
+
+ expect_mock_psa_fwu_start(mapping[0].component, NULL, 0, PSA_SUCCESS);
+ expect_mock_psa_fwu_start(mapping[1].component, NULL, 0, PSA_SUCCESS);
+ LONGS_EQUAL(FWU_STATUS_SUCCESS, update_agent_begin_staging(agent, 0, 0, NULL));
+}
+
+TEST(psa_fwu_m_update_agent, begin_staging_in_trial_state)
+{
+ begin_staging();
+ end_staging();
+ LONGS_EQUAL(FWU_STATUS_DENIED, update_agent_begin_staging(agent, 0, 0, NULL));
+}
+
+TEST(psa_fwu_m_update_agent, end_staging_not_in_staging)
+{
+ LONGS_EQUAL(FWU_STATUS_DENIED, update_agent_end_staging(agent));
+}
+
+TEST(psa_fwu_m_update_agent, end_staging_finish_fail)
+{
+ begin_staging();
+
+ expect_mock_psa_fwu_finish(mapping[0].component, PSA_SUCCESS);
+ expect_mock_psa_fwu_finish(mapping[1].component, PSA_ERROR_GENERIC_ERROR);
+
+ LONGS_EQUAL(FWU_STATUS_DENIED, update_agent_end_staging(agent));
+}
+
+TEST(psa_fwu_m_update_agent, end_staging_install_fail)
+{
+ begin_staging();
+
+ expect_mock_psa_fwu_finish(mapping[0].component, PSA_SUCCESS);
+ expect_mock_psa_fwu_finish(mapping[1].component, PSA_SUCCESS);
+
+ expect_mock_psa_fwu_install(PSA_ERROR_GENERIC_ERROR);
+
+ LONGS_EQUAL(FWU_STATUS_DENIED, update_agent_end_staging(agent));
+}
+
+TEST(psa_fwu_m_update_agent, end_staging)
+{
+ begin_staging();
+ end_staging();
+
+ // In trial state, so a repeated begin_staging should not succeed
+ LONGS_EQUAL(FWU_STATUS_DENIED, update_agent_begin_staging(agent, 0, 0, NULL));
+}
+
+TEST(psa_fwu_m_update_agent, end_staging_with_opened_handles)
+{
+ begin_staging();
+ open();
+
+ LONGS_EQUAL(FWU_STATUS_BUSY, update_agent_end_staging(agent));
+}
+
+TEST(psa_fwu_m_update_agent, end_staging_all_accepted_accept_fail)
+{
+ begin_staging();
+
+ LONGS_EQUAL(FWU_STATUS_SUCCESS,
+ update_agent_open(agent, &mapping[0].uuid, FWU_OP_TYPE_WRITE, &handle));
+ LONGS_EQUAL(FWU_STATUS_SUCCESS,
+ update_agent_commit(agent, handle, true, 0, &progress, &total_work));
+
+ LONGS_EQUAL(FWU_STATUS_SUCCESS,
+ update_agent_open(agent, &mapping[1].uuid, FWU_OP_TYPE_WRITE, &handle));
+ LONGS_EQUAL(FWU_STATUS_SUCCESS,
+ update_agent_commit(agent, handle, true, 0, &progress, &total_work));
+
+ expect_mock_psa_fwu_finish(mapping[0].component, PSA_SUCCESS);
+ expect_mock_psa_fwu_finish(mapping[1].component, PSA_SUCCESS);
+
+ expect_mock_psa_fwu_install(PSA_SUCCESS);
+
+ expect_mock_psa_fwu_accept(PSA_ERROR_GENERIC_ERROR);
+
+ LONGS_EQUAL(FWU_STATUS_DENIED, update_agent_end_staging(agent));
+}
+
+TEST(psa_fwu_m_update_agent, end_staging_all_accepted)
+{
+ begin_staging();
+
+ LONGS_EQUAL(FWU_STATUS_SUCCESS,
+ update_agent_open(agent, &mapping[0].uuid, FWU_OP_TYPE_WRITE, &handle));
+ LONGS_EQUAL(FWU_STATUS_SUCCESS,
+ update_agent_commit(agent, handle, true, 0, &progress, &total_work));
+
+ LONGS_EQUAL(FWU_STATUS_SUCCESS,
+ update_agent_open(agent, &mapping[1].uuid, FWU_OP_TYPE_WRITE, &handle));
+ LONGS_EQUAL(FWU_STATUS_SUCCESS,
+ update_agent_commit(agent, handle, true, 0, &progress, &total_work));
+
+ expect_mock_psa_fwu_accept(PSA_SUCCESS);
+ end_staging();
+
+ // In regular state, repeated begin_staging should succeed
+ begin_staging();
+}
+
+TEST(psa_fwu_m_update_agent, cancel_staging_not_in_staging)
+{
+ LONGS_EQUAL(FWU_STATUS_DENIED, update_agent_cancel_staging(agent));
+}
+
+TEST(psa_fwu_m_update_agent, cancel_staging_cancel_fail)
+{
+ begin_staging();
+
+ expect_mock_psa_fwu_cancel(mapping[0].component, PSA_ERROR_GENERIC_ERROR);
+
+ LONGS_EQUAL(FWU_STATUS_DENIED, update_agent_cancel_staging(agent));
+}
+
+TEST(psa_fwu_m_update_agent, cancel_staging)
+{
+ begin_staging();
+
+ expect_mock_psa_fwu_cancel(mapping[0].component, PSA_SUCCESS);
+ expect_mock_psa_fwu_cancel(mapping[1].component, PSA_SUCCESS);
+
+ LONGS_EQUAL(FWU_STATUS_SUCCESS, update_agent_cancel_staging(agent));
+}
+
+TEST(psa_fwu_m_update_agent, open_for_write_not_staging)
+{
+ LONGS_EQUAL(FWU_STATUS_DENIED, update_agent_open(agent, &mapping[0].uuid,
+ FWU_OP_TYPE_WRITE, &handle));
+}
+
+
+TEST(psa_fwu_m_update_agent, open_for_write_uuid_not_exists)
+{
+ const struct uuid_octets uuid = { 0 };
+
+ begin_staging();
+
+ LONGS_EQUAL(FWU_STATUS_UNKNOWN, update_agent_open(agent, &uuid, FWU_OP_TYPE_WRITE,
+ &handle));
+}
+
+TEST(psa_fwu_m_update_agent, open_for_write_image_directory)
+{
+ begin_staging();
+
+ LONGS_EQUAL(FWU_STATUS_DENIED,
+ update_agent_open(agent, &image_directory_uuid, FWU_OP_TYPE_WRITE, &handle));
+}
+
+TEST(psa_fwu_m_update_agent, open_for_write_partial_not_staging)
+{
+ expect_mock_psa_fwu_start(mapping[0].component, NULL, 0, PSA_SUCCESS);
+
+ LONGS_EQUAL(FWU_STATUS_SUCCESS, update_agent_begin_staging(agent, 0, 1, &mapping[0].uuid));
+
+ LONGS_EQUAL(FWU_STATUS_DENIED,
+ update_agent_open(agent, &mapping[1].uuid, FWU_OP_TYPE_WRITE, &handle));
+}
+
+TEST(psa_fwu_m_update_agent, open_for_read)
+{
+ begin_staging();
+
+ LONGS_EQUAL(FWU_STATUS_NOT_AVAILABLE,
+ update_agent_open(agent, &mapping[0].uuid, FWU_OP_TYPE_READ, &handle));
+}
+
+TEST(psa_fwu_m_update_agent, open)
+{
+ begin_staging();
+ open();
+}
+
+TEST(psa_fwu_m_update_agent, open_too_many)
+{
+ begin_staging();
+
+ while (1) {
+ int result = FWU_STATUS_DENIED;
+
+ result = update_agent_open(agent, &mapping[0].uuid, FWU_OP_TYPE_WRITE, &handle);
+ if (result == FWU_STATUS_NOT_AVAILABLE) {
+ break;
+ } else {
+ LONGS_EQUAL(FWU_STATUS_SUCCESS, result);
+ }
+ }
+}
+
+TEST(psa_fwu_m_update_agent, write_stream_not_in_staging)
+{
+ LONGS_EQUAL(FWU_STATUS_DENIED, update_agent_write_stream(agent, 0, NULL, 0));
+}
+
+TEST(psa_fwu_m_update_agent, write_stream_invalid_handle)
+{
+ begin_staging();
+
+ LONGS_EQUAL(FWU_STATUS_UNKNOWN, update_agent_write_stream(agent, 0, NULL, 0));
+ LONGS_EQUAL(FWU_STATUS_UNKNOWN, update_agent_write_stream(agent, 0xffffffff, NULL, 0));
+}
+
+TEST(psa_fwu_m_update_agent, write_stream_opened_for_read)
+{
+ begin_staging();
+
+ LONGS_EQUAL(FWU_STATUS_SUCCESS,
+ update_agent_open(agent, &image_directory_uuid, FWU_OP_TYPE_READ, &handle));
+ LONGS_EQUAL(FWU_STATUS_NO_PERMISSION, update_agent_write_stream(agent, handle, NULL, 0));
+}
+
+TEST(psa_fwu_m_update_agent, write_stream_write_fail)
+{
+ begin_staging();
+ open();
+
+ expect_mock_psa_fwu_write(mapping[0].component, 0, NULL, 0, PSA_ERROR_GENERIC_ERROR);
+ LONGS_EQUAL(FWU_STATUS_DENIED, update_agent_write_stream(agent, handle, NULL, 0));
+}
+
+TEST(psa_fwu_m_update_agent, write_stream_overflow)
+{
+ uint8_t data[8];
+
+ memset(data, 0x5a, sizeof(data));
+
+ begin_staging();
+ open();
+
+ expect_mock_psa_fwu_write(mapping[0].component, 0, data, sizeof(data), PSA_SUCCESS);
+ LONGS_EQUAL(FWU_STATUS_SUCCESS,
+ update_agent_write_stream(agent, handle, data, sizeof(data)));
+
+ LONGS_EQUAL(FWU_STATUS_OUT_OF_BOUNDS,
+ update_agent_write_stream(agent, handle, NULL, 0xffffffffffffffff));
+}
+
+TEST(psa_fwu_m_update_agent, write_stream)
+{
+ uint8_t data[8];
+
+ memset(data, 0x5a, sizeof(data));
+
+ begin_staging();
+ open();
+
+ expect_mock_psa_fwu_write(mapping[0].component, 0, data, sizeof(data), PSA_SUCCESS);
+ LONGS_EQUAL(FWU_STATUS_SUCCESS,
+ update_agent_write_stream(agent, handle, data, sizeof(data)));
+
+ expect_mock_psa_fwu_write(mapping[0].component, sizeof(data), data, sizeof(data), PSA_SUCCESS);
+ LONGS_EQUAL(FWU_STATUS_SUCCESS,
+ update_agent_write_stream(agent, handle, data, sizeof(data)));
+}
+
+TEST(psa_fwu_m_update_agent, read_stream_invalid_handle)
+{
+ LONGS_EQUAL(FWU_STATUS_UNKNOWN, update_agent_read_stream(agent, 0, NULL, 0, NULL, NULL));
+ LONGS_EQUAL(FWU_STATUS_UNKNOWN,
+ update_agent_read_stream(agent, 0xffffffff, NULL, 0, NULL, NULL));
+}
+
+TEST(psa_fwu_m_update_agent, read_stream_opened_for_write)
+{
+ begin_staging();
+ open();
+
+ LONGS_EQUAL(FWU_STATUS_NO_PERMISSION,
+ update_agent_read_stream(agent, handle, NULL, 0, NULL, NULL));
+}
+
+TEST(psa_fwu_m_update_agent, read_image_directory_too_short)
+{
+ const size_t expected_len = sizeof(fwu_image_directory) + 2 * sizeof(fwu_image_info_entry);
+ size_t read_len = 0;
+ size_t total_len = 0;
+
+ LONGS_EQUAL(FWU_STATUS_SUCCESS,
+ update_agent_open(agent, &image_directory_uuid, FWU_OP_TYPE_READ, &handle));
+
+ LONGS_EQUAL(FWU_STATUS_SUCCESS,
+ update_agent_read_stream(agent, handle, NULL, 0, &read_len, &total_len));
+
+ UNSIGNED_LONGS_EQUAL(0, read_len);
+ UNSIGNED_LONGS_EQUAL(expected_len, total_len);
+}
+
+TEST(psa_fwu_m_update_agent, read_image_directory_query_fail)
+{
+ const size_t expected_len = sizeof(fwu_image_directory) + 2 * sizeof(fwu_image_info_entry);
+ uint8_t buffer[expected_len] = { 0 };
+ psa_fwu_component_info_t info = { 0 };
+ size_t read_len = 0;
+ size_t total_len = 0;
+
+ LONGS_EQUAL(FWU_STATUS_SUCCESS,
+ update_agent_open(agent, &image_directory_uuid, FWU_OP_TYPE_READ, &handle));
+
+ expect_mock_psa_fwu_query(mapping[0].component, &info, PSA_ERROR_GENERIC_ERROR);
+ LONGS_EQUAL(FWU_STATUS_DENIED,
+ update_agent_read_stream(agent, handle, buffer, sizeof(buffer), &read_len,
+ &total_len));
+
+ UNSIGNED_LONGS_EQUAL(0, read_len);
+ UNSIGNED_LONGS_EQUAL(expected_len, total_len);
+}
+
+TEST(psa_fwu_m_update_agent, read_image_directory)
+{
+ const size_t expected_len = sizeof(fwu_image_directory) + 2 * sizeof(fwu_image_info_entry);
+ uint8_t buffer[expected_len] = { 0 };
+ psa_fwu_component_info_t info0 = {
+ .state = PSA_FWU_REJECTED,
+ .version = {.major = 1, .minor = 2, .patch = 3},
+ .max_size = 123,
+
+ };
+ psa_fwu_component_info_t info1 = {
+ .state = PSA_FWU_UPDATED,
+ .version = {.major = 6, .minor = 7, .patch = 8},
+ .max_size = 456,
+ };
+ size_t read_len = 0;
+ size_t total_len = 0;
+
+ LONGS_EQUAL(FWU_STATUS_SUCCESS,
+ update_agent_open(agent, &image_directory_uuid, FWU_OP_TYPE_READ, &handle));
+
+ expect_mock_psa_fwu_query(mapping[0].component, &info0, PSA_SUCCESS);
+ expect_mock_psa_fwu_query(mapping[1].component, &info1, PSA_SUCCESS);
+ LONGS_EQUAL(FWU_STATUS_SUCCESS,
+ update_agent_read_stream(agent, handle, buffer, sizeof(buffer), &read_len,
+ &total_len));
+
+ UNSIGNED_LONGS_EQUAL(expected_len, read_len);
+ UNSIGNED_LONGS_EQUAL(expected_len, total_len);
+
+ struct fwu_image_directory *directory = (struct fwu_image_directory *)buffer;
+ UNSIGNED_LONGS_EQUAL(2, directory->directory_version);
+ UNSIGNED_LONGS_EQUAL(0x18, directory->img_info_offset);
+ UNSIGNED_LONGS_EQUAL(2, directory->num_images);
+ UNSIGNED_LONGS_EQUAL(0, directory->correct_boot);
+ UNSIGNED_LONGS_EQUAL(0x28, directory->img_info_size);
+
+ MEMCMP_EQUAL(&mapping[0].uuid, directory->img_info_entry[0].img_type_uuid,
+ sizeof(mapping[0].uuid));
+ UNSIGNED_LONGS_EQUAL(1, directory->img_info_entry[0].client_permissions);
+ UNSIGNED_LONGS_EQUAL(info0.max_size, directory->img_info_entry[0].img_max_size);
+ UNSIGNED_LONGS_EQUAL(0, directory->img_info_entry[0].lowest_accepted_version);
+ UNSIGNED_LONGLONGS_EQUAL(0x01020003, directory->img_info_entry[0].img_version);
+ UNSIGNED_LONGS_EQUAL(0, directory->img_info_entry[0].accepted);
+ UNSIGNED_LONGS_EQUAL(0, directory->img_info_entry[0].reserved);
+
+ MEMCMP_EQUAL(&mapping[1].uuid, directory->img_info_entry[1].img_type_uuid,
+ sizeof(mapping[1].uuid));
+ UNSIGNED_LONGS_EQUAL(1, directory->img_info_entry[1].client_permissions);
+ UNSIGNED_LONGS_EQUAL(info1.max_size, directory->img_info_entry[1].img_max_size);
+ UNSIGNED_LONGS_EQUAL(0, directory->img_info_entry[1].lowest_accepted_version);
+ UNSIGNED_LONGLONGS_EQUAL(0x06070008, directory->img_info_entry[1].img_version);
+ UNSIGNED_LONGS_EQUAL(1, directory->img_info_entry[1].accepted);
+ UNSIGNED_LONGS_EQUAL(0, directory->img_info_entry[1].reserved);
+}
+
+TEST(psa_fwu_m_update_agent, commit_invalid_handle)
+{
+ LONGS_EQUAL(FWU_STATUS_UNKNOWN, update_agent_commit(agent, 0, false, 0, &progress,
+ &total_work));
+ LONGS_EQUAL(FWU_STATUS_UNKNOWN, update_agent_commit(agent, 0xffffffff, false, 0, &progress,
+ &total_work));
+}
+
+TEST(psa_fwu_m_update_agent, commit_read)
+{
+ LONGS_EQUAL(FWU_STATUS_SUCCESS,
+ update_agent_open(agent, &image_directory_uuid, FWU_OP_TYPE_READ, &handle));
+
+ LONGS_EQUAL(FWU_STATUS_SUCCESS, update_agent_commit(agent, handle, false, 0, &progress,
+ &total_work));
+}
+
+TEST(psa_fwu_m_update_agent, commit_write)
+{
+ begin_staging();
+ open();
+
+ LONGS_EQUAL(FWU_STATUS_SUCCESS, update_agent_commit(agent, handle, false, 0, &progress,
+ &total_work));
+}
+
+TEST(psa_fwu_m_update_agent, accept_image_not_in_trial)
+{
+ LONGS_EQUAL(FWU_STATUS_DENIED, update_agent_accept_image(agent, NULL));
+}
+
+TEST(psa_fwu_m_update_agent, accept_image_invalid_uuid)
+{
+ struct uuid_octets uuid = { 0 };
+
+ begin_staging();
+ end_staging();
+ LONGS_EQUAL(FWU_STATUS_UNKNOWN, update_agent_accept_image(agent, &uuid));
+}
+
+TEST(psa_fwu_m_update_agent, accept_image_not_selected)
+{
+ begin_staging();
+ end_staging();
+ LONGS_EQUAL(FWU_STATUS_DENIED, update_agent_accept_image(agent, &image_directory_uuid));
+}
+
+TEST(psa_fwu_m_update_agent, accept_image_one)
+{
+ begin_staging();
+ end_staging();
+ LONGS_EQUAL(FWU_STATUS_SUCCESS, update_agent_accept_image(agent, &mapping[0].uuid));
+}
+
+TEST(psa_fwu_m_update_agent, accept_image_accept_fail)
+{
+ begin_staging();
+ end_staging();
+
+ expect_mock_psa_fwu_accept(PSA_ERROR_GENERIC_ERROR);
+ LONGS_EQUAL(FWU_STATUS_SUCCESS, update_agent_accept_image(agent, &mapping[0].uuid));
+ LONGS_EQUAL(FWU_STATUS_DENIED, update_agent_accept_image(agent, &mapping[1].uuid));
+}
+
+TEST(psa_fwu_m_update_agent, accept_image)
+{
+ begin_staging();
+ end_staging();
+
+ expect_mock_psa_fwu_accept(PSA_SUCCESS);
+ LONGS_EQUAL(FWU_STATUS_SUCCESS, update_agent_accept_image(agent, &mapping[0].uuid));
+ LONGS_EQUAL(FWU_STATUS_SUCCESS, update_agent_accept_image(agent, &mapping[1].uuid));
+}
+
+
+TEST(psa_fwu_m_update_agent, select_previous_not_in_trial)
+{
+ LONGS_EQUAL(FWU_STATUS_DENIED, update_agent_select_previous(agent));
+}
+
+TEST(psa_fwu_m_update_agent, select_previous_reject_fail)
+{
+ /* This test also covers error code conversion */
+ begin_staging();
+ end_staging();
+
+ expect_mock_psa_fwu_reject(0, PSA_ERROR_DOES_NOT_EXIST);
+ LONGS_EQUAL(FWU_STATUS_UNKNOWN, update_agent_select_previous(agent));
+
+ expect_mock_psa_fwu_reject(0, PSA_ERROR_INVALID_ARGUMENT);
+ LONGS_EQUAL(FWU_STATUS_OUT_OF_BOUNDS, update_agent_select_previous(agent));
+
+
+ expect_mock_psa_fwu_reject(0, PSA_ERROR_INVALID_SIGNATURE);
+ LONGS_EQUAL(FWU_STATUS_AUTH_FAIL, update_agent_select_previous(agent));
+
+
+ expect_mock_psa_fwu_reject(0, PSA_ERROR_NOT_PERMITTED);
+ LONGS_EQUAL(FWU_STATUS_NO_PERMISSION, update_agent_select_previous(agent));
+
+
+ expect_mock_psa_fwu_reject(0, PSA_ERROR_BAD_STATE);
+ LONGS_EQUAL(FWU_STATUS_DENIED, update_agent_select_previous(agent));
+}
+
+TEST(psa_fwu_m_update_agent, select_previous)
+{
+ /* This test also covers error code conversion */
+ begin_staging();
+ end_staging();
+
+ expect_mock_psa_fwu_reject(0, PSA_SUCCESS);
+ LONGS_EQUAL(FWU_STATUS_SUCCESS, update_agent_select_previous(agent));
+}
\ No newline at end of file
diff --git a/deployments/component-test/component-test.cmake b/deployments/component-test/component-test.cmake
index 625d348..8d4aaef 100644
--- a/deployments/component-test/component-test.cmake
+++ b/deployments/component-test/component-test.cmake
@@ -122,6 +122,8 @@
"components/service/fwu/inspector/direct"
"components/service/fwu/provider"
"components/service/fwu/provider/serializer"
+ "components/service/fwu/psa_fwu_m/agent"
+ "components/service/fwu/psa_fwu_m/agent/test"
"components/service/fwu/psa_fwu_m/interface/mock"
"components/service/fwu/psa_fwu_m/interface/mock/test"
"components/service/fwu/test/fwu_client/direct"