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/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;
+}